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