Add API key support for git authentication

- Add nixos_config_api_key_file option to NixOS configuration
- Support reading API token from file for private repositories
- Automatically inject token into HTTPS URLs (https://token@host/repo.git)
- Graceful fallback to original URL if key file missing/empty
- Default key file location: /var/lib/cm-dashboard/git-api-key

Usage: echo 'your-api-token' | sudo tee /var/lib/cm-dashboard/git-api-key
This commit is contained in:
Christoffer Martinsson 2025-10-24 19:30:26 +02:00
parent 8978356c49
commit 114ad52ae8
5 changed files with 39 additions and 7 deletions

View File

@ -232,9 +232,9 @@ impl Agent {
error!("Failed to execute service control: {}", e); error!("Failed to execute service control: {}", e);
} }
} }
AgentCommand::SystemRebuild { git_url, git_branch, working_dir } => { AgentCommand::SystemRebuild { git_url, git_branch, working_dir, api_key_file } => {
info!("Processing SystemRebuild command: {} @ {} -> {}", 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 { if let Err(e) = self.handle_system_rebuild(&git_url, &git_branch, &working_dir, api_key_file.as_deref()).await {
error!("Failed to execute system rebuild: {}", e); error!("Failed to execute system rebuild: {}", e);
} }
} }
@ -282,7 +282,7 @@ impl Agent {
} }
/// Handle NixOS system rebuild commands with git clone approach /// 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<()> { async fn handle_system_rebuild(&self, git_url: &str, git_branch: &str, working_dir: &str, api_key_file: Option<&str>) -> Result<()> {
info!("Starting NixOS system rebuild: {} @ {} -> {}", git_url, git_branch, working_dir); info!("Starting NixOS system rebuild: {} @ {} -> {}", git_url, git_branch, working_dir);
// Enable maintenance mode before rebuild // Enable maintenance mode before rebuild
@ -294,7 +294,7 @@ impl Agent {
} }
// Clone or update repository // Clone or update repository
let git_result = self.ensure_git_repository(git_url, git_branch, working_dir).await; let git_result = self.ensure_git_repository(git_url, git_branch, working_dir, api_key_file).await;
// Execute nixos-rebuild if git operation succeeded // Execute nixos-rebuild if git operation succeeded
let rebuild_result = if git_result.is_ok() { let rebuild_result = if git_result.is_ok() {
@ -345,9 +345,37 @@ impl Agent {
} }
/// Ensure git repository is cloned and up to date /// 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<()> { async fn ensure_git_repository(&self, git_url: &str, git_branch: &str, working_dir: &str, api_key_file: Option<&str>) -> Result<()> {
use std::path::Path; use std::path::Path;
// Read API key if provided
let auth_url = if let Some(key_file) = api_key_file {
match tokio::fs::read_to_string(key_file).await {
Ok(api_key) => {
let api_key = api_key.trim();
if !api_key.is_empty() {
// Convert https://gitea.cmtec.se/cm/nixosbox.git to https://token@gitea.cmtec.se/cm/nixosbox.git
if git_url.starts_with("https://") {
let url_without_protocol = &git_url[8..]; // Remove "https://"
format!("https://{}@{}", api_key, url_without_protocol)
} else {
info!("API key provided but URL is not HTTPS, using original URL");
git_url.to_string()
}
} else {
info!("API key file is empty, using original URL");
git_url.to_string()
}
}
Err(e) => {
info!("Could not read API key file {}: {}, using original URL", key_file, e);
git_url.to_string()
}
}
} else {
git_url.to_string()
};
let git_dir = Path::new(working_dir).join(".git"); let git_dir = Path::new(working_dir).join(".git");
if git_dir.exists() { if git_dir.exists() {
@ -372,12 +400,12 @@ impl Agent {
} else { } else {
info!("Cloning git repository from {} (branch: {})", git_url, git_branch); info!("Cloning git repository from {} (branch: {})", git_url, git_branch);
// Clone repository // Clone repository with authentication if available
let output = tokio::process::Command::new("git") let output = tokio::process::Command::new("git")
.arg("clone") .arg("clone")
.arg("--branch") .arg("--branch")
.arg(git_branch) .arg(git_branch)
.arg(git_url) .arg(&auth_url) // Use authenticated URL
.arg(working_dir) .arg(working_dir)
.output() .output()
.await?; .await?;

View File

@ -109,6 +109,7 @@ pub enum AgentCommand {
git_url: String, git_url: String,
git_branch: String, git_branch: String,
working_dir: String, working_dir: String,
api_key_file: Option<String>,
}, },
} }

View File

@ -303,6 +303,7 @@ impl Dashboard {
git_url: self.config.system.nixos_config_git_url.clone(), git_url: self.config.system.nixos_config_git_url.clone(),
git_branch: self.config.system.nixos_config_branch.clone(), git_branch: self.config.system.nixos_config_branch.clone(),
working_dir: self.config.system.nixos_config_working_dir.clone(), working_dir: self.config.system.nixos_config_working_dir.clone(),
api_key_file: self.config.system.nixos_config_api_key_file.clone(),
}; };
self.zmq_command_sender.send_command(&hostname, agent_command).await?; self.zmq_command_sender.send_command(&hostname, agent_command).await?;
} }

View File

@ -26,6 +26,7 @@ pub enum AgentCommand {
git_url: String, git_url: String,
git_branch: String, git_branch: String,
working_dir: String, working_dir: String,
api_key_file: Option<String>,
}, },
} }

View File

@ -28,6 +28,7 @@ pub struct SystemConfig {
pub nixos_config_git_url: String, pub nixos_config_git_url: String,
pub nixos_config_branch: String, pub nixos_config_branch: String,
pub nixos_config_working_dir: String, pub nixos_config_working_dir: String,
pub nixos_config_api_key_file: Option<String>,
} }
impl DashboardConfig { impl DashboardConfig {