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;
15mod docker;
16pub mod docker_compose;
17mod docker_database;
18pub mod postgres;
19mod profile;
20mod profile_sections;
21mod secrets;
22mod types;
23mod 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        env = "SYSTEMPROMPT_DB_USER",
73        help = "PostgreSQL user (default: systemprompt_`<env>`)"
74    )]
75    pub db_user: Option<String>,
76
77    #[arg(
78        long,
79        env = "SYSTEMPROMPT_DB_PASSWORD",
80        help = "PostgreSQL password (auto-generated if not provided)"
81    )]
82    pub db_password: Option<String>,
83
84    #[arg(
85        long,
86        env = "SYSTEMPROMPT_DB_NAME",
87        help = "PostgreSQL database name (default: systemprompt_`<env>`)"
88    )]
89    pub db_name: Option<String>,
90
91    #[arg(long, env = "GEMINI_API_KEY", help = "Google AI (Gemini) API key")]
92    pub gemini_key: Option<String>,
93
94    #[arg(long, env = "ANTHROPIC_API_KEY", help = "Anthropic (Claude) API key")]
95    pub anthropic_key: Option<String>,
96
97    #[arg(long, env = "OPENAI_API_KEY", help = "OpenAI (GPT) API key")]
98    pub openai_key: Option<String>,
99
100    #[arg(long, env = "GITHUB_TOKEN", help = "GitHub token (optional)")]
101    pub github_token: Option<String>,
102
103    #[arg(
104        long,
105        env = "SYSTEMPROMPT_DEFAULT_PROVIDER",
106        help = "Provider to make the default (gemini | anthropic | openai); must have a key. \
107                In interactive mode the selected provider is used instead."
108    )]
109    pub default_provider: Option<String>,
110
111    #[arg(long, help = "Run database migrations after setup")]
112    pub migrate: bool,
113
114    #[arg(
115        long,
116        conflicts_with = "migrate",
117        help = "Skip migrations (non-interactive default)"
118    )]
119    pub no_migrate: bool,
120
121    #[arg(long, help = "Preview setup without creating files or making changes")]
122    pub dry_run: bool,
123
124    #[arg(short = 'y', long, help = "Skip confirmation prompts")]
125    pub yes: bool,
126
127    #[arg(
128        long,
129        help = "Overwrite existing profile/secrets files (default: preserve them)"
130    )]
131    pub force: bool,
132}
133
134impl SetupArgs {
135    pub fn effective_db_user(&self, env_name: &str) -> String {
136        self.db_user
137            .clone()
138            .unwrap_or_else(|| format!("systemprompt_{}", env_name))
139    }
140
141    pub fn effective_db_name(&self, env_name: &str) -> String {
142        self.db_name
143            .clone()
144            .unwrap_or_else(|| format!("systemprompt_{}", env_name))
145    }
146
147    pub const fn has_ai_provider(&self) -> bool {
148        self.gemini_key.is_some() || self.anthropic_key.is_some() || self.openai_key.is_some()
149    }
150
151    #[must_use]
152    fn normalized(mut self) -> Self {
153        self.gemini_key = none_if_blank(self.gemini_key);
154        self.anthropic_key = none_if_blank(self.anthropic_key);
155        self.openai_key = none_if_blank(self.openai_key);
156        self.github_token = none_if_blank(self.github_token);
157        self.default_provider = none_if_blank(self.default_provider);
158        self
159    }
160}
161
162pub async fn execute(
163    args: SetupArgs,
164    ctx: &crate::context::CommandContext,
165) -> Result<CommandOutput> {
166    wizard::execute(args.normalized(), ctx.prompter(), &ctx.cli).await
167}