44 Commits

Author SHA1 Message Date
Will Anderson 086a7b0bf0 add DocuSeal to GKE — manifests, Terraform, Argo CD app; update ci-base Dockerfile 2026-05-05 10:38:45 -05:00
Will Anderson 73b9095d83 migrate Terraform backend to GCS, remove dead Vault GCE nodes
Switch gcp/ backend from Cloudflare R2 (S3-compatible) to GCS — uses
Application Default Credentials automatically, no extra env vars needed.
State has been migrated to gs://neuron-785695-terraform-state/gcp/.

Remove GCE Vault node resources (vault-node-1/2/3) from vault-nodes.tf
and Terraform state. GKE Vault (Workload Identity + vault-unseal SA) has
been serving traffic since the NEG cutover; GCE nodes are confirmed dead.

Removed from state and config:
- google_compute_instance.vault_node (x3)
- google_compute_disk.vault_data (x3)
- google_compute_instance_group.vault (x3)
- google_compute_firewall.vault_iap_ssh / vault_raft / vault_api (GCE-targeted)
- google_storage_bucket_object.vault_startup
- google_storage_bucket_iam_member.vault_node_bucket_read
- google_project_iam_member.vault_node_{log_writer,metric_writer,compute_viewer}

Kept: KMS, vault-unseal SA, WI binding, NEG data sources, GKE firewall,
global LB (address, cert, backend service, url maps, proxies, fwd rules).
2026-05-05 10:38:22 -05:00
Will Anderson d54ec38a1c fix GCE runner: add build tools, libcurl, NOPASSWD sudo, Node.js 20 2026-05-05 10:27:45 -05:00
Will Anderson 422cca6962 scale gitea-runner to 0 while Autopilot DinD blocker is pending
Set replicas: 0 to prevent crash-loop restarts. The runner requires a Docker
daemon (even in host mode) which Autopilot won't provide. Scale to 1 when a
Standard node pool or external Docker TCP endpoint is available.
2026-05-05 10:22:21 -05:00
Will Anderson 9a368986b2 fix gitea-runner deployment for GKE Autopilot constraints
Switch to host-mode labels and document the DinD limitation: Autopilot's
Warden blocks privileged containers at the node level. Use internal Gitea
service URL (gitea.gitea.svc.cluster.local) to bypass Cloudflare Access.
Document three resolution paths (Standard node pool, Cloud Build, external
Docker TCP). GCE VM runner remains active in the interim.
2026-05-05 10:22:02 -05:00
Will Anderson c31edc8b83 deploy gitea CI runner to GKE as k8s pod
Replaces the GCE VM runner with a k8s Deployment in the ci namespace on
neuron-platform GKE. Uses Docker-in-Docker for build isolation since
Autopilot doesn't expose the node socket. Runner token pulled from Secret
Manager via ESO + Workload Identity.

- servers/gcp/k8s/gitea-runner/: namespace, serviceaccount, external-secrets,
  deployment manifests (ci namespace, dind sidecar, idempotent registration)
- servers/gcp/k8s/argocd-apps/gitea-runner-gke.yaml: Argo CD Application
- servers/gcp/gitea-runner.tf: gitea-runner-gke GCP SA with secretAccessor
  on gitea-runner-token, Workload Identity binding for ci/gitea-runner,
  artifactregistry.reader for pulling ci-base image
