conf backup?
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const DEFAULT_CONFIG_PATH: &str = "/etc/wakey-agent/config.toml";
|
||||
|
||||
@@ -44,12 +44,53 @@ pub fn load_config(path: &Path) -> Result<AgentConfig> {
|
||||
}
|
||||
|
||||
pub fn save_config(path: &Path, config: &AgentConfig) -> Result<()> {
|
||||
let content = toml::to_string_pretty(config).context("failed to serialize agent config")?;
|
||||
write_config_atomically(path, &content)
|
||||
}
|
||||
|
||||
pub fn save_config_with_backup(path: &Path, config: &AgentConfig) -> Result<Option<PathBuf>> {
|
||||
let backup = if path.exists() {
|
||||
Some(snapshot_existing_config(path)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
save_config(path, config)?;
|
||||
Ok(backup)
|
||||
}
|
||||
|
||||
pub fn restore_backup(path: &Path, backup_path: &Path) -> Result<()> {
|
||||
let content = std::fs::read_to_string(backup_path)
|
||||
.with_context(|| format!("failed to read config backup {}", backup_path.display()))?;
|
||||
write_config_atomically(path, &content)
|
||||
}
|
||||
|
||||
fn snapshot_existing_config(path: &Path) -> Result<PathBuf> {
|
||||
let ts = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_secs();
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.unwrap_or("config.toml");
|
||||
let backup_name = format!("{file_name}.bak.{ts}");
|
||||
let backup_path = path.with_file_name(backup_name);
|
||||
std::fs::copy(path, &backup_path).with_context(|| {
|
||||
format!(
|
||||
"failed to create config backup {} from {}",
|
||||
backup_path.display(),
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
Ok(backup_path)
|
||||
}
|
||||
|
||||
fn write_config_atomically(path: &Path, content: &str) -> Result<()> {
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent)
|
||||
.with_context(|| format!("failed to create config dir {}", parent.display()))?;
|
||||
}
|
||||
|
||||
let content = toml::to_string_pretty(config).context("failed to serialize agent config")?;
|
||||
let tmp = path.with_extension("toml.tmp");
|
||||
std::fs::write(&tmp, content)
|
||||
.with_context(|| format!("failed to write temp config {}", tmp.display()))?;
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
use anyhow::{Context, Result};
|
||||
use reqwest::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::config::{AgentConfig, save_config};
|
||||
use crate::config::{AgentConfig, save_config_with_backup};
|
||||
|
||||
pub struct EnrollOutcome {
|
||||
pub config: AgentConfig,
|
||||
pub backup_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct EnrollRequest<'a> {
|
||||
@@ -22,7 +27,7 @@ pub async fn enroll(
|
||||
server_url: &str,
|
||||
enroll_token: &str,
|
||||
config_path: &Path,
|
||||
) -> Result<AgentConfig> {
|
||||
) -> Result<EnrollOutcome> {
|
||||
let server_url = normalize_server_url(server_url);
|
||||
let endpoint = format!("{server_url}/api/v1/agents/enroll");
|
||||
info!(endpoint = %endpoint, config_path = %config_path.display(), "starting agent enrollment");
|
||||
@@ -56,9 +61,12 @@ pub async fn enroll(
|
||||
reconnect_base_ms: 1_000,
|
||||
reconnect_max_ms: 30_000,
|
||||
};
|
||||
save_config(config_path, &config)?;
|
||||
let backup_path = save_config_with_backup(config_path, &config)?;
|
||||
info!(agent_id = %config.agent_id, config_path = %config_path.display(), "agent enrollment succeeded and config was written");
|
||||
Ok(config)
|
||||
Ok(EnrollOutcome {
|
||||
config,
|
||||
backup_path,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn normalize_server_url(server_url: &str) -> String {
|
||||
@@ -131,13 +139,15 @@ mod tests {
|
||||
));
|
||||
let path = dir.join("config.toml");
|
||||
|
||||
let config = enroll(&server_url, "enroll-abc", &path)
|
||||
let outcome = enroll(&server_url, "enroll-abc", &path)
|
||||
.await
|
||||
.expect("enroll should succeed");
|
||||
let config = outcome.config;
|
||||
|
||||
assert_eq!(config.agent_id, "agent-123");
|
||||
assert_eq!(config.agent_token, "token-xyz");
|
||||
assert_eq!(config.server_url, "https://control.example.com");
|
||||
assert!(outcome.backup_path.is_none());
|
||||
|
||||
let persisted = crate::config::load_config(&path).expect("load persisted config");
|
||||
assert_eq!(persisted, config);
|
||||
|
||||
+29
-3
@@ -23,15 +23,41 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
Command::Enroll(args) => {
|
||||
::tracing::info!(server_url = %args.server_url, config = %args.config.display(), "wakey-agent command: enroll");
|
||||
let config = enroll::enroll(&args.server_url, &args.enroll_token, &args.config).await?;
|
||||
println!("agent_id={}", config.agent_id);
|
||||
let outcome =
|
||||
enroll::enroll(&args.server_url, &args.enroll_token, &args.config).await?;
|
||||
println!("agent_id={}", outcome.config.agent_id);
|
||||
println!("config={}", args.config.display());
|
||||
println!("config_write=updated");
|
||||
if let Some(backup_path) = &outcome.backup_path {
|
||||
println!("config_backup={}", backup_path.display());
|
||||
}
|
||||
if args.reload_running {
|
||||
match serve::reload_daemon(&args.pid_file) {
|
||||
Ok(()) => println!("reload=signaled pid_file={}", args.pid_file.display()),
|
||||
Err(err) => {
|
||||
::tracing::warn!(error = %err, pid_file = %args.pid_file.display(), "enroll completed but daemon reload failed")
|
||||
::tracing::warn!(error = %err, pid_file = %args.pid_file.display(), "enroll completed but daemon reload failed");
|
||||
if let Some(backup_path) = &outcome.backup_path {
|
||||
match config::restore_backup(&args.config, backup_path) {
|
||||
Ok(()) => {
|
||||
println!("rollback=restored backup={}", backup_path.display());
|
||||
anyhow::bail!(
|
||||
"reload failed after enroll; restored previous config from {}",
|
||||
backup_path.display()
|
||||
);
|
||||
}
|
||||
Err(restore_err) => {
|
||||
anyhow::bail!(
|
||||
"reload failed after enroll and rollback failed: reload_error={}, rollback_error={}",
|
||||
err,
|
||||
restore_err
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"reload failed after enroll; no previous config backup was available"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user