Implement git clone approach for nixos-rebuild

Replace direct directory access with git clone/pull approach:
- Add git configuration options (url, branch, working_dir) to NixOS module
- Update SystemConfig and AgentCommand to use git parameters
- Implement ensure_git_repository() method for clone/pull operations
- Agent clones nixosbox to /var/lib/cm-dashboard/nixos-config
- Maintains security while solving permission denied issues

The agent now manages its own copy of the configuration without
needing access to /home/cm directory.
This commit is contained in:
Christoffer Martinsson 2025-10-24 19:16:44 +02:00
parent 864cafd61f
commit b3c67f4b7f
5 changed files with 95 additions and 20 deletions

View File

@ -232,9 +232,9 @@ impl Agent {
error!("Failed to execute service control: {}", e);
}
}
AgentCommand::SystemRebuild { nixos_path } => {
info!("Processing SystemRebuild command with path: {}", nixos_path);
if let Err(e) = self.handle_system_rebuild(&nixos_path).await {
AgentCommand::SystemRebuild { git_url, git_branch, working_dir } => {
info!("Processing SystemRebuild command: {} @ {} -> {}", git_url, git_branch, working_dir);
if let Err(e) = self.handle_system_rebuild(&git_url, &git_branch, &working_dir).await {
error!("Failed to execute system rebuild: {}", e);
}
}
@ -281,9 +281,9 @@ impl Agent {
Ok(())
}
/// Handle NixOS system rebuild commands
async fn handle_system_rebuild(&self, nixos_path: &str) -> Result<()> {
info!("Starting NixOS system rebuild from path: {}", nixos_path);
/// Handle NixOS system rebuild commands with git clone approach
async fn handle_system_rebuild(&self, git_url: &str, git_branch: &str, working_dir: &str) -> Result<()> {
info!("Starting NixOS system rebuild: {} @ {} -> {}", git_url, git_branch, working_dir);
// Enable maintenance mode before rebuild
let maintenance_file = "/tmp/cm-maintenance";
@ -293,15 +293,23 @@ impl Agent {
info!("Maintenance mode enabled");
}
// Change to nixos directory and execute rebuild as cm user
let output = tokio::process::Command::new("sudo")
// Clone or update repository
let git_result = self.ensure_git_repository(git_url, git_branch, working_dir).await;
// Execute nixos-rebuild if git operation succeeded
let rebuild_result = if git_result.is_ok() {
info!("Git repository ready, executing nixos-rebuild");
tokio::process::Command::new("sudo")
.arg("-u")
.arg("cm")
.arg("nixos-rebuild")
.arg("switch")
.current_dir(nixos_path)
.current_dir(working_dir)
.output()
.await;
.await
} else {
return git_result.and_then(|_| unreachable!());
};
// Always try to remove maintenance mode file
if let Err(e) = tokio::fs::remove_file(maintenance_file).await {
@ -313,7 +321,7 @@ impl Agent {
}
// Check rebuild result
match output {
match rebuild_result {
Ok(output) => {
if output.status.success() {
info!("NixOS rebuild completed successfully");
@ -335,4 +343,54 @@ impl Agent {
info!("System rebuild completed, triggering metric refresh");
Ok(())
}
/// Ensure git repository is cloned and up to date
async fn ensure_git_repository(&self, git_url: &str, git_branch: &str, working_dir: &str) -> Result<()> {
use std::path::Path;
let git_dir = Path::new(working_dir).join(".git");
if git_dir.exists() {
info!("Git repository exists, updating to latest {}", git_branch);
// Pull latest changes
let output = tokio::process::Command::new("git")
.arg("pull")
.arg("origin")
.arg(git_branch)
.current_dir(working_dir)
.output()
.await?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
error!("Git pull failed: {}", stderr);
return Err(anyhow::anyhow!("Git pull failed: {}", stderr));
}
info!("Git repository updated successfully");
} else {
info!("Cloning git repository from {} (branch: {})", git_url, git_branch);
// Clone repository
let output = tokio::process::Command::new("git")
.arg("clone")
.arg("--branch")
.arg(git_branch)
.arg(git_url)
.arg(working_dir)
.output()
.await?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
error!("Git clone failed: {}", stderr);
return Err(anyhow::anyhow!("Git clone failed: {}", stderr));
}
info!("Git repository cloned successfully");
}
Ok(())
}
}

View File

@ -106,7 +106,9 @@ pub enum AgentCommand {
},
/// Rebuild NixOS system
SystemRebuild {
nixos_path: String, // Path to nixosbox directory
git_url: String,
git_branch: String,
working_dir: String,
},
}

View File

@ -22,6 +22,7 @@ pub struct Dashboard {
terminal: Option<Terminal<CrosstermBackend<io::Stdout>>>,
headless: bool,
initial_commands_sent: std::collections::HashSet<String>,
config: DashboardConfig,
}
impl Dashboard {
@ -132,6 +133,7 @@ impl Dashboard {
terminal,
headless,
initial_commands_sent: std::collections::HashSet::new(),
config,
})
}
@ -298,7 +300,9 @@ impl Dashboard {
UiCommand::SystemRebuild { hostname } => {
info!("Sending system rebuild command to {}", hostname);
let agent_command = AgentCommand::SystemRebuild {
nixos_path: "/home/cm/nixosbox".to_string(), // Fixed path per requirements
git_url: self.config.system.nixos_config_git_url.clone(),
git_branch: self.config.system.nixos_config_branch.clone(),
working_dir: self.config.system.nixos_config_working_dir.clone(),
};
self.zmq_command_sender.send_command(&hostname, agent_command).await?;
}

View File

@ -23,7 +23,9 @@ pub enum AgentCommand {
},
/// Rebuild NixOS system
SystemRebuild {
nixos_path: String, // Path to nixosbox directory
git_url: String,
git_branch: String,
working_dir: String,
},
}

View File

@ -7,6 +7,7 @@ use std::path::Path;
pub struct DashboardConfig {
pub zmq: ZmqConfig,
pub hosts: HostsConfig,
pub system: SystemConfig,
}
/// ZMQ consumer configuration
@ -21,6 +22,14 @@ pub struct HostsConfig {
pub predefined_hosts: Vec<String>,
}
/// System configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemConfig {
pub nixos_config_git_url: String,
pub nixos_config_branch: String,
pub nixos_config_working_dir: String,
}
impl DashboardConfig {
pub fn load_from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();