# Neuron — MCP server + Axon webhook hub # MCP: https://neuron.nook.family (internal, Traefik) # Webhooks: https://axon.neuralplatform.ai (external, Cloudflare tunnel) # # Image: registry.nook.family/neural-platform/neuron:latest # Built by GitHub Actions runner on Legion — see neural-platform/neuron ci.yml # # TODO: Migrate SQLite → Postgres once neuron db migration is ready # TODO: Wire Gmail OAuth credentials via k8s Secret (currently unauthenticated) resource "kubernetes_namespace" "neuron" { metadata { name = "neuron" labels = { managed-by = "terraform" tier = "platform" } } } resource "kubernetes_persistent_volume_claim" "neuron_data" { metadata { name = "neuron-data" namespace = kubernetes_namespace.neuron.metadata[0].name } spec { access_modes = ["ReadWriteOnce"] resources { requests = { storage = "5Gi" } } } } resource "kubernetes_config_map" "neuron_config" { metadata { name = "neuron-config" namespace = kubernetes_namespace.neuron.metadata[0].name } data = { MCP_PORT = "8001" MCP_HOST = "0.0.0.0" WEBHOOK_PORT = "3847" NEURON_DB_PATH = "/data/neuron.db" AXON_DB_PATH = "/data/axon.db" TUNNEL_NAME = "neural-platform" "default.yaml" = <<-EOT runtime: name: neuron version: 0.1.0 storage: db_path: /data/neuron.db cache: max_l1_size: 1000 gc: gen0_threshold: 3 gen1_threshold: 5 knowledge: base_path: /data/knowledge synapse: path: /data/synapse EOT } } resource "kubernetes_secret" "neuron_secrets" { metadata { name = "neuron-secrets" namespace = kubernetes_namespace.neuron.metadata[0].name } data = { GITHUB_WEBHOOK_SECRET = var.github_webhook_secret GITEA_WEBHOOK_SECRET = var.gitea_webhook_secret SLACK_BOT_TOKEN = var.slack_bot_token SLACK_SIGNING_SECRET = var.slack_signing_secret } } resource "kubernetes_deployment" "neuron" { metadata { name = "neuron" namespace = kubernetes_namespace.neuron.metadata[0].name labels = { app = "neuron" } } spec { replicas = 1 strategy { type = "Recreate" # SQLite cannot handle concurrent writers } selector { match_labels = { app = "neuron" } } template { metadata { labels = { app = "neuron" } } spec { container { name = "neuron" image = "registry.nook.family/neural-platform/neuron:latest" image_pull_policy = "Always" command = [ "bash", "-c", "mkdir -p /root/.neuron && ln -sf /data/neuron.db /root/.neuron/neuron.db && python -m synapse platform serve --mcp-host 0.0.0.0 --mcp-port 8001 --webhook-port 3847" ] port { name = "mcp" container_port = 8001 } port { name = "webhook" container_port = 3847 } env_from { config_map_ref { name = kubernetes_config_map.neuron_config.metadata[0].name } } env_from { secret_ref { name = kubernetes_secret.neuron_secrets.metadata[0].name } } volume_mount { name = "data" mount_path = "/data" } volume_mount { name = "app-config" mount_path = "/app/config/default.yaml" sub_path = "default.yaml" } resources { requests = { memory = "256Mi" cpu = "100m" } limits = { memory = "1Gi" cpu = "500m" } } liveness_probe { http_get { path = "/health" port = "mcp" } initial_delay_seconds = 10 period_seconds = 30 } readiness_probe { http_get { path = "/health" port = "mcp" } initial_delay_seconds = 5 period_seconds = 10 } } volume { name = "data" persistent_volume_claim { claim_name = kubernetes_persistent_volume_claim.neuron_data.metadata[0].name } } volume { name = "app-config" config_map { name = kubernetes_config_map.neuron_config.metadata[0].name items { key = "default.yaml" path = "default.yaml" } } } } } } depends_on = [kubernetes_persistent_volume_claim.neuron_data] } resource "kubernetes_service" "neuron" { metadata { name = "neuron" namespace = kubernetes_namespace.neuron.metadata[0].name } spec { selector = { app = "neuron" } port { name = "mcp" port = 8001 target_port = 8001 } port { name = "webhook" port = 3847 target_port = 3847 } type = "ClusterIP" } } # Internal MCP ingress — accessible within nook.family network resource "kubernetes_ingress_v1" "neuron" { metadata { name = "neuron" namespace = kubernetes_namespace.neuron.metadata[0].name annotations = { "traefik.ingress.kubernetes.io/router.entrypoints" = "websecure" "cert-manager.io/cluster-issuer" = "letsencrypt-prod" } } spec { ingress_class_name = "traefik" tls { secret_name = "neuron-tls" hosts = ["neuron.${var.infra_domain}"] } rule { host = "neuron.${var.infra_domain}" http { path { path = "/" path_type = "Prefix" backend { service { name = kubernetes_service.neuron.metadata[0].name port { number = 8001 } } } } } } } depends_on = [helm_release.cert_manager] } # cloudflared — Cloudflare tunnel for external access to Axon webhooks # Routes: axon.neuralplatform.ai → neuron:3847 # webhooks.neuralplatform.ai → neuron:3847 resource "kubernetes_deployment" "cloudflared" { metadata { name = "cloudflared" namespace = kubernetes_namespace.neuron.metadata[0].name labels = { app = "cloudflared" } } spec { replicas = 1 selector { match_labels = { app = "cloudflared" } } template { metadata { labels = { app = "cloudflared" } } spec { container { name = "cloudflared" image = "cloudflare/cloudflared:latest" args = ["tunnel", "--no-autoupdate", "run"] env { name = "TUNNEL_TOKEN" value = var.cloudflared_tunnel_token } resources { requests = { memory = "64Mi" cpu = "50m" } limits = { memory = "128Mi" cpu = "200m" } } liveness_probe { http_get { path = "/ready" port = 20241 } initial_delay_seconds = 10 period_seconds = 30 } } } } } depends_on = [kubernetes_deployment.neuron] }