config file

This commit is contained in:
lda
2026-05-03 03:11:35 +07:00 Verified
parent b850c218ec
commit f5ed1ab8f6
6 changed files with 191 additions and 45 deletions
+9 -5
View File
@@ -7,6 +7,8 @@ param(
$PASSWD = Get-DefaultPassword $PASSWD $PASSWD = Get-DefaultPassword $PASSWD
Write-Output $PSScriptRoot
# idk what this does it works like that then thats how it is # idk what this does it works like that then thats how it is
pscp.exe -l root -scp -pw $PASSWD -r 192.168.100.1:/etc/ldlda_help $PSScriptRoot pscp.exe -l root -scp -pw $PASSWD -r 192.168.100.1:/etc/ldlda_help $PSScriptRoot
pscp.exe -l root -scp -pw $PASSWD 192.168.100.1:/etc/rc.local $PSScriptRoot pscp.exe -l root -scp -pw $PASSWD 192.168.100.1:/etc/rc.local $PSScriptRoot
@@ -23,13 +25,15 @@ New-Item -ItemType Directory -Force -Path $initDir | Out-Null
$files = "update_wakey" , "wakey" , "update_tailscale" , "wireguard_setup", "lda-override" $files = "update_wakey" , "wakey" , "update_tailscale" , "wireguard_setup", "lda-override"
$remote = $files.ForEach({ "/etc/init.d/$_" }) $remote = $files.ForEach({ "/etc/init.d/$_" })
$remote | ForEach-Object { $remote | ForEach-Object {
pscp.exe -l root -scp -pw $PASSWD "192.168.100.1:$_" "$initDir\" pscp.exe -l root -scp -pw $PASSWD "192.168.100.1:$_" "$initDir/"
} }
$hotplugDir = Join-Path $PSScriptRoot 'hotplug.d'
New-Item -ItemType Directory -Force -Path $hotplugDir | Out-Null
$files = "dhcp/95-wakey", "neigh/95-wakey" $files = "dhcp/95-wakey", "neigh/95-wakey"
$remote = $files.ForEach({ "/etc/hotplug.d/$_" })
$hotplugDir = Join-Path $PSScriptRoot 'hotplug.d'
$remote | ForEach-Object { $files | ForEach-Object {
pscp.exe -l root -scp -pw $PASSWD "192.168.100.1:$_" "$hotplugDir\" New-Item -ItemType Directory -Force -Path (Split-Path "$hotplugDir/$_")
pscp.exe -l root -scp -pw $PASSWD "192.168.100.1:/etc/hotplug.d/$_" "$hotplugDir/$_"
} }
+40 -11
View File
@@ -4,8 +4,6 @@ use clap::{ArgAction, Args, Parser, Subcommand};
use crate::config; use crate::config;
pub const DEFAULT_PID_FILE: &str = "/var/run/wakey-agent.pid";
#[derive(Parser)] #[derive(Parser)]
#[command(name = "wakey-agent")] #[command(name = "wakey-agent")]
#[command(version, about = "Outbound control-plane agent for Wakey")] #[command(version, about = "Outbound control-plane agent for Wakey")]
@@ -42,9 +40,9 @@ pub struct ServeArgs {
#[arg(long, short = 'c', default_value = config::DEFAULT_CONFIG_PATH)] #[arg(long, short = 'c', default_value = config::DEFAULT_CONFIG_PATH)]
pub config: PathBuf, pub config: PathBuf,
/// Path to pid file for reload signaling. /// Path to pid file. Overrides `pid_file` in config.
#[arg(long, default_value = DEFAULT_PID_FILE)] #[arg(long)]
pub pid_file: PathBuf, pub pid_file: Option<PathBuf>,
} }
#[derive(Args)] #[derive(Args)]
@@ -69,9 +67,9 @@ pub struct EnrollArgs {
#[arg(long)] #[arg(long)]
pub reload_running: bool, pub reload_running: bool,
/// Path to pid file used when `--reload-running` is enabled. /// Path to pid file used when `--reload-running` is enabled. Overrides `pid_file` in config.
#[arg(long, default_value = DEFAULT_PID_FILE)] #[arg(long)]
pub pid_file: PathBuf, pub pid_file: Option<PathBuf>,
} }
#[derive(Args)] #[derive(Args)]
@@ -107,9 +105,9 @@ pub struct InitConfigArgs {
#[derive(Args)] #[derive(Args)]
pub struct ReloadArgs { pub struct ReloadArgs {
/// Path to pid file for reload signaling. /// Path to pid file for reload signaling. Overrides `pid_file` in config.
#[arg(long, default_value = DEFAULT_PID_FILE)] #[arg(long)]
pub pid_file: PathBuf, pub pid_file: Option<PathBuf>,
} }
#[derive(Args)] #[derive(Args)]
@@ -151,3 +149,34 @@ pub struct ObserveNeighArgs {
#[arg(long)] #[arg(long)]
pub ip: Option<String>, pub ip: Option<String>,
} }
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn global_config_is_accepted_before_enroll() {
let cli = Cli::try_parse_from([
"wakey-agent",
"--config",
"/tmp/wakey-agent.toml",
"enroll",
"--server-url",
"https://wakey.example.com",
"--enroll-token",
"token-123",
])
.expect("global --config should parse before enroll");
assert_eq!(cli.config, Some(PathBuf::from("/tmp/wakey-agent.toml")));
let Command::Enroll(args) = cli.command else {
panic!("expected enroll command");
};
assert_eq!(
args.server_url.as_deref(),
Some("https://wakey.example.com")
);
assert_eq!(args.enroll_token, "token-123");
}
}
+9
View File
@@ -4,6 +4,7 @@ use std::fmt;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub const DEFAULT_CONFIG_PATH: &str = "/etc/wakey-agent/config.toml"; pub const DEFAULT_CONFIG_PATH: &str = "/etc/wakey-agent/config.toml";
pub const DEFAULT_PID_FILE: &str = "/var/run/wakey-agent.pid";
const WAKEY_DHCP_LEASES_ENV: &str = "WAKEY_DHCP_LEASES"; const WAKEY_DHCP_LEASES_ENV: &str = "WAKEY_DHCP_LEASES";
const WAKEY_MAC_NAME_CACHE_ENV: &str = "WAKEY_MAC_NAME_CACHE"; const WAKEY_MAC_NAME_CACHE_ENV: &str = "WAKEY_MAC_NAME_CACHE";
const WAKEY_OBSERVATION_STORE_ENV: &str = "WAKEY_OBSERVATION_STORE"; const WAKEY_OBSERVATION_STORE_ENV: &str = "WAKEY_OBSERVATION_STORE";
@@ -22,6 +23,8 @@ pub struct AgentConfig {
pub reconnect_max_ms: u64, pub reconnect_max_ms: u64,
#[serde(default = "default_observation_sync_interval_seconds")] #[serde(default = "default_observation_sync_interval_seconds")]
pub observation_sync_interval_seconds: u64, pub observation_sync_interval_seconds: u64,
#[serde(default = "default_pid_file")]
pub pid_file: PathBuf,
#[serde(default = "default_dhcp_leases_path")] #[serde(default = "default_dhcp_leases_path")]
pub dhcp_leases_path: PathBuf, pub dhcp_leases_path: PathBuf,
#[serde(default = "default_mac_name_cache_path")] #[serde(default = "default_mac_name_cache_path")]
@@ -42,6 +45,7 @@ impl fmt::Debug for AgentConfig {
"observation_sync_interval_seconds", "observation_sync_interval_seconds",
&self.observation_sync_interval_seconds, &self.observation_sync_interval_seconds,
) )
.field("pid_file", &self.pid_file)
.field("dhcp_leases_path", &self.dhcp_leases_path) .field("dhcp_leases_path", &self.dhcp_leases_path)
.field("mac_name_cache_path", &self.mac_name_cache_path) .field("mac_name_cache_path", &self.mac_name_cache_path)
.field("observation_store_path", &self.observation_store_path) .field("observation_store_path", &self.observation_store_path)
@@ -61,6 +65,10 @@ const fn default_observation_sync_interval_seconds() -> u64 {
60 60
} }
fn default_pid_file() -> PathBuf {
DEFAULT_PID_FILE.into()
}
fn default_dhcp_leases_path() -> PathBuf { fn default_dhcp_leases_path() -> PathBuf {
DEFAULT_DHCP_LEASES_PATH.into() DEFAULT_DHCP_LEASES_PATH.into()
} }
@@ -175,6 +183,7 @@ mod tests {
reconnect_base_ms: 123, reconnect_base_ms: 123,
reconnect_max_ms: 456, reconnect_max_ms: 456,
observation_sync_interval_seconds: 7, observation_sync_interval_seconds: 7,
pid_file: "/tmp/test-wakey-agent.pid".into(),
dhcp_leases_path: "/tmp/test-dhcp.leases".into(), dhcp_leases_path: "/tmp/test-dhcp.leases".into(),
mac_name_cache_path: "/tmp/test-names.json".into(), mac_name_cache_path: "/tmp/test-names.json".into(),
observation_store_path: "/tmp/test-observations.json".into(), observation_store_path: "/tmp/test-observations.json".into(),
+38 -7
View File
@@ -27,6 +27,7 @@ pub async fn enroll(
server_url: &str, server_url: &str,
enroll_token: &str, enroll_token: &str,
config_path: &Path, config_path: &Path,
base_config: Option<&AgentConfig>,
) -> Result<EnrollOutcome> { ) -> Result<EnrollOutcome> {
let server_url = normalize_server_url(server_url); let server_url = normalize_server_url(server_url);
let endpoint = format!("{server_url}/api/v1/agents/enroll"); let endpoint = format!("{server_url}/api/v1/agents/enroll");
@@ -58,12 +59,27 @@ pub async fn enroll(
server_url: payload.server_url.unwrap_or(server_url), server_url: payload.server_url.unwrap_or(server_url),
agent_id: payload.agent_id, agent_id: payload.agent_id,
agent_token: payload.agent_token, agent_token: payload.agent_token,
reconnect_base_ms: 1_000, reconnect_base_ms: base_config
reconnect_max_ms: 30_000, .map(|config| config.reconnect_base_ms)
observation_sync_interval_seconds: 60, .unwrap_or(1_000),
dhcp_leases_path: "/tmp/dhcp.leases".into(), reconnect_max_ms: base_config
mac_name_cache_path: "/tmp/wakey_mac_names.json".into(), .map(|config| config.reconnect_max_ms)
observation_store_path: "/tmp/wakey_observations.json".into(), .unwrap_or(30_000),
observation_sync_interval_seconds: base_config
.map(|config| config.observation_sync_interval_seconds)
.unwrap_or(60),
pid_file: base_config
.map(|config| config.pid_file.clone())
.unwrap_or_else(|| crate::config::DEFAULT_PID_FILE.into()),
dhcp_leases_path: base_config
.map(|config| config.dhcp_leases_path.clone())
.unwrap_or_else(|| "/tmp/dhcp.leases".into()),
mac_name_cache_path: base_config
.map(|config| config.mac_name_cache_path.clone())
.unwrap_or_else(|| "/tmp/wakey_mac_names.json".into()),
observation_store_path: base_config
.map(|config| config.observation_store_path.clone())
.unwrap_or_else(|| "/tmp/wakey_observations.json".into()),
}; };
let backup_path = save_config_with_backup(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"); info!(agent_id = %config.agent_id, config_path = %config_path.display(), "agent enrollment succeeded and config was written");
@@ -143,7 +159,20 @@ mod tests {
)); ));
let path = dir.join("config.toml"); let path = dir.join("config.toml");
let outcome = enroll(&server_url, "enroll-abc", &path) let base_config = AgentConfig {
server_url: "https://old.example.com".into(),
agent_id: "old-agent".into(),
agent_token: "old-token".into(),
reconnect_base_ms: 2_000,
reconnect_max_ms: 60_000,
observation_sync_interval_seconds: 30,
pid_file: "/tmp/custom-wakey-agent.pid".into(),
dhcp_leases_path: "/tmp/custom-dhcp.leases".into(),
mac_name_cache_path: "/tmp/custom-names.json".into(),
observation_store_path: "/tmp/custom-observations.json".into(),
};
let outcome = enroll(&server_url, "enroll-abc", &path, Some(&base_config))
.await .await
.expect("enroll should succeed"); .expect("enroll should succeed");
let config = outcome.config; let config = outcome.config;
@@ -151,6 +180,8 @@ mod tests {
assert_eq!(config.agent_id, "agent-123"); assert_eq!(config.agent_id, "agent-123");
assert_eq!(config.agent_token, "token-xyz"); assert_eq!(config.agent_token, "token-xyz");
assert_eq!(config.server_url, "https://control.example.com"); assert_eq!(config.server_url, "https://control.example.com");
assert_eq!(config.pid_file, base_config.pid_file);
assert_eq!(config.dhcp_leases_path, base_config.dhcp_leases_path);
assert!(outcome.backup_path.is_none()); assert!(outcome.backup_path.is_none());
let persisted = crate::config::load_config(&path).expect("load persisted config"); let persisted = crate::config::load_config(&path).expect("load persisted config");
+87 -15
View File
@@ -29,21 +29,30 @@ async fn main() -> Result<()> {
if let Some(config) = global_config { if let Some(config) = global_config {
args.config = config.to_path_buf(); args.config = config.to_path_buf();
} }
let existing_config = config::load_config(&args.config).ok();
let resolved_server_url = if let Some(server_url) = args.server_url.as_deref() { let resolved_server_url = if let Some(server_url) = args.server_url.as_deref() {
server_url.to_string() server_url.to_string()
} else if let Some(cfg) = existing_config.as_ref() {
cfg.server_url.clone()
} else { } else {
match config::load_config(&args.config) { anyhow::bail!(
Ok(cfg) => cfg.server_url,
Err(_) => anyhow::bail!(
"missing control-plane URL: pass --server-url or provide server_url in {}", "missing control-plane URL: pass --server-url or provide server_url in {}",
args.config.display() args.config.display()
), );
}
}; };
::tracing::info!(server_url = %resolved_server_url, config = %args.config.display(), "wakey-agent command: enroll"); ::tracing::info!(server_url = %resolved_server_url, config = %args.config.display(), "wakey-agent command: enroll");
let outcome = let outcome = enroll::enroll(
enroll::enroll(&resolved_server_url, &args.enroll_token, &args.config).await?; &resolved_server_url,
&args.enroll_token,
&args.config,
existing_config.as_ref(),
)
.await?;
let pid_file = args
.pid_file
.as_deref()
.unwrap_or(outcome.config.pid_file.as_path());
println!("agent_id={}", outcome.config.agent_id); println!("agent_id={}", outcome.config.agent_id);
println!("config={}", args.config.display()); println!("config={}", args.config.display());
println!("config_write=updated"); println!("config_write=updated");
@@ -51,10 +60,10 @@ async fn main() -> Result<()> {
println!("config_backup={}", backup_path.display()); println!("config_backup={}", backup_path.display());
} }
if args.reload_running { if args.reload_running {
match serve::reload_daemon(&args.pid_file) { match serve::reload_daemon(pid_file) {
Ok(()) => println!("reload=signaled pid_file={}", args.pid_file.display()), Ok(()) => println!("reload=signaled pid_file={}", pid_file.display()),
Err(err) => { Err(err) => {
::tracing::warn!(error = %err, pid_file = %args.pid_file.display(), "enroll completed but daemon reload failed"); ::tracing::warn!(error = %err, pid_file = %pid_file.display(), "enroll completed but daemon reload failed");
if let Some(backup_path) = &outcome.backup_path { if let Some(backup_path) = &outcome.backup_path {
match config::restore_backup(&args.config, backup_path) { match config::restore_backup(&args.config, backup_path) {
Ok(()) => { Ok(()) => {
@@ -83,9 +92,10 @@ async fn main() -> Result<()> {
println!("reload=not_requested"); println!("reload=not_requested");
println!("runtime_config=unchanged_until_reload_or_restart"); println!("runtime_config=unchanged_until_reload_or_restart");
println!( println!(
"next=wakey-agent reload --pid-file {} # or restart wakey-agent", "next=wakey-agent reload --pid-file {} # if daemon is already running",
args.pid_file.display() pid_file.display()
); );
println!("run={}", serve_command_for_config(&args.config));
} }
} }
Command::InitConfig(mut args) => { Command::InitConfig(mut args) => {
@@ -98,8 +108,9 @@ async fn main() -> Result<()> {
init_config(args)? init_config(args)?
} }
Command::Reload(args) => { Command::Reload(args) => {
::tracing::info!(pid_file = %args.pid_file.display(), "wakey-agent command: reload"); let pid_file = resolve_pid_file(global_config, args.pid_file.as_deref())?;
serve::reload_daemon(&args.pid_file)? ::tracing::info!(pid_file = %pid_file.display(), "wakey-agent command: reload");
serve::reload_daemon(&pid_file)?
} }
Command::Observe(mut args) => { Command::Observe(mut args) => {
if let Some(config) = global_config { if let Some(config) = global_config {
@@ -112,6 +123,27 @@ async fn main() -> Result<()> {
Ok(()) Ok(())
} }
fn serve_command_for_config(config: &std::path::Path) -> String {
if config == std::path::Path::new(config::DEFAULT_CONFIG_PATH) {
"wakey-agent serve".to_string()
} else {
format!("wakey-agent --config {} serve", config.display())
}
}
fn resolve_pid_file(
config_path: Option<&std::path::Path>,
explicit_pid_file: Option<&std::path::Path>,
) -> Result<std::path::PathBuf> {
if let Some(pid_file) = explicit_pid_file {
return Ok(pid_file.to_path_buf());
}
if let Some(config_path) = config_path {
return Ok(config::load_config(config_path)?.pid_file);
}
Ok(config::DEFAULT_PID_FILE.into())
}
fn observe(args: cli::ObserveArgs) -> Result<()> { fn observe(args: cli::ObserveArgs) -> Result<()> {
let mut cmd = std::process::Command::new(resolve_wakey_binary()); let mut cmd = std::process::Command::new(resolve_wakey_binary());
cmd.arg("observe"); cmd.arg("observe");
@@ -211,6 +243,7 @@ fn init_config(args: InitConfigArgs) -> Result<()> {
reconnect_base_ms: 1_000, reconnect_base_ms: 1_000,
reconnect_max_ms: 30_000, reconnect_max_ms: 30_000,
observation_sync_interval_seconds: 60, observation_sync_interval_seconds: 60,
pid_file: config::DEFAULT_PID_FILE.into(),
dhcp_leases_path: "/tmp/dhcp.leases".into(), dhcp_leases_path: "/tmp/dhcp.leases".into(),
mac_name_cache_path: "/tmp/wakey_mac_names.json".into(), mac_name_cache_path: "/tmp/wakey_mac_names.json".into(),
observation_store_path: "/tmp/wakey_observations.json".into(), observation_store_path: "/tmp/wakey_observations.json".into(),
@@ -230,10 +263,49 @@ fn init_config(args: InitConfigArgs) -> Result<()> {
if let Some(path) = &args.config { if let Some(path) = &args.config {
config::save_config(path, &cfg)?; config::save_config(path, &cfg)?;
println!("config={}", path.display()); println!("config={}", path.display());
println!("next=wakey-agent serve --config {}", path.display()); println!("run={}", serve_command_for_config(path));
} else { } else {
let rendered = toml::to_string_pretty(&cfg)?; let rendered = toml::to_string_pretty(&cfg)?;
print!("{}", rendered); print!("{}", rendered);
} }
Ok(()) Ok(())
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serve_command_omits_default_config_path() {
assert_eq!(
serve_command_for_config(std::path::Path::new(config::DEFAULT_CONFIG_PATH)),
"wakey-agent serve"
);
}
#[test]
fn serve_command_includes_custom_config_path() {
assert_eq!(
serve_command_for_config(std::path::Path::new("/tmp/wakey-agent.toml")),
"wakey-agent --config /tmp/wakey-agent.toml serve"
);
}
#[test]
fn explicit_pid_file_wins_without_loading_config() {
let pid_file = resolve_pid_file(
Some(std::path::Path::new("/tmp/missing-agent.toml")),
Some(std::path::Path::new("/tmp/explicit.pid")),
)
.expect("explicit pid should resolve");
assert_eq!(pid_file, std::path::PathBuf::from("/tmp/explicit.pid"));
}
#[test]
fn pid_file_defaults_when_no_config_is_available() {
let pid_file = resolve_pid_file(None, None).expect("default pid should resolve");
assert_eq!(pid_file, std::path::PathBuf::from(config::DEFAULT_PID_FILE));
}
}
+6 -5
View File
@@ -16,10 +16,11 @@ pub async fn serve(args: ServeArgs) -> Result<()> {
); );
} }
write_pid_file(&args.pid_file)?;
info!(pid_file = %args.pid_file.display(), "wrote wakey-agent pid file");
let mut cfg = config::load_config(&args.config)?; let mut cfg = config::load_config(&args.config)?;
let pid_file = args.pid_file.unwrap_or_else(|| cfg.pid_file.clone());
write_pid_file(&pid_file)?;
info!(pid_file = %pid_file.display(), "wrote wakey-agent pid file");
info!(config_path = %args.config.display(), agent_id = %cfg.agent_id, "starting wakey-agent"); info!(config_path = %args.config.display(), agent_id = %cfg.agent_id, "starting wakey-agent");
let mut worker = tokio::spawn(session::run(cfg.clone())); let mut worker = tokio::spawn(session::run(cfg.clone()));
@@ -51,7 +52,7 @@ pub async fn serve(args: ServeArgs) -> Result<()> {
} }
join = &mut worker => { join = &mut worker => {
warn!("agent worker task exited; shutting down daemon"); warn!("agent worker task exited; shutting down daemon");
let _ = remove_pid_file(&args.pid_file); let _ = remove_pid_file(&pid_file);
return join.context("agent session join failed")?; return join.context("agent session join failed")?;
} }
} }
@@ -66,7 +67,7 @@ pub async fn serve(args: ServeArgs) -> Result<()> {
worker.abort(); worker.abort();
} }
let _ = remove_pid_file(&args.pid_file); let _ = remove_pid_file(&pid_file);
Ok(()) Ok(())
} }