Skip to main content

systemprompt_cli/commands/cloud/profile/
create_setup.rs

1//! Post-create profile setup, including the Postgres compose container.
2//!
3//! Copyright (c) systemprompt.io — Business Source License 1.1.
4//! See <https://systemprompt.io> for licensing details.
5
6use anyhow::Result;
7use std::path::Path;
8use systemprompt_cloud::{DockerCli, ProjectContext};
9use systemprompt_logging::CliService;
10
11use super::templates::{run_migrations_cmd, validate_connection};
12use crate::cloud::tenant::wait_for_postgres_healthy;
13use crate::interactive::Prompter;
14
15pub async fn handle_local_tenant_setup(
16    prompter: &dyn Prompter,
17    cloud_user: &crate::cloud::sync::admin_user::CloudUser,
18    db_url: &str,
19    tenant_name: &str,
20    profile_path: &Path,
21) -> Result<()> {
22    let spinner = CliService::spinner("Validating PostgreSQL connection...");
23    let mut connection_valid = validate_connection(db_url).await;
24    spinner.finish_and_clear();
25
26    if !connection_valid {
27        let ctx = ProjectContext::discover();
28        let compose_path = ctx.docker_dir().join(format!("{}.yaml", tenant_name));
29
30        if compose_path.exists() {
31            let start_docker =
32                prompter.confirm("PostgreSQL not running. Start Docker container?", true)?;
33
34            if start_docker {
35                connection_valid = start_postgres_container(&compose_path).await?;
36            }
37        } else {
38            CliService::warning("Could not connect to PostgreSQL.");
39            CliService::info("Ensure PostgreSQL is running before starting services.");
40        }
41    }
42
43    if connection_valid {
44        CliService::success("PostgreSQL connection verified");
45
46        let run_migrations = prompter.confirm("Run database migrations?", true)?;
47
48        let migrations_succeeded = if run_migrations {
49            match run_migrations_cmd(profile_path) {
50                Ok(()) => true,
51                Err(e) => {
52                    CliService::warning(&format!("Migration failed: {}", e));
53                    false
54                },
55            }
56        } else {
57            false
58        };
59
60        if migrations_succeeded {
61            let result = crate::cloud::sync::admin_user::sync_admin_to_database(
62                cloud_user,
63                db_url,
64                tenant_name,
65            )
66            .await;
67
68            match &result {
69                crate::cloud::sync::admin_user::SyncResult::Created { email, .. } => {
70                    CliService::success(&format!("Created admin user: {}", email));
71                },
72                crate::cloud::sync::admin_user::SyncResult::Promoted { email, .. } => {
73                    CliService::success(&format!("Promoted user to admin: {}", email));
74                },
75                crate::cloud::sync::admin_user::SyncResult::AlreadyAdmin { email, .. } => {
76                    CliService::info(&format!("User '{}' is already admin", email));
77                },
78                crate::cloud::sync::admin_user::SyncResult::ConnectionFailed { error, .. } => {
79                    CliService::warning(&format!("Could not sync admin user: {}", error));
80                },
81                crate::cloud::sync::admin_user::SyncResult::Failed { error, .. } => {
82                    CliService::warning(&format!("Admin user sync failed: {}", error));
83                },
84            }
85        }
86    }
87
88    Ok(())
89}
90
91pub fn get_cloud_user() -> Result<crate::cloud::sync::admin_user::CloudUser> {
92    crate::cloud::sync::admin_user::CloudUser::from_credentials()?.ok_or_else(|| {
93        anyhow::anyhow!("Cloud credentials required. Run 'systemprompt cloud login' first.")
94    })
95}
96
97async fn start_postgres_container(compose_path: &Path) -> Result<bool> {
98    CliService::info("Starting PostgreSQL container...");
99
100    let compose_path_str = compose_path
101        .to_str()
102        .ok_or_else(|| anyhow::anyhow!("Invalid compose path"))?;
103
104    let docker = DockerCli::new();
105    let status = docker
106        .status(&["compose", "-f", compose_path_str, "up", "-d"])
107        .map_err(|_e| anyhow::anyhow!("Failed to execute docker compose. Is Docker running?"))?;
108
109    if !status.success() {
110        CliService::warning("Failed to start PostgreSQL container. Is Docker running?");
111        return Ok(false);
112    }
113
114    let spinner = CliService::spinner("Waiting for PostgreSQL to be ready...");
115    match wait_for_postgres_healthy(&docker, compose_path, 60).await {
116        Ok(()) => {
117            spinner.finish_and_clear();
118            Ok(true)
119        },
120        Err(e) => {
121            spinner.finish_and_clear();
122            CliService::warning(&format!("PostgreSQL failed to become healthy: {}", e));
123            Ok(false)
124        },
125    }
126}