Skip to main content

systemprompt_cli/commands/admin/config/server/
mod.rs

1//! `admin config server` command: show and edit profile server settings.
2//!
3//! [`ServerCommands`] reports and updates host, port, URLs, and HTTPS settings,
4//! and delegates the CORS allowed-origins list to the `cors` submodule. Changes
5//! persist to the active profile.
6//!
7//! Copyright (c) systemprompt.io — Business Source License 1.1.
8//! See <https://systemprompt.io> for licensing details.
9
10mod cors;
11
12use anyhow::{Context, Result, bail};
13use clap::{Args, Subcommand};
14use std::fs;
15use systemprompt_config::ProfileBootstrap;
16use systemprompt_logging::CliService;
17use systemprompt_models::Profile;
18
19use super::types::{ServerConfigOutput, ServerSetOutput};
20use crate::CliConfig;
21use crate::cli_settings::OutputFormat;
22use crate::shared::{CommandOutput, render_result};
23
24#[derive(Debug, Subcommand)]
25pub enum ServerCommands {
26    #[command(about = "Show server configuration")]
27    Show,
28
29    #[command(about = "Set server configuration value")]
30    Set(SetArgs),
31
32    #[command(subcommand, about = "Manage CORS allowed origins")]
33    Cors(cors::CorsCommands),
34}
35
36#[derive(Debug, Clone, Args)]
37pub struct SetArgs {
38    #[arg(long, help = "Server host address")]
39    pub host: Option<String>,
40
41    #[arg(long, help = "Server port")]
42    pub port: Option<u16>,
43
44    #[arg(long, help = "Enable/disable HTTPS")]
45    pub use_https: Option<bool>,
46
47    #[arg(long, help = "API server URL")]
48    pub api_server_url: Option<String>,
49
50    #[arg(long, help = "API internal URL")]
51    pub api_internal_url: Option<String>,
52
53    #[arg(long, help = "API external URL")]
54    pub api_external_url: Option<String>,
55}
56
57pub fn execute(command: &ServerCommands, config: &CliConfig) -> Result<()> {
58    match command {
59        ServerCommands::Show => execute_show(config),
60        ServerCommands::Set(args) => execute_set(args, config),
61        ServerCommands::Cors(cmd) => cors::execute(cmd, config),
62    }
63}
64
65pub(super) fn execute_show(config: &CliConfig) -> Result<()> {
66    let profile = ProfileBootstrap::get()?;
67
68    let output = ServerConfigOutput {
69        host: profile.server.host.clone(),
70        port: profile.server.port,
71        api_server_url: profile.server.api_server_url.clone(),
72        api_internal_url: profile.server.api_internal_url.clone(),
73        api_external_url: profile.server.api_external_url.clone(),
74        use_https: profile.server.use_https,
75        cors_allowed_origins: profile.server.cors_allowed_origins.clone(),
76    };
77
78    render_result(
79        &CommandOutput::card_value("Server Configuration", &output),
80        config,
81    );
82
83    Ok(())
84}
85
86fn change(field: &str, old: String, new: String) -> ServerSetOutput {
87    ServerSetOutput {
88        field: field.to_owned(),
89        message: format!("Updated {field} to {new}"),
90        old_value: old,
91        new_value: new,
92    }
93}
94
95pub(super) fn execute_set(args: &SetArgs, config: &CliConfig) -> Result<()> {
96    if args.host.is_none()
97        && args.port.is_none()
98        && args.use_https.is_none()
99        && args.api_server_url.is_none()
100        && args.api_internal_url.is_none()
101        && args.api_external_url.is_none()
102    {
103        bail!(
104            "Must specify at least one option: --host, --port, --use-https, --api-server-url, \
105             --api-internal-url, --api-external-url"
106        );
107    }
108
109    let profile_path = ProfileBootstrap::get_path()?;
110    let mut profile = load_profile(profile_path)?;
111
112    let mut changes: Vec<ServerSetOutput> = Vec::new();
113
114    if let Some(ref host) = args.host {
115        let old = profile.server.host.clone();
116        profile.server.host.clone_from(host);
117        changes.push(change("host", old, host.clone()));
118    }
119
120    if let Some(port) = args.port {
121        let old = profile.server.port;
122        profile.server.port = port;
123        changes.push(change("port", old.to_string(), port.to_string()));
124    }
125
126    if let Some(use_https) = args.use_https {
127        let old = profile.server.use_https;
128        profile.server.use_https = use_https;
129        changes.push(change("use_https", old.to_string(), use_https.to_string()));
130    }
131
132    if let Some(ref url) = args.api_server_url {
133        let old = profile.server.api_server_url.clone();
134        profile.server.api_server_url.clone_from(url);
135        changes.push(change("api_server_url", old, url.clone()));
136    }
137
138    if let Some(ref url) = args.api_internal_url {
139        let old = profile.server.api_internal_url.clone();
140        profile.server.api_internal_url.clone_from(url);
141        changes.push(change("api_internal_url", old, url.clone()));
142    }
143
144    if let Some(ref url) = args.api_external_url {
145        let old = profile.server.api_external_url.clone();
146        profile.server.api_external_url.clone_from(url);
147        changes.push(change("api_external_url", old, url.clone()));
148    }
149
150    save_profile(&profile, profile_path)?;
151
152    for change in &changes {
153        render_result(&CommandOutput::card_value("Server Updated", change), config);
154    }
155
156    if config.output_format() == OutputFormat::Table {
157        CliService::warning("Restart services for changes to take effect");
158    }
159
160    Ok(())
161}
162
163fn load_profile(path: &str) -> Result<Profile> {
164    let content =
165        fs::read_to_string(path).with_context(|| format!("Failed to read profile: {}", path))?;
166    let profile: Profile = serde_yaml::from_str(&content)
167        .with_context(|| format!("Failed to parse profile: {}", path))?;
168    Ok(profile)
169}
170
171pub(super) fn save_profile(profile: &Profile, path: &str) -> Result<()> {
172    let content = serde_yaml::to_string(profile).context("Failed to serialize profile")?;
173    fs::write(path, content).with_context(|| format!("Failed to write profile: {}", path))?;
174    Ok(())
175}