# VAULT MIGRATION PLAN - Multi-Tenant Structure ## Hierarchy ``` tenant (cluster/deployment) ← INFRASTRUCTURE └── org (customer organization) ├── bot └── user ``` **tenant ≠ org** - **tenant** = deployment cluster (dev, staging, prod) - **org** = customer organization inside a tenant --- ## VAULT PATH STRUCTURE ``` gbo/ ├── tenants/ # PER-TENANT (cluster/deployment) │ └── {tenant_id}/ # dev, staging, prod │ ├── infrastructure/ # TENANT INFRASTRUCTURE │ │ ├── tables/ # host, port, username, password │ │ ├── drive/ # host, port, accesskey, secret │ │ ├── cache/ # host, port, password │ │ ├── email/ # smtp host, port, user, pass │ │ ├── directory/ # Zitadel url │ │ ├── llm/ # LLM endpoint │ │ └── models/ # Model server url │ │ │ └── config/ # Tenant settings │ ├── name │ ├── domain │ └── settings │ ├── orgs/ # PER-ORGANIZATION (customer) │ └── {org_id}/ │ ├── bots/ │ │ └── {bot_id}/ │ │ ├── email/ # Bot email credentials │ │ ├── whatsapp/ │ │ ├── llm/ # Bot-specific LLM override │ │ └── api-keys/ │ │ │ └── users/ │ └── {user_id}/ │ ├── email/ # User email credentials │ └── oauth/ │ └── system/ # GLOBAL FALLBACK ├── jwt/secret ├── tables/ # Fallback if tenant not set ├── drive/ ├── cache/ ├── email/ ├── llm/ ├── directory/ ├── security/ ├── alm/ ├── cloud/ └── app/ │ │ │ │ │ ├── smtp-port │ │ │ │ │ ├── smtp-user │ │ │ │ │ ├── smtp-password │ │ │ │ │ ├── imap-host │ │ │ │ │ ├── imap-port │ │ │ │ │ ├── imap-user │ │ │ │ │ └── imap-password │ │ │ │ │ │ │ │ │ ├── whatsapp/ # Bot WhatsApp │ │ │ │ │ ├── phone-number-id │ │ │ │ │ ├── business-account-id │ │ │ │ │ └── api-key │ │ │ │ │ │ │ │ │ ├── llm/ # Bot-specific LLM (override) │ │ │ │ │ ├── provider │ │ │ │ │ ├── model │ │ │ │ │ └── api-key │ │ │ │ │ │ │ │ │ └── api-keys/ │ │ │ │ ├── openai │ │ │ │ ├── anthropic │ │ │ │ └── custom/ │ │ │ │ │ │ │ └── {bot_id2}/ │ │ │ └── ... │ │ │ │ │ └── users/ │ │ ├── {user_id}/ │ │ │ ├── email/ # User email credentials │ │ │ │ ├── imap-host │ │ │ │ ├── imap-port │ │ │ │ ├── imap-user │ │ │ │ ├── imap-password │ │ │ │ ├── smtp-host │ │ │ │ ├── smtp-port │ │ │ │ ├── smtp-user │ │ │ │ └── smtp-password │ │ │ │ │ │ │ └── oauth/ │ │ │ ├── google/ │ │ │ │ ├── client-id │ │ │ │ └── client-secret │ │ │ ├── microsoft/ │ │ │ │ ├── client-id │ │ │ │ └── client-secret │ │ │ └── github/ │ │ │ ├── client-id │ │ │ └── client-secret │ │ │ │ │ └── {user_id2}/ │ │ └── ... │ │ │ └── {org_id2}/ │ └── ... │ ├── system/ # SYSTEM-WIDE (includes ALM/deployment) │ ├── jwt/ │ │ └── secret │ ├── tables/ # Database │ │ ├── host │ │ ├── port │ │ ├── database │ │ ├── username │ │ └── password │ ├── drive/ # Storage │ │ ├── accesskey │ │ └── secret │ ├── cache/ │ │ └── url │ ├── email/ # Global SMTP fallback │ │ ├── smtp-host │ │ ├── smtp-port │ │ ├── smtp-user │ │ ├── smtp-password │ │ └── smtp-from │ ├── llm/ # Global LLM defaults │ │ ├── url │ │ ├── model │ │ └── providers/ │ │ └── openai/ │ │ └── api-key │ ├── models/ # Model serving │ │ └── url │ ├── directory/ # Zitadel │ │ └── config │ ├── security/ │ │ ├── require-auth │ │ └── anonymous-paths │ ├── alm/ # ALM/Deployment (Forgejo) │ │ ├── url │ │ ├── token │ │ └── default-org │ ├── cloud/ # Cloud providers (AWS, etc) │ │ ├── access-key │ │ ├── secret-key │ │ ├── region │ │ └── s3-endpoint │ └── app/ # Application config │ ├── url │ ├── environment │ └── disable-tls ``` --- ## ENV VARS → VAULT MAPPING | Current ENV | Vault Path | Scope | |------------|------------|-------| | `JWT_SECRET` | `gbo/system/jwt/secret` | system | | `LLM_KEY` | `gbo/system/llm/providers/openai/api-key` | system | | `OPENAI_API_KEY` | `gbo/system/llm/providers/openai/api-key` | system | | `SMTP_HOST` | `gbo/system/email/smtp-host` | system | | `SMTP_USER` | `gbo/system/email/smtp-user` | system | | `SMTP_PASS` | `gbo/system/email/smtp-password` | system | | `SMTP_FROM` | `gbo/system/email/smtp-from` | system | | `FORGEJO_URL` | `gbo/system/alm/url` | system | | `FORGEJO_TOKEN` | `gbo/system/alm/token` | system | | `FORGEJO_DEFAULT_ORG` | `gbo/system/alm/default-org` | system | | `AWS_ACCESS_KEY_ID` | `gbo/system/cloud/access-key` | system | | `AWS_SECRET_ACCESS_KEY` | `gbo/system/cloud/secret-key` | system | | `AWS_REGION` | `gbo/system/cloud/region` | system | | `S3_ENDPOINT` | `gbo/system/cloud/s3-endpoint` | system | | `ZITADEL_ISSUER_URL` | `gbo/system/directory/zitadel-issuer` | system | | `ZITADEL_CLIENT_ID` | `gbo/system/directory/zitadel-client-id` | system | | `REQUIRE_AUTH` | `gbo/system/security/require-auth` | system | | `ANONYMOUS_PATHS` | `gbo/system/security/anonymous-paths` | system | | `APP_URL` | `gbo/system/app/url` | system | | `BOTSERVER_ENV` | `gbo/system/app/environment` | system | | `LLM_URL` | `gbo/system/llm/url` | system | | `LLM_MODEL` | `gbo/system/llm/model` | system | | `BOTMODELS_URL` | `gbo/system/models/url` | system | --- ## CODE PATTERNS ### Get Bot Email (tenant → bot) ```rust async fn get_bot_email(state: &AppState, org_id: Uuid, bot_id: Uuid) -> Result { // Try per-bot Vault path first let path = format!("gbo/tenants/{}/bots/{}/email", org_id, bot_id); if let Ok(creds) = state.secrets.get_secret(&path).await { return Ok(BotEmail::from_vault(creds)); } // Fallback: system SMTP let system = state.secrets.get_secret("gbo/system/email").await?; Ok(BotEmail::from_system(system)) } ``` ### Get User Email (tenant → user) ```rust async fn get_user_email(state: &AppState, org_id: Uuid, user_id: Uuid) -> Result { let path = format!("gbo/tenants/{}/users/{}/email", org_id, user_id); state.secrets.get_secret(&path).await } ``` ### Get Bot LLM (tenant → bot, with system fallback) ```rust async fn get_bot_llm(state: &AppState, org_id: Uuid, bot_id: Uuid) -> Result { // Bot-specific override let bot_path = format!("gbo/tenants/{}/bots/{}/llm", org_id, bot_id); if let Ok(config) = state.secrets.get_secret(&bot_path).await { return Ok(LlmConfig::from_vault(config)); } // System default state.secrets.get_secret("gbo/system/llm").await } ```