1use aegis_dto::{HostId, protocol::AegisNetworkConfig};
2use anyhow::Result;
3use capulus::shell::shell_quote as sh_quote;
4
5use crate::cli::AgentMode;
6
7use super::{
8 REMOTE_HOST_CERT_PATH, REMOTE_HOST_KEY_PATH, install, mesh_bootstrap, system, wireguard,
9};
10
11pub(super) struct RemotePrepareHostScript;
12
13impl RemotePrepareHostScript {
14 pub(super) fn render(publish_ssh: bool) -> String {
15 format!(
16 "{}\n{}\n{}\nsudo \"$system_aegis\" agent prepare-identity {}\n",
17 system::prerequisites_script(true),
18 system_program_bootstrap_script(true),
19 crate::platform::BINARY_SHELL_ASSIGNMENT,
20 if publish_ssh { "--inbound-ssh" } else { "" }
21 )
22 }
23}
24
25pub(super) struct RemoteFinalizeInstall<'a> {
26 api_base: &'a str,
27 pending_host: &'a crate::config::CachedHost,
28 hub_peers: &'a [wireguard::HubPeer],
29 network: &'a AegisNetworkConfig,
30 server_certificate: Option<&'a str>,
31 agent_token: &'a str,
32 login_principal: &'a str,
33 initial_user_id: Option<&'a str>,
34 mode: AgentMode,
35 inbound_ssh: bool,
36}
37
38pub(super) struct RemoteFinalizeInstallParts<'a> {
39 pub(super) api_base: &'a str,
40 pub(super) pending_host: &'a crate::config::CachedHost,
41 pub(super) hub_peers: &'a [wireguard::HubPeer],
42 pub(super) network: &'a AegisNetworkConfig,
43 pub(super) server_certificate: Option<&'a str>,
44 pub(super) agent_token: &'a str,
45 pub(super) login_principal: &'a str,
46 pub(super) initial_user_id: Option<&'a str>,
47 pub(super) mode: AgentMode,
48 pub(super) inbound_ssh: bool,
49}
50
51impl<'a> RemoteFinalizeInstall<'a> {
52 pub(super) fn new(parts: RemoteFinalizeInstallParts<'a>) -> Self {
53 Self {
54 api_base: parts.api_base,
55 pending_host: parts.pending_host,
56 hub_peers: parts.hub_peers,
57 network: parts.network,
58 server_certificate: parts.server_certificate,
59 agent_token: parts.agent_token,
60 login_principal: parts.login_principal,
61 initial_user_id: parts.initial_user_id,
62 mode: parts.mode,
63 inbound_ssh: parts.inbound_ssh,
64 }
65 }
66
67 pub(super) fn render(&self) -> Result<String> {
68 let mut install_args = format!("--host-id {}", self.pending_host.host_id);
69 if self.server_certificate.is_some() {
70 install_args.push_str(&format!(
71 " --key {} --cert {}",
72 sh_quote(REMOTE_HOST_KEY_PATH),
73 sh_quote(REMOTE_HOST_CERT_PATH),
74 ));
75 }
76 install_args.push_str(&format!(
77 " --user {} --inbound-ssh {} --staged-enrollment",
78 sh_quote(self.login_principal),
79 if self.inbound_ssh { "yes" } else { "no" }
80 ));
81 if let Some(principal) = self.initial_user_id {
82 install_args.push_str(&format!(" --initial-user-id {}", sh_quote(principal)));
83 }
84 let agent_refresh_token_env =
85 install::agent_refresh_token_env_assignment(self.agent_token)?;
86 let wireguard_setup = if self.hub_peers.is_empty()
87 || self.pending_host.platform.operating_system
88 == aegis_dto::platform::OperatingSystem::MacOs
89 {
90 String::new()
91 } else {
92 mesh_bootstrap::BootstrapMeshScript::new(
93 self.pending_host,
94 self.hub_peers,
95 self.network,
96 self.mode,
97 )
98 .render()?
99 };
100 let host_certificate_bootstrap = self
101 .server_certificate
102 .map(|server_certificate| {
103 format!(
104 "cat <<'EOF_AEGIS_SERVER_CERT' | sudo tee {REMOTE_HOST_CERT_PATH} >/dev/null\n\
105{server_certificate}\n\
106EOF_AEGIS_SERVER_CERT\n\
107sudo chmod 644 {REMOTE_HOST_CERT_PATH}\n"
108 )
109 })
110 .unwrap_or_default();
111 Ok(format!(
112 "set -euo pipefail\n\
113 sudo -v\n\
114 login_user={login_user}\n\
115 {wireguard_setup}\n\
116 {tool_bootstrap}\
117 {host_certificate_bootstrap}\
118 sudo env {agent_refresh_token_env} {system_binary} --api-base {api_base} advanced install {install_args}\n",
119 api_base = sh_quote(self.api_base),
120 agent_refresh_token_env = agent_refresh_token_env,
121 host_certificate_bootstrap = host_certificate_bootstrap,
122 install_args = install_args,
123 tool_bootstrap = system_program_bootstrap_script(true),
124 login_user = sh_quote(self.login_principal),
125 system_binary = crate::platform::system_binary_path(self.pending_host.platform),
126 wireguard_setup = wireguard_setup,
127 ))
128 }
129}
130
131pub fn system_program_bootstrap_script(use_sudo: bool) -> String {
132 format!(
133 "{sudo}bash -seuo pipefail <<'EOF_AEGIS_BOOTSTRAP'\naegis_bootstrap_version={version}\n{script}\nEOF_AEGIS_BOOTSTRAP\n",
134 sudo = if use_sudo { "sudo " } else { "" },
135 version = sh_quote(env!("CARGO_PKG_VERSION")),
136 script = include_str!("../../assets/bootstrap.sh"),
137 )
138}
139
140pub fn system_agent_activation_script() -> String {
141 format!(
142 "case $(uname -s) in\nDarwin)\n if ! sudo launchctl print system/aegis-agent >/dev/null 2>&1; then\n sudo launchctl bootstrap system /Library/LaunchDaemons/aegis-agent.plist\n fi\n sudo launchctl print system/aegis-agent >/dev/null\n ;;\nLinux)\n sudo systemctl enable --now {application_socket} {management_socket}\n sudo systemctl enable --now {service}\n sudo systemctl is-active --quiet {application_socket} {management_socket} {service}\n ;;\n*) exit 1 ;;\nesac\n",
143 application_socket = sh_quote(crate::managed::APPLICATION_SOCKET_NAME),
144 management_socket = sh_quote(crate::managed::MANAGEMENT_SOCKET_NAME),
145 service = sh_quote(super::AEGIS_AGENT_SERVICE_NAME)
146 )
147}
148
149pub(super) struct LocalTargetInstall<'a> {
150 api_base: &'a str,
151 host_id: HostId,
152 inbound_ssh: bool,
153 install_host_certificate: bool,
154 agent_token: &'a str,
155 initial_user_id: Option<&'a str>,
156 system_bootstrap: String,
157}
158
159pub(super) struct LocalTargetInstallOptions<'a> {
160 pub api_base: &'a str,
161 pub host_id: HostId,
162 pub inbound_ssh: bool,
163 pub install_host_certificate: bool,
164 pub agent_token: &'a str,
165 pub initial_user_id: Option<&'a str>,
166}
167
168impl<'a> LocalTargetInstall<'a> {
169 pub(super) fn new(options: LocalTargetInstallOptions<'a>) -> Self {
170 Self {
171 api_base: options.api_base,
172 host_id: options.host_id,
173 inbound_ssh: options.inbound_ssh,
174 install_host_certificate: options.install_host_certificate,
175 agent_token: options.agent_token,
176 initial_user_id: options.initial_user_id,
177 system_bootstrap: system_program_bootstrap_script(false),
178 }
179 }
180
181 pub(super) fn run(&self) -> Result<()> {
182 system::LocalRoot::run_script(&self.system_bootstrap)?;
183 let mut install_args = vec![
184 "advanced".to_string(),
185 "install".to_string(),
186 "--staged-enrollment".to_string(),
187 "--host-id".to_string(),
188 self.host_id.to_string(),
189 ];
190 if self.install_host_certificate {
191 install_args.push("--key".to_string());
192 install_args.push(REMOTE_HOST_KEY_PATH.to_string());
193 install_args.push("--cert".to_string());
194 install_args.push(REMOTE_HOST_CERT_PATH.to_string());
195 }
196 install_args.push("--inbound-ssh".to_string());
197 install_args.push(if self.inbound_ssh {
198 "yes".to_string()
199 } else {
200 "no".to_string()
201 });
202 if let Some(principal) = self.initial_user_id {
203 install_args.push("--initial-user-id".to_string());
204 install_args.push(principal.to_string());
205 }
206 system::LocalRoot::run_aegis_command(self.api_base, &install_args, self.agent_token)
207 }
208}