systemprompt_cli/commands/cloud/tenant/docker/
config.rs1use anyhow::{Context, Result};
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use std::fs;
5use std::path::PathBuf;
6use systemprompt_cloud::ProjectContext;
7use systemprompt_identifiers::TenantId;
8
9pub(in crate::commands::cloud::tenant) const SHARED_CONTAINER_NAME: &str =
10 "systemprompt-postgres-shared";
11pub(in crate::commands::cloud) const SHARED_ADMIN_USER: &str = "systemprompt_admin";
12pub(in crate::commands::cloud) const SHARED_VOLUME_NAME: &str = "systemprompt-postgres-shared-data";
13pub(in crate::commands::cloud) const SHARED_PORT: u16 = 5432;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct SharedContainerConfig {
17 pub admin_password: String,
18 pub port: u16,
19 pub created_at: DateTime<Utc>,
20 pub tenant_databases: Vec<TenantDatabaseMapping>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct TenantDatabaseMapping {
25 pub tenant_id: TenantId,
26 pub database_name: String,
27}
28
29impl SharedContainerConfig {
30 pub fn new(admin_password: String, port: u16) -> Self {
31 Self {
32 admin_password,
33 port,
34 created_at: Utc::now(),
35 tenant_databases: Vec::new(),
36 }
37 }
38
39 pub(in crate::commands::cloud::tenant) fn add_tenant(
40 &mut self,
41 tenant: TenantId,
42 database_name: String,
43 ) {
44 self.tenant_databases.push(TenantDatabaseMapping {
45 tenant_id: tenant,
46 database_name,
47 });
48 }
49
50 pub(in crate::commands::cloud::tenant) fn remove_tenant(
51 &mut self,
52 tenant: &str,
53 ) -> Option<TenantDatabaseMapping> {
54 self.tenant_databases
55 .iter()
56 .position(|t| t.tenant_id == tenant)
57 .map(|pos| self.tenant_databases.remove(pos))
58 }
59}
60
61pub(in crate::commands::cloud::tenant) fn shared_config_path() -> PathBuf {
62 let ctx = ProjectContext::discover();
63 ctx.docker_dir().join("shared_config.json")
64}
65
66pub(in crate::commands::cloud) fn load_shared_config() -> Result<Option<SharedContainerConfig>> {
67 let path = shared_config_path();
68 if !path.exists() {
69 return Ok(None);
70 }
71 let content =
72 fs::read_to_string(&path).with_context(|| format!("Failed to read {}", path.display()))?;
73 let config: SharedContainerConfig = serde_json::from_str(&content)
74 .with_context(|| format!("Failed to parse {}", path.display()))?;
75 Ok(Some(config))
76}
77
78pub(in crate::commands::cloud) fn save_shared_config(config: &SharedContainerConfig) -> Result<()> {
79 let path = shared_config_path();
80 if let Some(parent) = path.parent() {
81 fs::create_dir_all(parent)?;
82 }
83 let content = serde_json::to_string_pretty(config)?;
84 fs::write(&path, content)?;
85
86 #[cfg(unix)]
87 {
88 use std::os::unix::fs::PermissionsExt;
89 let mut perms = fs::metadata(&path)?.permissions();
90 perms.set_mode(0o600);
91 fs::set_permissions(&path, perms)?;
92 }
93
94 Ok(())
95}