Files
Will Anderson d4c65d5857 Expand GCP infra: accounts + API services, Cloud SQL, Artifact Registry
Architecture: intelligence stays on Legion; only compiled artifacts cross
to GCP. Source code and Neuron's knowledge base never leave the system.

Artifact Registry:
- neuron-marketing, neuron-accounts, neuron-api repos in us-central1
- Keep-last-10 cleanup policy; ci-pusher SA with writer access
- Legion CI runners authenticate via GCP_SA_KEY Gitea secret

Cloud SQL (cloud-sql.tf):
- postgres-15 on db-g1-small, us-central1 (scale up to REGIONAL HA at 1k users)
- Point-in-time recovery, 14-day backup retention
- Accounts DB + user; password generated and stored in Secret Manager
- JWT signing key in Secret Manager (shared by accounts + api)
- Cloud Run connects via built-in Auth Proxy (Unix socket volume mount)

Accounts Cloud Run (cloud-run-accounts.tf):
- 3 regions (us-central1, europe-west1, asia-northeast1), min:1 max:50
- Cloud SQL proxy volume mount; secrets via Secret Manager
- Stripe + JWT env vars; health probe on /health

API Cloud Run (cloud-run-api.tf):
- 3 regions, min:1 max:100, cpu_idle=false (always-hot)
- Validates JWTs from accounts service; no direct DB connection
- License admin token from Secret Manager

Load balancer (host-based routing):
- Same global anycast IP for all three services
- URL map routes by Host: neurontechnologies.ai→marketing,
  api.neurontechnologies.ai→api, accounts.neurontechnologies.ai→accounts
- New managed SSL certs for api.* and accounts.* added to HTTPS proxy
- Cloud Armor (WAF + rate limit) applied to all backends

Service accounts + IAM:
- neuron-accounts-sa: secretmanager.secretAccessor + cloudsql.client
- neuron-api-sa: secretmanager.secretAccessor
- allUsers invoker on all prod Cloud Run services (LB health checks)

bootstrap.sh:
- One-shot setup: pulls Stripe secrets from Vault → Secret Manager,
  creates CI SA JSON key, prints DNS + next-step instructions
2026-04-25 22:54:18 -05:00

98 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# ── GCP Bootstrap — run once after terraform apply ────────────────────────────
# Sets up secrets and credentials that Terraform can't create automatically.
#
# Prerequisites:
# - GOOGLE_APPLICATION_CREDENTIALS or `gcloud auth application-default login`
# - GCP project access
# - Vault running (for reading existing secrets)
# - terraform apply has run successfully (creates SA, secrets)
#
# Usage:
# cd servers/gcp
# terraform apply
# bash bootstrap.sh
set -euo pipefail
PROJECT="neuron-785695"
VAULT_ADDR="${VAULT_ADDR:-https://vault.neuralplatform.ai}"
echo "==> Bootstrapping GCP secrets and CI credentials"
# ── 1. Populate Stripe secrets in GCP Secret Manager ─────────────────────────
echo "--> Pulling Stripe secrets from Vault..."
MARKETING_SECRETS=$(curl -sf \
-H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/secret/data/neuron-technologies/marketing")
populate_secret() {
local secret_id="$1"
local vault_key="$2"
local value
value=$(echo "${MARKETING_SECRETS}" | python3 -c "
import json,sys; d=json.load(sys.stdin)['data']['data']; print(d.get('${vault_key}',''))
")
if [ -z "${value}" ]; then
echo " WARN: vault key '${vault_key}' not found — skipping ${secret_id}"
return
fi
# Check if secret already has a version
if gcloud secrets versions list "${secret_id}" --project="${PROJECT}" \
--format="value(name)" 2>/dev/null | grep -q .; then
echo " SKIP: ${secret_id} already has a version"
else
echo "${value}" | gcloud secrets versions add "${secret_id}" \
--project="${PROJECT}" \
--data-file=-
echo " OK: ${secret_id}"
fi
}
populate_secret "stripe-secret-key" "stripe_secret_key"
populate_secret "stripe-webhook-secret" "stripe_webhook_secret"
populate_secret "stripe-price-professional" "stripe_price_professional"
populate_secret "stripe-price-founding" "stripe_price_founding"
# ── 2. License admin token ────────────────────────────────────────────────────
echo "--> Populating license-admin-token..."
LICENSE_TOKEN=$(curl -sf \
-H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/secret/data/neuron-technologies/marketing" \
| python3 -c "import json,sys; print(json.load(sys.stdin)['data']['data'].get('license_admin_token',''))")
if [ -n "${LICENSE_TOKEN}" ]; then
echo "${LICENSE_TOKEN}" | gcloud secrets versions add "license-admin-token" \
--project="${PROJECT}" --data-file=- 2>/dev/null || echo " SKIP: already exists"
echo " OK: license-admin-token"
fi
# ── 3. Create CI service account JSON key ────────────────────────────────────
CI_SA_EMAIL=$(terraform output -raw ci_pusher_email)
echo "--> Creating CI SA key for: ${CI_SA_EMAIL}"
KEY_FILE="/tmp/neuron-ci-pusher.json"
gcloud iam service-accounts keys create "${KEY_FILE}" \
--iam-account="${CI_SA_EMAIL}" \
--project="${PROJECT}"
echo " SA key written to: ${KEY_FILE}"
echo ""
echo " !! Add this as a Gitea secret GCP_SA_KEY in the neuron-technologies/neuron repo:"
echo " tea secret set --repo neuron-technologies/neuron GCP_SA_KEY < ${KEY_FILE}"
echo " Then delete the key file:"
echo " rm ${KEY_FILE}"
echo ""
# ── 4. Summary ────────────────────────────────────────────────────────────────
echo "==> Bootstrap complete"
echo ""
echo "Next steps:"
echo " 1. Set Gitea secret: tea secret set --repo neuron-technologies/neuron GCP_SA_KEY < /tmp/neuron-ci-pusher.json"
echo " 2. Delete key file: rm /tmp/neuron-ci-pusher.json"
echo " 3. Point DNS at: $(terraform output -raw prod_lb_ip)"
echo " A records (proxied=true in Cloudflare):"
echo " neurontechnologies.ai → prod_lb_ip"
echo " www.neurontechnologies.ai → prod_lb_ip"
echo " api.neurontechnologies.ai → prod_lb_ip"
echo " accounts.neurontechnologies.ai → prod_lb_ip"
echo " 4. Run a marketing CI build on main to push first image to GCP AR"
echo " 5. Check SSL cert provisioning: gcloud compute ssl-certificates list --project=${PROJECT}"