mail4agent_messenger_shell/cli/
launch.rs1use std::path::PathBuf;
10use std::process::Command;
11
12use crate::session_account::{provision_session, SESSION_CAP_ENV};
13use crate::{ProviderKind, STORE_ROOT_ENV};
14
15fn fail(msg: &str) -> ! {
16 eprintln!("m4a-agent launch: {msg}");
17 std::process::exit(64);
18}
19
20pub fn run(args: Vec<String>) {
21 let (mut provider, mut session, mut nick) = (None, None, None);
22 let mut it = args.into_iter();
23 let mut cmd: Vec<String> = Vec::new();
24 while let Some(a) = it.next() {
25 match a.as_str() {
26 "--provider" => provider = it.next(),
27 "--session" => session = it.next(),
28 "--nick" => nick = it.next(),
29 "--" => {
30 cmd.extend(it.by_ref());
31 break;
32 }
33 other => fail(&format!("unknown argument {other}")),
34 }
35 }
36 let provider = provider.as_deref().and_then(ProviderKind::parse).unwrap_or_else(|| fail("--provider is required"));
37 if cmd.is_empty() {
38 fail("usage: m4a-agent launch --provider P [--session ID] [--nick NICK] -- <command...>");
39 }
40 let base = std::env::var(crate::ACCOUNTS_ROOT_ENV)
41 .ok()
42 .filter(|v| !v.is_empty())
43 .or_else(|| std::env::var(STORE_ROOT_ENV).ok().filter(|v| !v.is_empty()))
44 .map(PathBuf::from)
45 .unwrap_or_else(|| fail("M4A_STORE_ROOT (or M4A_ACCOUNTS_ROOT) is required"));
46 let session = session.unwrap_or_else(fresh_session_id);
47 let made = provision_session(&base, provider, &session, nick.as_deref(), true).unwrap_or_else(|e| fail(&e.to_string()));
49 #[cfg(unix)]
50 if made.entry.os_uid.is_none() {
51 if let Ok(mut reg) = crate::session_account::AccountRegistry::open(&base) {
52 if let Ok(id) = crate::session_account::SessionIdentity::new(provider, &session) {
53 let _ = reg.set_os_uid(&id, crate::ipc_own_uid());
54 }
55 }
56 }
57 if made.entry.state != crate::session_account::ServerState::Enrolled {
60 if let Some(invite) = std::env::var(crate::PRODUCT_INVITE_ENV).ok().filter(|v| !v.is_empty()) {
61 let me = std::env::current_exe().unwrap_or_else(|_| PathBuf::from("m4a-agent"));
62 let mut enroll = Command::new(me);
63 enroll.arg("enroll").env(crate::PRODUCT_INVITE_ENV, invite).env_remove(SESSION_CAP_ENV);
64 for (k, v) in made.session_env().into_iter().filter(|(k, _)| *k != SESSION_CAP_ENV) {
65 enroll.env(k, v);
66 }
67 match enroll.status() {
68 Ok(s) if s.success() => {}
69 _ => eprintln!("m4a-agent launch: enroll failed; the session starts local-only"),
70 }
71 }
72 }
73 eprintln!("m4a-agent launch: session account {} ({})", made.entry.nick, provider.id());
74 let mut child = Command::new(&cmd[0]);
75 child.args(&cmd[1..]).env_remove(SESSION_CAP_ENV).env_remove(crate::PRODUCT_INVITE_ENV);
76 child.env("M4A_PROVIDER", provider.id()).env("M4A_PROVIDER_SESSION_ID", &session);
77 for (k, v) in made.session_env() {
78 child.env(k, v);
79 }
80 match child.status() {
81 Ok(status) => std::process::exit(status.code().unwrap_or(1)),
82 Err(err) => fail(&format!("could not start {}: {}", cmd[0], err.kind())),
83 }
84}
85
86fn fresh_session_id() -> String {
87 let raw = m4a_agent::vault::random_bytes(16);
88 let h: String = raw.iter().map(|b| format!("{b:02x}")).collect();
89 format!("launch-{h}")
90}