2026-05-05 10:16:49 -05:00
Will Anderson 3462ea37a4 Add ignoreDifferences for Deployment status in gitea-gke
Argo CD's bundled schema is missing terminatingReplicas from the
Deployment status spec, causing a ComparisonError on every sync.
Ignoring /status entirely is safe — Argo CD never manages status
fields; they're owned by kube-controller-manager.
2026-05-05 05:07:15 -05:00
Will Anderson 82e05429f1 Fix Argo CD child app repoURL: k3s Gitea → GKE Gitea
gitea-gke and vault-gke were pointing at the dead k3s-internal Gitea
(http://gitea.git.svc.cluster.local:3000). gke-apps already uses
https://git.neuralplatform.ai and syncs successfully — update child apps
to match. Once gke-apps picks this up, gitea-gke will sync the pending
GITEA__actions__ENABLED=true commit.
2026-05-05 05:04:01 -05:00
Will Anderson 686041946b Enable Gitea Actions (GITEA__actions__ENABLED=true) 2026-05-05 04:56:29 -05:00
Will Anderson cebaf44db3 Scale GKE Vault to 1 replica while raft standby deadlock is unresolved
Pods 1 and 2 can't unseal because port 8201 (cluster port) only opens
after barrier initialization, but the barrier key transfer requires 8201
to be reachable — deadlock. Running single-node on pod 0 so
vault.neuralplatform.ai reliably routes to the active, unsealed instance.
2026-05-05 04:37:20 -05:00
Will Anderson 2e5655c583 vault: rolling restart to fix raft join; remove sealedok from readiness probe 2026-05-05 03:59:33 -05:00
Will Anderson 804060c958 fix vault raft join addresses for GKE StatefulSet naming 2026-05-04 23:56:10 -05:00
Will Anderson 872a834989 Cut over Vault and Gitea traffic from GCE/Legion to GKE
Vault:
- Annotate vault-helm-gke Service with cloud.google.com/neg exposed_ports
  to create container-native NEGs (k8s1-bfbeff02-vault-...) in 3 zones
- Add vault-api-from-lb-gke firewall rule allowing GCP health check ranges
  (130.211.0.0/22, 35.191.0.0/16) to reach GKE pod IPs on port 8200
- Replace GCE instance group backends in google_compute_backend_service.vault
  with GKE NEG backends (RATE balancing mode, 100 req/endpoint)
- GCP Global HTTPS LB frontend unchanged — DNS stays at 34.54.164.21
- vault.neuralplatform.ai now terminates at GKE pods (all 3 NEGs HEALTHY)

Gitea:
- Change GKE Gitea Service from ClusterIP to LoadBalancer (external IP: 34.31.145.131)
- Add Cloudflare DNS A record for git.neuralplatform.ai → 34.31.145.131 (proxied)
- Remove git.neuralplatform.ai route from Legion Cloudflare tunnel config
- Add Cloudflare config rule: flexible SSL for git.neuralplatform.ai
  (origin serves HTTP/3000, CF proxies HTTPS termination)
- Scale Legion Gitea deployment to 0 replicas (PVC preserved)
- git.neuralplatform.ai now serves from GKE Gitea pod
2026-05-04 23:46:20 -05:00
Will Anderson 1c99931de6 fix(vault-gke): switch vault-1/2 to standard-rwo storage class
SSD quota (SSD_TOTAL_GB) is exhausted at 500/500 GB. pd-ssd PVCs for vault-1
and vault-2 cannot provision. Switching to standard-rwo (pd-balanced) unblocks
the Vault HA cluster immediately.

Quota increase request submitted (Cloud Quotas preference API, preference ID
0aa576b1-5050-4fa7-911d-6f408230df7f, target 1000GB). Once approved, can
optionally migrate back to premium-rwo for all-SSD Vault storage.
2026-05-04 22:41:38 -05:00
Will Anderson 3d581368c3 fix(vault-gke): relax topology spread to ScheduleAnyway while SSD quota is exhausted
vault-1 and vault-2 are stuck Pending because GCE quota (SSD_TOTAL_GB 500/500)
prevents new nodes from provisioning. ScheduleAnyway lets them land in any zone
that has capacity while the quota increase request is pending.

Also adds ESO Workload Identity IAM bindings to Terraform state (previously applied
out-of-band via gcloud; now tracked in cloud-sql.tf).
2026-05-04 22:37:17 -05:00
Will Anderson 8592a1ed74 fix(gitea-gke): configure ESO SecretStore to use workload identity with gitea SA 2026-05-04 22:05:01 -05:00
Will Anderson 9252b069a2 fix(gitea-gke): update ESO API version from v1beta1 to v1 2026-05-04 22:00:21 -05:00
Will Anderson c4f7dcc185 fix(vault-gke): raise cpu request to 500m for GKE Autopilot anti-affinity minimum 2026-05-04 21:59:51 -05:00
Will Anderson b572f5720b infra: wire GKE cluster endpoint and fix gitea SA account_id
- Replace GKE_CLUSTER_ENDPOINT placeholder with real endpoint (34.63.89.52)
  in all three Argo CD Application manifests
- Fix gitea GCP SA account_id from 'gitea' (5 chars, too short) to 'gitea-gke'
  to satisfy GCP's 6-30 char constraint
- Update serviceaccount.yaml annotation to match new SA email (gitea-gke@...)
2026-05-04 21:56:06 -05:00
Will Anderson 9330107fcc migrate Vault and Gitea to GKE Autopilot cluster
Implements Option A: move Vault (3x GCE e2-small) and Gitea (Legion k8s)
onto a new GKE Autopilot cluster (neuron-platform, us-central1) managed
through Legion Argo CD.

Terraform (servers/gcp/):
- gke.tf: GKE Autopilot cluster, Workload Identity bindings for Vault (KMS)
  and Gitea (Cloud SQL)
- cloud-sql.tf: gitea database + user on neuron-prod-pg15, gitea GCP SA,
  gitea-database-url and gitea-db-password Secret Manager secrets
- vault-nodes.tf: PENDING DECOMMISSION comment with migration checklist

k8s manifests (servers/gcp/k8s/):
- vault/: namespace.yaml
- gitea/: namespace, serviceaccount (Workload Identity annotation), pvc (50Gi
  standard-rwo), deployment (Gitea + Cloud SQL Auth Proxy sidecar), service,
  configmap (custom CSS carried from Legion), external-secrets (GCP SM provider)
- argocd-apps/: vault-gke.yaml, vault-helm-gke.yaml (Helm chart, HA Raft 3
  replicas, GCP KMS auto-unseal, topologySpread across zones, 10Gi premium-rwo),
  gitea-gke.yaml — all target GKE_CLUSTER_ENDPOINT placeholder

Legion (servers/legion/):
- apps/gke-apps.yaml: App-of-Apps entry point on Legion Argo CD that syncs the
  GKE Application manifests
- k8s/gitea-runner/Dockerfile: add system-level git insteadOf so GKE CI runners
  resolve Gitea in-cluster without Cloudflare Access headers
2026-05-04 20:40:48 -05:00
Will Anderson 246368c132 vault: GCE Raft HA cluster replacing defunct Legion k3s deployment
Legion is offline. Vault needs a GCP-native home. This commit provisions
a 3-node Vault HA cluster on GCE e2-small VMs (us-central1 a/b/c) backed
by Raft consensus with GCP KMS auto-unseal already in place.

- vault-nodes.tf: 3 VMs with separate pd-ssd data disks (prevent_destroy),
  IAP-only SSH firewall, Raft peer firewall (8201), global HTTPS LB for
  vault.neuralplatform.ai with Google-managed cert, HTTP health checks
  against /v1/sys/health
- vault/startup.sh: idempotent boot script — installs Vault from HashiCorp
  APT repo, mounts/formats the data disk, writes Raft config with dynamic
  retry_join peers resolved via gcloud metadata, starts vault.service
- vault-auth-gcp.tf: GCP Secret Manager secret for init script; outputs
  the service account emails needed for GCP auth roles
- vault/configure-vault-auth.sh: post-init script to enable gcp auth
  method, write marketing-policy and soma-policy, bind roles to Cloud Run
  service account identities

After apply: set vault_lb_ip output as A record for vault.neuralplatform.ai
in Cloudflare, then run vault operator init on vault-node-1 to bootstrap.
2026-05-04 10:17:12 -05:00
Will Anderson 89564ccec7 bring unmanaged GCP resources under Terraform
vault-kms.tf: KMS keyring/key for Vault auto-unseal + vault-unseal SA
and IAM bindings — all imported from existing GCP resources.
prevent_destroy=true on the crypto key to protect against accidental deletion.

unmanaged-secrets.tf: 8 secrets that were referenced by live Cloud Run
services but had no TF resource — anthropic-api-key, resend-api-key,
supabase-service-key, docuseal-webhook-token, stripe-price-professional,
stripe-price-founding, stripe-price-family-child, gitea-runner-token.

service-account.tf: 4 manually-created SAs — neuron-workspace-dwd,
docuseal-storage, analytics-reader, harmonic-search-console.
2026-05-04 09:57:33 -05:00
Will Anderson 7e092d4686 fix TF drift: add DOCUSEAL_WEBHOOK_TOKEN and TOGETHER_API_KEY env vars
DOCUSEAL_WEBHOOK_TOKEN was present on all live marketing services but
missing from cloud-run.tf and cloud-run-stage.tf — next apply would
have stripped it and broken DocuSeal webhook validation.

TOGETHER_API_KEY (soma-together-api-key) was present on soma-prod-us
but missing from cloud-run-soma.tf — next apply would have broken the
Together.ai inference routing path.
2026-05-04 09:44:46 -05:00
Will Anderson b3609d6401 fold in dev-env + ci-runner provisioning fixups
cloud-run.tf + cloud-run-stage.tf: small alignment edits from the
dev-env agent's work to match the actual deployed Cloud Run shape.

runners/startup.sh: 10-line additions from the gitea-runner agent
during initial provisioning - environment setup adjustments
discovered when the runner came up the first time.

stripe-billing.tf: prior-session work that hadn't been committed,
folding it in now to clean the working tree before further changes.
2026-05-02 13:21:32 -05:00
Will Anderson cd2c22c295 add marketing-dev environment - team-internal auto-deploy zone
dev.neurontechnologies.ai mirrors stage in shape but looser:
broken builds OK, auto-deploys on every push to dev branch (once
CI lands), dev Supabase project (isolated), test Stripe keys,
RESEND_DRY_RUN=1 so transactional email never actually sends.

Locked behind Cloudflare Access with team policy: anyone with
@neurontechnologies.ai email plus named external collaborators
via the access app's emails list.

Initial image is marketing:fix-gallery-render-1236 (matches stage
at the time of this commit). Future deploys land via CI workflow.
2026-05-02 13:13:58 -05:00
Will Anderson 40c6a31784 add sandbox.neurontechnologies.ai - internal-only experimentation env
Locked down to email_domain == neurontechnologies.ai via Cloudflare
Access. Dedicated SA with zero prod IAM. Isolated Supabase project
(not a schema in prod). No outbound email path. /api/share and /said
return 410 Gone in sandbox so the public-artifact surface stays
strictly in prod.

Stub deploy ready. Real soul build pipeline lands separately.
2026-05-02 12:53:16 -05:00
Will Anderson 0c32964ead ci: add gitea actions runner on GCP with WIF-backed deploy SA
A single e2-standard-4 GCE VM in us-central1-a runs act_runner,
registered to git.neuralplatform.ai. Workload Identity Federation
binds the runner to the existing neuron-ci-pusher SA (artifactregistry.writer
+ run.developer + serviceAccountUser on the runtime SAs). No long-lived
JSON keys live on the runner; the GCP_SA_KEY secret remains as a fallback
in case the Gitea OIDC issuer isn't reachable from oauth2.googleapis.com.

The runner VM has its own minimal-scope SA (gitea-runner-vm) that can
only read the registration token from Secret Manager — splitting boot
identity from deploy identity so a runner compromise doesn't grant push.

SSH is IAP-only (no public ingress on :22). Reference workflow lives at
products/web/.gitea/workflows/deploy.yaml and takes manual gcloud out of
the loop: push to main triggers build, push to AR, parallel deploy to
all 3 marketing prod regions, traffic flip, smoke check.
2026-05-02 12:45:25 -05:00
Will Anderson 7ee6099139 dns: document email auth records and rationale for out-of-band management
SPF / DKIM / DMARC for neurontechnologies.ai are managed via the Cloudflare
API rather than Terraform because the cloudflare_record provider rewrites
name="@" on apply, which forces replacement of the apex SPF record and
opens a propagation gap. A duplicate (malformed-quoted) apex SPF that was
causing Gmail to fail SPF was also removed via the API; the surviving
record is the canonical one and is documented here for traceability.
2026-05-01 22:49:32 -05:00
Will Anderson ec2d5794ec prod: fix Cloud Run config for v1.0 (port 8080, env vars, secrets) 2026-05-01 18:13:00 -05:00
Will Anderson d9ac977fef Wire Stripe billing secrets and env vars into Soma Cloud Run
- stripe-billing.tf: Secret Manager resources for all billing IDs:
  meters (input/output), plan prices (free/professional/founding),
  overage prices per plan per token type
- cloud-run-soma.tf: inject all 13 Stripe env vars into the Soma service
  via Secret Manager secret_key_ref; add billing secrets to depends_on
2026-04-29 04:34:13 -05:00
Will Anderson e5c9fd1287 Add Anthropic API key secret and env var to Soma Cloud Run
Provisions soma-anthropic-api-key in Secret Manager and injects it as
ANTHROPIC_API_KEY into the Cloud Run service. Also sets ANTHROPIC_MODEL
to claude-sonnet-4-5. Soma's chat handler now uses this to route the
neuron model to Anthropic instead of spinning up GPU VMs.
2026-04-28 21:29:27 -05:00
Will Anderson c3a90ac484 Add SOMA_KEY_SECRET to Secret Manager and wire it into Cloud Run
Creates soma-key-secret in GCP Secret Manager for HMAC key signing, and
adds SOMA_KEY_SECRET + SOMA_BASE_DOMAIN env vars to the soma-prod-us Cloud
Run service so stateless API key validation works in production.
2026-04-28 11:39:33 -05:00
Will Anderson 1a8e039ecf Rename inference endpoint to neuron.neurontechnologies.ai, add wildcard DNS for customer orgs 2026-04-28 11:15:11 -05:00
Will Anderson 9e2646701c Grant soma compute permissions for GCE inference nodes
- Add roles/compute.instanceAdmin.v1 for soma SA (provision/terminate GCE VMs)
- Add roles/iam.serviceAccountUser for soma SA (assign SA to inference VMs)
- Add firewall rule: allow TCP 8000 inbound to soma-inference-node tagged VMs
- Add soma-node-secret to Secret Manager (GCE startup script auth)
- Inject SOMA_NODE_SECRET, GCP_PROJECT_ID, GCP_ZONE, SOMA_PUBLIC_URL into
  the soma Cloud Run service via env vars
2026-04-28 10:48:32 -05:00
Will Anderson f2b025a433 Deploy soma to GCP Cloud Run at ai.neurontechnologies.ai
- Add cloud-run-soma.tf: soma-prod-us Cloud Run service in us-central1,
  neuron-soma-sa service account, soma Artifact Registry repo, Secret
  Manager secrets for HF token and operator key, serverless NEG, backend
  service, SSL cert
- Add dns-gcp.tf: Cloudflare A record for ai.neurontechnologies.ai pointing
  to GCP LB IP; Cloudflare provider added to main.tf/variables.tf
- Update load-balancer.tf: soma host rule + path matcher, soma SSL cert
  added to HTTPS proxy
- Update outputs.tf: soma service URL and artifact registry URL outputs
- Remove legion soma k8s manifests (Legion is gone)
- Update AGENTS.md to reflect GCP as primary production environment
2026-04-28 10:15:15 -05:00
Will Anderson 55a38bb693 fix: update Gitea URL to new GCP cluster instance
Legion hardware died April 27, 2026. New GCP k3s instance at 104.197.5.199.
Gitea now at gitea.git.svc.cluster.local:3000 within cluster.
2026-04-27 16:20:46 -05:00
Will Anderson fdbbfdb0ad iam: grant ci-pusher serviceAccountUser on all Cloud Run runtime SAs 2026-04-26 11:18:50 -05:00
Will Anderson e22dce6b5a iam: grant ci-pusher run.developer for Cloud Run deploys 2026-04-26 10:53:21 -05:00
Will Anderson 5b16aebdcb Remove stage environment from GCP — staging is local only 2026-04-26 01:09:44 -05:00
Will Anderson 53f2ad3c16 Fix Cloud Run probe ports and LB timeout_sec
- accounts: add ACCOUNTS_PORT=8080 env var (service defaults to 7753)
- api: add SERVER_PORT=8080 env var, change probe path to /actuator/health
- LB backends: remove timeout_sec (not supported for serverless NEG backends)
2026-04-26 00:28:33 -05:00
Will Anderson 36cfd3738d Fix Cloud Run: remove reserved PORT env, increase startup probe tolerance 2026-04-25 23:55:33 -05:00
Will Anderson bb583e3ccb Fix HCL syntax errors in accounts and api Cloud Run definitions 2026-04-25 22:54:18 -05:00
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
Will Anderson 91bfc42989 ops: scale down all non-critical services — dedicate resources to Neuron
Scaled to replicas: 0: fornax (coordinator/ui/workers), jellyfin via media,
overseerr, plex, radarr, sonarr, bazarr, prowlarr, flaresolverr, ollama,
harmonic-wordpress, docuseal, listmonk, memos, plane, redpanda, wp-coordinator
2026-04-24 20:07:41 -05:00