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