Skip to main content

aegis_tool/
lib.rs

1mod agent;
2mod agent_credentials;
3mod api;
4mod app;
5mod apparmor;
6mod cli;
7pub mod client;
8mod command;
9mod config;
10mod egress_probe;
11mod invitation;
12mod locks;
13mod managed;
14mod metadata;
15mod principal_grants;
16mod redeploy_version;
17mod release;
18mod system_user;
19mod tunnel_operation;
20pub mod ui;
21mod wireguard_endpoint;
22
23use std::sync::Arc;
24
25use anyhow::{Result, bail};
26use clap::Parser;
27
28use crate::cli::{AgentCommands, Cli, Commands};
29
30pub fn run_cli() -> capulus::CliTermination {
31    let Cli {
32        api_base,
33        namespace,
34        ui: ui_options,
35        command,
36    } = Cli::parse();
37    if matches!(command, Commands::Agent(_)) && (api_base.is_some() || namespace.is_some()) {
38        return capulus::CliTermination::without_ui(Err(anyhow::anyhow!(
39            "agent commands use the enrolled context in their configuration; --api-base and --namespace apply to CLI operations"
40        )));
41    }
42    let ui_configuration = ui_options.options();
43    match command {
44        Commands::Agent(agent) => match agent.command {
45            AgentCommands::DirectSsh => {
46                if let Err(error) = ui::init(ui_configuration) {
47                    return capulus::CliTermination::without_ui(Err(error));
48                }
49                capulus::CliTermination::with_ui(ui::current(), app::run_direct_ssh())
50            }
51            command => capulus::CliTermination::without_ui(run_agent(command).map(|()| 0)),
52        },
53        command => {
54            if let Err(error) = ui::init(ui_configuration) {
55                return capulus::CliTermination::without_ui(Err(error));
56            }
57            capulus::CliTermination::with_ui(
58                ui::current(),
59                app::run(Cli {
60                    api_base,
61                    namespace,
62                    ui: ui_options,
63                    command,
64                }),
65            )
66        }
67    }
68}
69
70fn run_agent(command: AgentCommands) -> Result<()> {
71    match command {
72        AgentCommands::Serve(args) => {
73            require_agent_root()?;
74            let status = agent::run(&args)?;
75            if status == 0 {
76                Ok(())
77            } else {
78                bail!("aegis-agent exited with status {status}")
79            }
80        }
81        AgentCommands::DirectSsh => {
82            unreachable!("the direct SSH endpoint is dispatched with the interactive UI")
83        }
84        AgentCommands::Lifecycle(command) => {
85            let product = Arc::new(managed::product()?);
86            let health_product = Arc::clone(&product);
87            command.run(product, move || {
88                app::application_agent_info(&health_product)
89            })
90        }
91        AgentCommands::EgressProbeWorker => {
92            require_agent_root()?;
93            // The supervising agent receives stderr, so include the complete cause in
94            // the worker's display message while preserving typed interruption.
95            egress_probe::run_worker().map_err(|error| {
96                let detail = format!("{error:#}");
97                error.context(detail)
98            })
99        }
100    }
101}
102
103fn require_agent_root() -> Result<()> {
104    if rustix::process::geteuid().is_root() {
105        Ok(())
106    } else {
107        bail!("aegis agent operations must run as root")
108    }
109}