# ── Cloud SQL — PostgreSQL 15 ───────────────────────────────────────────────── # Single instance in us-central1 for the accounts service. # Cloud Run services connect via the built-in Cloud SQL Auth Proxy # (no direct IP exposure, encrypted, IAM-authenticated). # # Sizing: db-g1-small (1 shared vCPU, 1.7 GB) for launch. Scale up to # db-n1-standard-2 once traffic warrants it — zero-downtime restart. resource "google_sql_database_instance" "main" { name = "neuron-prod-pg15" database_version = "POSTGRES_15" region = "us-central1" project = var.project_id # Prevent accidental deletion via terraform destroy deletion_protection = true settings { tier = "db-g1-small" availability_type = "ZONAL" # Upgrade to REGIONAL (HA) once > 1k users backup_configuration { enabled = true point_in_time_recovery_enabled = true start_time = "03:00" # 3am UTC — before backup window on Legion transaction_log_retention_days = 7 backup_retention_settings { retained_backups = 14 } } maintenance_window { day = 7 # Sunday hour = 4 # 4am UTC update_track = "stable" } ip_configuration { # No public IP — Cloud Run uses the Auth Proxy via private service connect # Flip ipv4_enabled=true + authorized_networks if you ever need direct access # for migrations/seeding from a bastion. ipv4_enabled = true # Required for Cloud Run Auth Proxy (until VPC SC configured) ssl_mode = "ENCRYPTED_ONLY" } database_flags { name = "max_connections" value = "100" # Cloud Run can burst many instances; cap connections early } database_flags { name = "log_min_duration_statement" value = "500" # Log queries taking > 500ms } insights_config { query_insights_enabled = true query_string_length = 1024 record_application_tags = true record_client_address = false } } } # ── Databases ───────────────────────────────────────────────────────────────── resource "google_sql_database" "accounts" { name = "accounts" instance = google_sql_database_instance.main.name project = var.project_id } # ── Users ───────────────────────────────────────────────────────────────────── # Passwords are stored in Secret Manager. The accounts service reads them # at runtime via the ACCOUNTS_DATABASE_URL secret. # Cloud IAM DB users are the preferred path long-term (no password rotation); # using password auth here for compatibility with pgx/stdlib. resource "google_sql_user" "accounts" { name = "accounts" instance = google_sql_database_instance.main.name project = var.project_id password = random_password.accounts_db.result } resource "random_password" "accounts_db" { length = 32 special = true } # ── Secret Manager — Database URL ───────────────────────────────────────────── # Stored as a full DSN so the app just reads one env var. # Uses /cloudsql/ Unix socket path — Cloud Run Auth Proxy mounts it there. resource "google_secret_manager_secret" "accounts_database_url" { secret_id = "accounts-database-url" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "accounts_database_url" { secret = google_secret_manager_secret.accounts_database_url.id secret_data = "host=/cloudsql/${google_sql_database_instance.main.connection_name} user=accounts password=${random_password.accounts_db.result} dbname=accounts sslmode=disable" } # ── Secret Manager — JWT signing key ───────────────────────────────────────── # Shared between accounts (issues tokens) and neuron-rest (validates tokens). resource "random_password" "jwt_secret" { length = 64 special = false # URL-safe; used as HMAC key } resource "google_secret_manager_secret" "jwt_secret" { secret_id = "jwt-signing-key" project = var.project_id replication { auto {} } } resource "google_secret_manager_secret_version" "jwt_secret" { secret = google_secret_manager_secret.jwt_secret.id secret_data = random_password.jwt_secret.result } # ── Secret Manager — License admin token ───────────────────────────────────── # Used by neuron-rest to authorize license validation calls. # Currently held in Vault under neuron-technologies/marketing.license_admin_token. # Create this secret manually from Vault: # gcloud secrets create license-admin-token --data-file=<(vault kv get \ # -field=license_admin_token secret/neuron-technologies/marketing) resource "google_secret_manager_secret" "license_admin_token" { secret_id = "license-admin-token" project = var.project_id replication { auto {} } # Version populated manually or via bootstrap script — not managed by Terraform # to avoid exposing the value in state. lifecycle { ignore_changes = [id] } }