systemprompt_cli/commands/cloud/profile/
create_setup.rs1use 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}