ag
This commit is contained in:
@@ -0,0 +1,60 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::{ArgAction, Args, Parser, Subcommand};
|
||||||
|
|
||||||
|
use crate::config;
|
||||||
|
|
||||||
|
pub const DEFAULT_PID_FILE: &str = "/var/run/wakey-agent.pid";
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[command(name = "wakey-agent")]
|
||||||
|
#[command(version, about = "Outbound control-plane agent for Wakey")]
|
||||||
|
pub struct Cli {
|
||||||
|
/// Increase log verbosity. Use `-v` for debug and `-vv` for trace.
|
||||||
|
#[arg(short = 'v', long = "verbose", action = ArgAction::Count, global = true)]
|
||||||
|
pub verbose: u8,
|
||||||
|
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum Command {
|
||||||
|
/// Start the agent daemon and maintain the outbound control-plane session.
|
||||||
|
Serve(ServeArgs),
|
||||||
|
/// Enroll this router with a control plane and write agent config.
|
||||||
|
Enroll(EnrollArgs),
|
||||||
|
/// Reload a running agent daemon by sending SIGHUP.
|
||||||
|
Reload(ReloadArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
pub struct ServeArgs {
|
||||||
|
/// Path to the agent config file.
|
||||||
|
#[arg(long, default_value = config::DEFAULT_CONFIG_PATH)]
|
||||||
|
pub config: PathBuf,
|
||||||
|
|
||||||
|
/// Path to pid file for reload signaling.
|
||||||
|
#[arg(long, default_value = DEFAULT_PID_FILE)]
|
||||||
|
pub pid_file: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
pub struct EnrollArgs {
|
||||||
|
/// Base HTTPS URL of the control plane.
|
||||||
|
#[arg(long)]
|
||||||
|
pub server_url: String,
|
||||||
|
/// One-time or short-lived enroll token provided by the control plane.
|
||||||
|
#[arg(long)]
|
||||||
|
pub enroll_token: String,
|
||||||
|
/// Path to the agent config file to write.
|
||||||
|
#[arg(long, default_value = config::DEFAULT_CONFIG_PATH)]
|
||||||
|
pub config: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
pub struct ReloadArgs {
|
||||||
|
/// Path to pid file for reload signaling.
|
||||||
|
#[arg(long, default_value = DEFAULT_PID_FILE)]
|
||||||
|
pub pid_file: PathBuf,
|
||||||
|
}
|
||||||
+8
-50
@@ -1,71 +1,29 @@
|
|||||||
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
mod dispatch;
|
mod dispatch;
|
||||||
mod enroll;
|
mod enroll;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod session;
|
mod session;
|
||||||
|
mod serve;
|
||||||
mod tracing;
|
mod tracing;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
use clap::{ArgAction, Args, Parser, Subcommand};
|
use cli::{Cli, Command};
|
||||||
use ::tracing::info;
|
|
||||||
|
|
||||||
#[derive(Parser)]
|
|
||||||
#[command(name = "wakey-agent")]
|
|
||||||
#[command(version, about = "Outbound control-plane agent for Wakey")]
|
|
||||||
struct Cli {
|
|
||||||
/// Increase log verbosity. Use `-v` for debug and `-vv` for trace.
|
|
||||||
#[arg(short = 'v', long = "verbose", action = ArgAction::Count, global = true)]
|
|
||||||
verbose: u8,
|
|
||||||
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Command,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand)]
|
|
||||||
enum Command {
|
|
||||||
/// Start the agent daemon and maintain the outbound control-plane session.
|
|
||||||
Serve(ServeArgs),
|
|
||||||
/// Enroll this router with a control plane and write agent config.
|
|
||||||
Enroll(EnrollArgs),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
|
||||||
struct ServeArgs {
|
|
||||||
/// Path to the agent config file.
|
|
||||||
#[arg(long, default_value = config::DEFAULT_CONFIG_PATH)]
|
|
||||||
config: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Args)]
|
|
||||||
struct EnrollArgs {
|
|
||||||
/// Base HTTPS URL of the control plane.
|
|
||||||
#[arg(long)]
|
|
||||||
server_url: String,
|
|
||||||
/// One-time or short-lived enroll token provided by the control plane.
|
|
||||||
#[arg(long)]
|
|
||||||
enroll_token: String,
|
|
||||||
/// Path to the agent config file to write.
|
|
||||||
#[arg(long, default_value = config::DEFAULT_CONFIG_PATH)]
|
|
||||||
config: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
tracing::init(cli.verbose);
|
tracing::init(cli.verbose);
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Command::Serve(args) => {
|
Command::Serve(args) => serve::serve(args).await?,
|
||||||
let config = config::load_config(&args.config)?;
|
|
||||||
info!(config_path = %args.config.display(), agent_id = %config.agent_id, "starting wakey-agent");
|
|
||||||
session::run(config).await?;
|
|
||||||
}
|
|
||||||
Command::Enroll(args) => {
|
Command::Enroll(args) => {
|
||||||
let config = enroll::enroll(&args.server_url, &args.enroll_token, &args.config).await?;
|
let config = enroll::enroll(&args.server_url, &args.enroll_token, &args.config).await?;
|
||||||
println!("agent_id={}", config.agent_id);
|
println!("agent_id={}", config.agent_id);
|
||||||
println!("config={}", args.config.display());
|
println!("config={}", args.config.display());
|
||||||
}
|
}
|
||||||
|
Command::Reload(args) => serve::reload_daemon(&args.pid_file)?,
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
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<()> {
|
||||||
|
write_pid_file(&args.pid_file)?;
|
||||||
|
|
||||||
|
let mut cfg = config::load_config(&args.config)?;
|
||||||
|
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")?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
_ = tokio::signal::ctrl_c() => {
|
||||||
|
info!("ctrl-c received; shutting down wakey-agent");
|
||||||
|
worker.abort();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ = hup.recv() => {
|
||||||
|
match config::load_config(&args.config) {
|
||||||
|
Ok(new_cfg) => {
|
||||||
|
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 => {
|
||||||
|
let _ = remove_pid_file(&args.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = remove_pid_file(&args.pid_file);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reload_daemon(pid_file: &Path) -> Result<()> {
|
||||||
|
let pid = read_pid(pid_file)?;
|
||||||
|
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<()> {
|
||||||
|
let status = std::process::Command::new("kill")
|
||||||
|
.arg("-HUP")
|
||||||
|
.arg(pid.to_string())
|
||||||
|
.status()
|
||||||
|
.context("failed to invoke kill -HUP")?;
|
||||||
|
if !status.success() {
|
||||||
|
anyhow::bail!("kill -HUP failed for pid {pid}");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user