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