Skip to main content

link_assistant_router/
server_command.rs

1//! CLI output layer for `router server`.
2
3use std::io::Read as _;
4use std::process::ExitCode;
5
6use crate::cli::ServerOp;
7use crate::managed_server::{
8    PersistedServer, claim_managed, clear_persisted, configured_source, managed_status,
9    remove_managed, save_persisted, start_managed, stop_managed,
10};
11
12type AnyError = Box<dyn std::error::Error + Send + Sync>;
13
14#[must_use]
15pub fn run(op: &ServerOp) -> ExitCode {
16    let result = match op {
17        ServerOp::Use {
18            server,
19            token,
20            token_stdin,
21            clear,
22            run_max_requests,
23        } => configure(
24            server.as_deref(),
25            token.clone(),
26            *token_stdin,
27            *clear,
28            *run_max_requests,
29        ),
30        ServerOp::Status => status(),
31        ServerOp::Start => start_managed().map(|url| {
32            println!("managed router started at {url}");
33        }),
34        ServerOp::Claim => claim_managed().map(|token| {
35            eprintln!(
36                "The managed administrator is now claimed. Save this credential; future `with` runs require a token."
37            );
38            println!("{token}");
39        }),
40        ServerOp::Stop => stop_managed().map(|()| {
41            println!("managed router stopped; container and volume were preserved");
42        }),
43        ServerOp::Remove { yes } => remove_managed(*yes).map(|()| {
44            println!("removed the managed router container and credential volume");
45        }),
46        ServerOp::Reap { pid } => return crate::managed_server::reap(*pid),
47    };
48    match result {
49        Ok(()) => ExitCode::SUCCESS,
50        Err(error) => {
51            eprintln!("error: {error}");
52            ExitCode::from(1)
53        }
54    }
55}
56
57fn configure(
58    server: Option<&str>,
59    token: Option<String>,
60    token_stdin: bool,
61    clear: bool,
62    run_max_requests: Option<u64>,
63) -> Result<(), AnyError> {
64    if clear {
65        if server.is_some() || token.is_some() || token_stdin || run_max_requests.is_some() {
66            return Err("--clear cannot be combined with a server, token, or run budget".into());
67        }
68        let path = clear_persisted()?;
69        println!("cleared persisted server selection at {}", path.display());
70        return Ok(());
71    }
72    let server = server.ok_or("provide a server URL or use --clear")?;
73    let token = if token_stdin {
74        Some(read_token()?)
75    } else {
76        token
77    };
78    let path = save_persisted(&PersistedServer {
79        server: server.to_string(),
80        token,
81        run_max_requests,
82    })?;
83    println!(
84        "saved server selection in {} (token {})",
85        path.display(),
86        if crate::managed_server::load_persisted()?.is_some_and(|config| config.token.is_some()) {
87            "set"
88        } else {
89            "unset"
90        }
91    );
92    Ok(())
93}
94
95fn status() -> Result<(), AnyError> {
96    println!("effective server: {}", configured_source()?);
97    println!("managed server: {}", managed_status()?);
98    Ok(())
99}
100
101pub fn read_token() -> Result<String, AnyError> {
102    let mut input = String::new();
103    std::io::stdin().read_to_string(&mut input)?;
104    let token = input.lines().next().unwrap_or_default().trim();
105    if token.is_empty() {
106        Err("standard input did not contain a token".into())
107    } else {
108        Ok(token.to_string())
109    }
110}