Files
wakey/wakey-agent/src/serve.rs
T

141 lines
4.8 KiB
Rust

use std::path::Path;
use anyhow::{Context, Result};
use tracing::{info, warn};
use crate::cli::ServeArgs;
use crate::{config, session};
pub async fn serve(args: ServeArgs) -> Result<()> {
if !args.config.exists() {
anyhow::bail!(
"agent config {} not found. Run `wakey-agent enroll --server-url <url> --enroll-token <token> --config {}` or `wakey-agent init-config --config {}` first",
args.config.display(),
args.config.display(),
args.config.display()
);
}
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");
let mut worker = tokio::spawn(session::run(cfg.clone()));
#[cfg(unix)]
{
use tokio::signal::unix::{SignalKind, signal};
let mut hup = signal(SignalKind::hangup()).context("failed to install SIGHUP handler")?;
let mut terminate =
signal(SignalKind::terminate()).context("failed to install SIGTERM handler")?;
loop {
tokio::select! {
_ = tokio::signal::ctrl_c() => {
info!("ctrl-c received; shutting down wakey-agent");
worker.abort();
break;
}
_ = terminate.recv() => {
info!("SIGTERM received; shutting down wakey-agent");
worker.abort();
break;
}
_ = hup.recv() => {
match config::load_config(&args.config) {
Ok(new_cfg) => {
if new_cfg.telemetry != cfg.telemetry {
warn!("telemetry configuration changed; restart the daemon to apply it");
}
cfg = new_cfg;
info!(config_path = %args.config.display(), agent_id = %cfg.agent_id, "reload requested; restarting session with updated config");
worker.abort();
worker = tokio::spawn(session::run(cfg.clone()));
}
Err(err) => {
warn!(error = %err, config_path = %args.config.display(), "reload requested but config reload failed; keeping current session");
}
}
}
join = &mut worker => {
warn!("agent worker task exited; shutting down daemon");
let _ = remove_pid_file(&pid_file);
return join.context("agent session join failed")?;
}
}
}
}
#[cfg(not(unix))]
{
tokio::signal::ctrl_c()
.await
.context("failed waiting for ctrl-c")?;
worker.abort();
}
// Await the cancelled session task before flushing its telemetry.
let _ = worker.await;
let _ = remove_pid_file(&pid_file);
Ok(())
}
pub fn reload_daemon(pid_file: &Path) -> Result<()> {
let pid = read_pid(pid_file)?;
info!(pid, pid_file = %pid_file.display(), "sending wakey-agent reload signal");
send_hup(pid)
}
fn write_pid_file(path: &Path) -> Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("failed to create pid dir {}", parent.display()))?;
}
std::fs::write(path, format!("{}\n", std::process::id()))
.with_context(|| format!("failed to write pid file {}", path.display()))
}
fn remove_pid_file(path: &Path) -> Result<()> {
match std::fs::remove_file(path) {
Ok(()) => Ok(()),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(err) => {
Err(err).with_context(|| format!("failed to remove pid file {}", path.display()))
}
}
}
fn read_pid(path: &Path) -> Result<i32> {
let raw = std::fs::read_to_string(path)
.with_context(|| format!("failed to read pid file {}", path.display()))?;
let pid = raw
.trim()
.parse::<i32>()
.with_context(|| format!("invalid pid in {}", path.display()))?;
if pid <= 0 {
anyhow::bail!("invalid non-positive pid {pid}");
}
Ok(pid)
}
fn send_hup(pid: i32) -> Result<()> {
#[cfg(unix)]
{
use nix::sys::signal::{Signal, kill};
use nix::unistd::Pid;
kill(Pid::from_raw(pid), Signal::SIGHUP)
.with_context(|| format!("failed to send SIGHUP to pid {pid}"))?;
Ok(())
}
#[cfg(not(unix))]
{
let _ = pid;
anyhow::bail!("reload is only supported on Unix (SIGHUP unavailable on this platform)")
}
}