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 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}