systemprompt_cli/commands/admin/config/server/
mod.rs1mod 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}