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