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_manifest::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(long, default_value = "localhost", help = "PostgreSQL host")]
57 pub db_host: String,
58
59 #[arg(long, default_value = "5432", help = "PostgreSQL port")]
60 pub db_port: u16,
61
62 #[arg(
63 long,
64 default_value = "0",
65 help = "Shift every locally-bound MCP and agent port by this amount"
66 )]
67 pub port_offset: u16,
68
69 #[arg(long, help = "PostgreSQL user (default: systemprompt_`<env>`)")]
70 pub db_user: Option<String>,
71
72 #[arg(long, help = "PostgreSQL password (auto-generated if not provided)")]
73 pub db_password: Option<String>,
74
75 #[arg(
76 long,
77 help = "PostgreSQL database name (default: systemprompt_`<env>`)"
78 )]
79 pub db_name: Option<String>,
80
81 #[arg(
82 long,
83 env = "GEMINI_API_KEY",
84 hide_env_values = true,
85 help = "Google AI (Gemini) API key"
86 )]
87 pub gemini_key: Option<String>,
88
89 #[arg(
90 long,
91 env = "ANTHROPIC_API_KEY",
92 hide_env_values = true,
93 help = "Anthropic (Claude) API key"
94 )]
95 pub anthropic_key: Option<String>,
96
97 #[arg(
98 long,
99 env = "OPENAI_API_KEY",
100 hide_env_values = true,
101 help = "OpenAI (GPT) API key"
102 )]
103 pub openai_key: Option<String>,
104
105 #[arg(
106 long,
107 env = "GITHUB_TOKEN",
108 hide_env_values = true,
109 help = "GitHub token (optional)"
110 )]
111 pub github_token: Option<String>,
112
113 #[arg(
114 long,
115 help = "Provider to make the default (gemini | anthropic | openai); must have a key. \
116 In interactive mode the selected provider is used instead."
117 )]
118 pub default_provider: Option<String>,
119
120 #[arg(
121 long,
122 env = "SYSTEMPROMPT_ADMIN_EMAIL",
123 hide_env_values = true,
124 help = "Email identifying the platform admin on sign-in and consent screens; required non-interactively"
125 )]
126 pub admin_email: Option<String>,
127
128 #[arg(long, help = "Run database migrations after setup")]
129 pub migrate: bool,
130
131 #[arg(
132 long,
133 conflicts_with = "migrate",
134 help = "Skip migrations (non-interactive default)"
135 )]
136 pub no_migrate: bool,
137
138 #[arg(long, help = "Preview setup without creating files or making changes")]
139 pub dry_run: bool,
140
141 #[arg(short = 'y', long, help = "Skip confirmation prompts")]
142 pub yes: bool,
143
144 #[arg(
145 long,
146 help = "Overwrite existing profile/secrets files (default: preserve them)"
147 )]
148 pub force: bool,
149}
150
151impl SetupArgs {
152 pub fn effective_db_user(&self, env_name: &str) -> String {
153 self.db_user
154 .clone()
155 .unwrap_or_else(|| format!("systemprompt_{}", env_name))
156 }
157
158 pub fn effective_db_name(&self, env_name: &str) -> String {
159 self.db_name
160 .clone()
161 .unwrap_or_else(|| format!("systemprompt_{}", env_name))
162 }
163
164 pub const fn has_ai_provider(&self) -> bool {
165 self.gemini_key.is_some() || self.anthropic_key.is_some() || self.openai_key.is_some()
166 }
167
168 #[must_use]
169 fn normalized(mut self) -> Self {
170 self.gemini_key = none_if_blank(self.gemini_key);
171 self.anthropic_key = none_if_blank(self.anthropic_key);
172 self.openai_key = none_if_blank(self.openai_key);
173 self.github_token = none_if_blank(self.github_token);
174 self.default_provider = none_if_blank(self.default_provider);
175 self.admin_email = none_if_blank(self.admin_email);
176 self
177 }
178}
179
180pub async fn execute(
181 args: SetupArgs,
182 ctx: &crate::context::CommandContext,
183) -> Result<CommandOutput> {
184 wizard::execute(args.normalized(), ctx.prompter(), &ctx.cli).await
185}