Skip to main content

systemprompt_cli/commands/admin/setup/
mod.rs

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