systemprompt_cli/commands/admin/setup/
mod.rs1pub mod ai_config;
12pub mod catalog;
13pub mod common;
14pub mod ddl;
15pub mod docker;
16pub mod docker_compose;
17mod docker_database;
18pub mod postgres;
19mod profile;
20mod profile_sections;
21pub mod secrets;
22pub mod services_files;
23mod types;
24pub mod wizard;
25mod wizard_dry_run;
26#[doc(hidden)]
27pub mod wizard_prompts;
28
29use crate::shared::CommandOutput;
30use anyhow::Result;
31use clap::Args;
32use systemprompt_models::none_if_blank;
33
34pub use secrets::SecretsData;
35pub use types::*;
36
37#[derive(Debug, Args)]
38#[expect(
39 clippy::struct_excessive_bools,
40 reason = "each bool is an independent clap CLI flag, not a state machine"
41)]
42pub struct SetupArgs {
43 #[arg(
44 short,
45 long,
46 help = "Target environment name (e.g., dev, staging, prod)"
47 )]
48 pub environment: Option<String>,
49
50 #[arg(
51 long,
52 help = "Use Docker for PostgreSQL (default: use existing installation)"
53 )]
54 pub docker: bool,
55
56 #[arg(
57 long,
58 env = "SYSTEMPROMPT_DB_HOST",
59 default_value = "localhost",
60 help = "PostgreSQL host"
61 )]
62 pub db_host: String,
63
64 #[arg(
65 long,
66 env = "SYSTEMPROMPT_DB_PORT",
67 default_value = "5432",
68 help = "PostgreSQL port"
69 )]
70 pub db_port: u16,
71
72 #[arg(
73 long,
74 default_value = "0",
75 help = "Shift every locally-bound MCP and agent port by this amount"
76 )]
77 pub port_offset: u16,
78
79 #[arg(
80 long,
81 env = "SYSTEMPROMPT_DB_USER",
82 hide_env_values = true,
83 help = "PostgreSQL user (default: systemprompt_`<env>`)"
84 )]
85 pub db_user: Option<String>,
86
87 #[arg(
88 long,
89 env = "SYSTEMPROMPT_DB_PASSWORD",
90 hide_env_values = true,
91 help = "PostgreSQL password (auto-generated if not provided)"
92 )]
93 pub db_password: Option<String>,
94
95 #[arg(
96 long,
97 env = "SYSTEMPROMPT_DB_NAME",
98 help = "PostgreSQL database name (default: systemprompt_`<env>`)"
99 )]
100 pub db_name: Option<String>,
101
102 #[arg(
103 long,
104 env = "GEMINI_API_KEY",
105 hide_env_values = true,
106 help = "Google AI (Gemini) API key"
107 )]
108 pub gemini_key: Option<String>,
109
110 #[arg(
111 long,
112 env = "ANTHROPIC_API_KEY",
113 hide_env_values = true,
114 help = "Anthropic (Claude) API key"
115 )]
116 pub anthropic_key: Option<String>,
117
118 #[arg(
119 long,
120 env = "OPENAI_API_KEY",
121 hide_env_values = true,
122 help = "OpenAI (GPT) API key"
123 )]
124 pub openai_key: Option<String>,
125
126 #[arg(
127 long,
128 env = "GITHUB_TOKEN",
129 hide_env_values = true,
130 help = "GitHub token (optional)"
131 )]
132 pub github_token: Option<String>,
133
134 #[arg(
135 long,
136 env = "SYSTEMPROMPT_DEFAULT_PROVIDER",
137 help = "Provider to make the default (gemini | anthropic | openai); must have a key. \
138 In interactive mode the selected provider is used instead."
139 )]
140 pub default_provider: Option<String>,
141
142 #[arg(
143 long,
144 env = "SYSTEMPROMPT_ADMIN_EMAIL",
145 hide_env_values = true,
146 help = "Email identifying the platform admin on sign-in and consent screens; required non-interactively"
147 )]
148 pub admin_email: Option<String>,
149
150 #[arg(long, help = "Run database migrations after setup")]
151 pub migrate: bool,
152
153 #[arg(
154 long,
155 conflicts_with = "migrate",
156 help = "Skip migrations (non-interactive default)"
157 )]
158 pub no_migrate: bool,
159
160 #[arg(long, help = "Preview setup without creating files or making changes")]
161 pub dry_run: bool,
162
163 #[arg(short = 'y', long, help = "Skip confirmation prompts")]
164 pub yes: bool,
165
166 #[arg(
167 long,
168 help = "Overwrite existing profile/secrets files (default: preserve them)"
169 )]
170 pub force: bool,
171}
172
173impl SetupArgs {
174 pub fn effective_db_user(&self, env_name: &str) -> String {
175 self.db_user
176 .clone()
177 .unwrap_or_else(|| format!("systemprompt_{}", env_name))
178 }
179
180 pub fn effective_db_name(&self, env_name: &str) -> String {
181 self.db_name
182 .clone()
183 .unwrap_or_else(|| format!("systemprompt_{}", env_name))
184 }
185
186 pub const fn has_ai_provider(&self) -> bool {
187 self.gemini_key.is_some() || self.anthropic_key.is_some() || self.openai_key.is_some()
188 }
189
190 #[must_use]
191 fn normalized(mut self) -> Self {
192 self.gemini_key = none_if_blank(self.gemini_key);
193 self.anthropic_key = none_if_blank(self.anthropic_key);
194 self.openai_key = none_if_blank(self.openai_key);
195 self.github_token = none_if_blank(self.github_token);
196 self.default_provider = none_if_blank(self.default_provider);
197 self.admin_email = none_if_blank(self.admin_email);
198 self
199 }
200}
201
202pub async fn execute(
203 args: SetupArgs,
204 ctx: &crate::context::CommandContext,
205) -> Result<CommandOutput> {
206 wizard::execute(args.normalized(), ctx.prompter(), &ctx.cli).await
207}