{
  "manifest_version": "1.0.0",
  "template": {
    "id": "2089fc66-aff5-4d27-95ea-c7e3fb380f8c",
    "slug": "aisoc-v20-ai-security-operations-center",
    "name": "AiSOC v20 AI Security Operations Center",
    "description": "Ingest security alerts, correlate threats and triage them with AI agents.",
    "url": "https://railway.com/deploy/aisoc-v20-ai-security-operations-center",
    "upstream": {
      "image": "ghcr.io/beenuar/aisoc-web:v20.3.0"
    }
  },
  "status": "unvalidated",
  "validated_at": null,
  "success_rate_30d": null,
  "services": [
    {
      "name": "connectors",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-connectors:v20.3.0"
      },
      "needs_volume": false,
      "http": false
    },
    {
      "name": "qdrant",
      "source": {
        "image": "qdrant/qdrant:v1.19.2"
      },
      "needs_volume": true,
      "volume_mount_path": "/qdrant/storage",
      "http": false
    },
    {
      "name": "opensearch",
      "source": {
        "repo": "https://github.com/baranberkay96/aisoc-railway"
      },
      "needs_volume": true,
      "volume_mount_path": "/usr/share/opensearch/data",
      "http": false
    },
    {
      "name": "ollama",
      "source": {
        "repo": "https://github.com/baranberkay96/aisoc-railway"
      },
      "needs_volume": true,
      "volume_mount_path": "/root/.ollama",
      "http": false
    },
    {
      "name": "neo4j",
      "source": {
        "image": "neo4j:5.15.0-community"
      },
      "needs_volume": true,
      "volume_mount_path": "/data",
      "http": false
    },
    {
      "name": "threatintel",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-threatintel:v20.3.0"
      },
      "needs_volume": false,
      "http": false
    },
    {
      "name": "web",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-web:v20.3.0"
      },
      "needs_volume": false,
      "http": true
    },
    {
      "name": "fusion",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-fusion:v20.3.0"
      },
      "needs_volume": true,
      "volume_mount_path": "/data",
      "http": false
    },
    {
      "name": "enrichment",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-enrichment:v20.3.0"
      },
      "needs_volume": false,
      "http": false
    },
    {
      "name": "actions",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-actions:v20.3.0"
      },
      "needs_volume": false,
      "http": false
    },
    {
      "name": "clickhouse",
      "source": {
        "repo": "https://github.com/baranberkay96/aisoc-railway"
      },
      "needs_volume": true,
      "volume_mount_path": "/var/lib/clickhouse",
      "http": false
    },
    {
      "name": "agents",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-agents:v20.3.0"
      },
      "needs_volume": true,
      "volume_mount_path": "/data",
      "http": false
    },
    {
      "name": "litellm",
      "source": {
        "repo": "https://github.com/baranberkay96/aisoc-railway"
      },
      "needs_volume": false,
      "http": false
    },
    {
      "name": "Redis",
      "source": {
        "image": "redis:8.2.11"
      },
      "needs_volume": true,
      "volume_mount_path": "/data",
      "http": false
    },
    {
      "name": "Postgres",
      "source": {
        "image": "ghcr.io/railwayapp-templates/postgres-ssl:18"
      },
      "needs_volume": true,
      "volume_mount_path": "/var/lib/postgresql/data",
      "http": false
    },
    {
      "name": "api",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-core-api:v20.3.0"
      },
      "needs_volume": false,
      "http": false
    },
    {
      "name": "ueba",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-ueba:v20.3.0"
      },
      "needs_volume": false,
      "http": false
    },
    {
      "name": "kafka",
      "source": {
        "image": "redpandadata/redpanda:v24.3.18"
      },
      "needs_volume": true,
      "volume_mount_path": "/var/lib/redpanda/data",
      "http": false
    },
    {
      "name": "realtime",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-realtime:v20.3.0"
      },
      "needs_volume": false,
      "http": false
    },
    {
      "name": "ingest",
      "source": {
        "image": "ghcr.io/beenuar/aisoc-ingest:v20.3.0"
      },
      "needs_volume": false,
      "http": true
    }
  ],
  "required_inputs": [
    {
      "key": "PORT",
      "service": "connectors",
      "description": "Port the connectors service listens on (dual-stack); the API's connectors URL points here.",
      "secret": false,
      "strategy": "default",
      "default": "8003"
    },
    {
      "key": "REDIS_URL",
      "service": "connectors",
      "description": "Redis (database 6).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "SECRET_KEY",
      "service": "connectors",
      "description": "Console token key (the API forwards console tokens here).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "ENVIRONMENT",
      "service": "connectors",
      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "CORE_API_URL",
      "service": "connectors",
      "description": "Core API.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "DATABASE_URL",
      "service": "connectors",
      "description": "Postgres as aisoc_app.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_DEV_MODE",
      "service": "connectors",
      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "INGEST_SERVICE_URL",
      "service": "connectors",
      "description": "Ingest service on the private network (polled events are pushed here).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_SERVICE_TOKEN",
      "service": "connectors",
      "description": "Shared service token (also used to push polled events to ingest).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_CREDENTIAL_KEY",
      "service": "connectors",
      "description": "Decrypts stored connector credentials at poll time (the scheduler refuses to start without it).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "KAFKA_BOOTSTRAP_SERVERS",
      "service": "connectors",
      "description": "Kafka-API broker.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_PUBLISHED_BIND_ADDRS",
      "service": "connectors",
      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
      "secret": false,
      "strategy": "default",
      "default": "0.0.0.0"
    },
    {
      "key": "AISOC_ALLOW_CLEARTEXT_KAFKA",
      "service": "connectors",
      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "PORT",
      "service": "qdrant",
      "description": "Qdrant HTTP port; Railway's healthcheck probes /readyz here.",
      "secret": false,
      "strategy": "default",
      "default": "6333"
    },
    {
      "key": "QDRANT__SERVICE__HOST",
      "service": "qdrant",
      "description": "Listen on IPv4 and IPv6.",
      "secret": false,
      "strategy": "default",
      "default": "::"
    },
    {
      "key": "QDRANT__TELEMETRY_DISABLED",
      "service": "qdrant",
      "description": "No usage telemetry.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "PORT",
      "service": "opensearch",
      "description": "OpenSearch HTTP port; Railway's healthcheck probes it.",
      "secret": false,
      "strategy": "default",
      "default": "9200"
    },
    {
      "key": "node.name",
      "service": "opensearch",
      "description": "Node name (as upstream).",
      "secret": false,
      "strategy": "default",
      "default": "aisoc-node-1"
    },
    {
      "key": "cluster.name",
      "service": "opensearch",
      "description": "Cluster name (as upstream).",
      "secret": false,
      "strategy": "default",
      "default": "aisoc-cluster"
    },
    {
      "key": "network.host",
      "service": "opensearch",
      "description": "Bind address (Java listens dual-stack). 0.0.0.0, not '::' (Rule: settings are passed through unquoted).",
      "secret": false,
      "strategy": "default",
      "default": "0.0.0.0"
    },
    {
      "key": "discovery.type",
      "service": "opensearch",
      "description": "Single-node cluster (also skips the production bootstrap checks).",
      "secret": false,
      "strategy": "default",
      "default": "single-node"
    },
    {
      "key": "OPENSEARCH_JAVA_OPTS",
      "service": "opensearch",
      "description": "JVM heap (as upstream). The process settles near 1 GB RSS with this heap.",
      "secret": false,
      "strategy": "default",
      "default": "-Xms512m -Xmx512m"
    },
    {
      "key": "bootstrap.memory_lock",
      "service": "opensearch",
      "description": "memlock ulimits cannot be raised on Railway (upstream sets true with an unlimited memlock).",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "node.store.allow_mmap",
      "service": "opensearch",
      "description": "Do not depend on the host's vm.max_map_count.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "DISABLE_SECURITY_PLUGIN",
      "service": "opensearch",
      "description": "Security plugin off as upstream: reachable only on the private network, and the threatintel client connects without credentials.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "DISABLE_INSTALL_DEMO_CONFIG",
      "service": "opensearch",
      "description": "No demo certificates or users (as upstream).",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "RAILWAY_DEPLOYMENT_DRAINING_SECONDS",
      "service": "opensearch",
      "description": "Allow a clean shutdown on redeploy.",
      "secret": false,
      "strategy": "default",
      "default": "30"
    },
    {
      "key": "DISABLE_PERFORMANCE_ANALYZER_AGENT_CLI",
      "service": "opensearch",
      "description": "Skip the performance-analyzer side process (saves memory; AiSOC does not use it).",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "PORT",
      "service": "ollama",
      "description": "Port Ollama listens on; Railway's healthcheck probes it.",
      "secret": false,
      "strategy": "default",
      "default": "11434"
    },
    {
      "key": "OLLAMA_HOST",
      "service": "ollama",
      "description": "Listen on IPv4 and IPv6.",
      "secret": false,
      "strategy": "default",
      "default": "[::]:11434"
    },
    {
      "key": "OLLAMA_BASE_MODEL",
      "service": "ollama",
      "description": "Model the CPU-tuned copy is created from.",
      "secret": false,
      "strategy": "default",
      "default": "llama3.2:3b-instruct-q4_K_M"
    },
    {
      "key": "OLLAMA_KEEP_ALIVE",
      "service": "ollama",
      "description": "Unload the model after 30 idle minutes (frees about 2.5 GB).",
      "secret": false,
      "strategy": "default",
      "default": "30m"
    },
    {
      "key": "OLLAMA_PULL_MODELS",
      "service": "ollama",
      "description": "Models pulled on first boot (comma-separated). Upstream's pinned CPU model, about 2 GB.",
      "secret": false,
      "strategy": "default",
      "default": "llama3.2:3b-instruct-q4_K_M"
    },
    {
      "key": "OLLAMA_NUM_PARALLEL",
      "service": "ollama",
      "description": "One generation at a time (CPU); other requests queue.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "OLLAMA_DEFAULT_MODEL",
      "service": "ollama",
      "description": "CPU-tuned copy (explicit thread count and context) that the LiteLLM aliases use. Recreated on every start.",
      "secret": false,
      "strategy": "default",
      "default": "aisoc-llama3.2:3b"
    },
    {
      "key": "OLLAMA_KV_CACHE_TYPE",
      "service": "ollama",
      "description": "8-bit KV cache (about half the memory of f16).",
      "secret": false,
      "strategy": "default",
      "default": "q8_0"
    },
    {
      "key": "OLLAMA_CONTEXT_LENGTH",
      "service": "ollama",
      "description": "Context window of the tuned model (tokens).",
      "secret": false,
      "strategy": "default",
      "default": "8192"
    },
    {
      "key": "OLLAMA_FLASH_ATTENTION",
      "service": "ollama",
      "description": "Flash attention on (smaller KV cache).",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "OLLAMA_MAX_LOADED_MODELS",
      "service": "ollama",
      "description": "Keep at most one model in memory.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "PORT",
      "service": "neo4j",
      "description": "Neo4j HTTP port; Railway's healthcheck probes it.",
      "secret": false,
      "strategy": "default",
      "default": "7474"
    },
    {
      "key": "NEO4J_AUTH",
      "service": "neo4j",
      "description": "Initial credentials, applied on first boot.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "NEO4J_PLUGINS",
      "service": "neo4j",
      "description": "APOC core (bundled in the image, as upstream).",
      "secret": false,
      "strategy": "default",
      "default": "[\"apoc\"]"
    },
    {
      "key": "AISOC_NEO4J_PASSWORD",
      "service": "neo4j",
      "description": "Password of the neo4j user (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "NEO4J_server_memory_heap_max__size",
      "service": "neo4j",
      "description": "JVM heap limit.",
      "secret": false,
      "strategy": "default",
      "default": "512m"
    },
    {
      "key": "NEO4J_server_memory_pagecache_size",
      "service": "neo4j",
      "description": "Page cache (Neo4j would otherwise size it from host memory).",
      "secret": false,
      "strategy": "default",
      "default": "256m"
    },
    {
      "key": "RAILWAY_DEPLOYMENT_DRAINING_SECONDS",
      "service": "neo4j",
      "description": "Allow a clean shutdown on redeploy.",
      "secret": false,
      "strategy": "default",
      "default": "30"
    },
    {
      "key": "NEO4J_server_memory_heap_initial__size",
      "service": "neo4j",
      "description": "JVM heap start (upstream values, Neo4j 5 setting names).",
      "secret": false,
      "strategy": "default",
      "default": "256m"
    },
    {
      "key": "NEO4J_dbms_security_procedures_unrestricted",
      "service": "neo4j",
      "description": "Allow APOC procedures (as upstream).",
      "secret": false,
      "strategy": "default",
      "default": "apoc.*"
    },
    {
      "key": "PORT",
      "service": "threatintel",
      "description": "Port the threat-intel service listens on (fixed in the image's command).",
      "secret": false,
      "strategy": "default",
      "default": "8005"
    },
    {
      "key": "NEO4J_URI",
      "service": "threatintel",
      "description": "Neo4j entity graph.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "REDIS_URL",
      "service": "threatintel",
      "description": "Redis (database 7).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "NEO4J_USER",
      "service": "threatintel",
      "description": "Neo4j user.",
      "secret": false,
      "strategy": "default",
      "default": "neo4j"
    },
    {
      "key": "SECRET_KEY",
      "service": "threatintel",
      "description": "Console token key.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "ENVIRONMENT",
      "service": "threatintel",
      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "QDRANT_HOST",
      "service": "threatintel",
      "description": "Qdrant host (this service reads host and port, not a URL).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "QDRANT_PORT",
      "service": "threatintel",
      "description": "Qdrant HTTP port.",
      "secret": false,
      "strategy": "default",
      "default": "6333"
    },
    {
      "key": "AISOC_DEV_MODE",
      "service": "threatintel",
      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "NEO4J_PASSWORD",
      "service": "threatintel",
      "description": "Neo4j password.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "OPENSEARCH_HOST",
      "service": "threatintel",
      "description": "OpenSearch host (IOC and actor full-text index).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "OPENSEARCH_PORT",
      "service": "threatintel",
      "description": "OpenSearch HTTP port.",
      "secret": false,
      "strategy": "default",
      "default": "9200"
    },
    {
      "key": "AISOC_SERVICE_TOKEN",
      "service": "threatintel",
      "description": "Shared service token (the API presents it when it proxies indicators).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "KAFKA_BOOTSTRAP_SERVERS",
      "service": "threatintel",
      "description": "Kafka-API broker.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_PUBLISHED_BIND_ADDRS",
      "service": "threatintel",
      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
      "secret": false,
      "strategy": "default",
      "default": "0.0.0.0"
    },
    {
      "key": "AISOC_ALLOW_CLEARTEXT_KAFKA",
      "service": "threatintel",
      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "PORT",
      "service": "web",
      "description": "Port the Next.js console listens on (the image starts it with -p 3000); Railway routes the public domain and its healthcheck here.",
      "secret": false,
      "strategy": "default",
      "default": "3000"
    },
    {
      "key": "API_URL",
      "service": "web",
      "description": "Core API on the private network. The console proxies /api/v1/* and /auth/* here server-side, so the API needs no public domain.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "NODE_ENV",
      "service": "web",
      "description": "Next.js production mode.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "AGENTS_URL",
      "service": "web",
      "description": "Agents service on the private network (copilot, hunts, playbooks routes).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "REALTIME_URL",
      "service": "web",
      "description": "Realtime gateway on the private network (WebSocket /ws and SSE /sse are proxied here).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "ENRICHMENT_URL",
      "service": "web",
      "description": "Deliberately unreachable. The console has no caller of /api/v1/enrichment/*, and that rewrite would otherwise expose the unauthenticated enrichment service through the public console.",
      "secret": false,
      "strategy": "default",
      "default": "http://127.0.0.1:9"
    },
    {
      "key": "AISOC_DEMO_MODE",
      "service": "web",
      "description": "Demo mode off: the console never shows seeded demo data or the demo banner. Keep false.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "PORT",
      "service": "fusion",
      "description": "Port fusion listens on (fixed in the image's command); Railway's healthcheck probes it.",
      "secret": false,
      "strategy": "default",
      "default": "8003"
    },
    {
      "key": "REDIS_URL",
      "service": "fusion",
      "description": "Redis (database 3).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "SECRET_KEY",
      "service": "fusion",
      "description": "Console token key (verifies console callers).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "ENVIRONMENT",
      "service": "fusion",
      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "DATABASE_URL",
      "service": "fusion",
      "description": "Postgres as aisoc_app (alerts, outbox).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_DEV_MODE",
      "service": "fusion",
      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "CLICKHOUSE_HOST",
      "service": "fusion",
      "description": "ClickHouse host.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "CLICKHOUSE_PORT",
      "service": "fusion",
      "description": "ClickHouse native port.",
      "secret": false,
      "strategy": "default",
      "default": "9000"
    },
    {
      "key": "CLICKHOUSE_USER",
      "service": "fusion",
      "description": "ClickHouse user.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_SERVICE_TOKEN",
      "service": "fusion",
      "description": "Shared service token.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "CLICKHOUSE_DATABASE",
      "service": "fusion",
      "description": "ClickHouse database.",
      "secret": false,
      "strategy": "default",
      "default": "aisoc"
    },
    {
      "key": "CLICKHOUSE_PASSWORD",
      "service": "fusion",
      "description": "ClickHouse password.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_LAKE_SPOOL_DIR",
      "service": "fusion",
      "description": "On-disk spool for lake rows not yet written (on the volume, so committed Kafka offsets are never lost on redeploy).",
      "secret": false,
      "strategy": "default",
      "default": "/data/lake-spool"
    },
    {
      "key": "ENRICHMENT_SERVICE_URL",
      "service": "fusion",
      "description": "Enrichment service for fuse-time IOC enrichment.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "KAFKA_BOOTSTRAP_SERVERS",
      "service": "fusion",
      "description": "Kafka-API broker (Redpanda).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_LAKE_WRITER_ENABLED",
      "service": "fusion",
      "description": "Archive every event to the ClickHouse lake (upstream 'full' profile).",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "AISOC_PUBLISHED_BIND_ADDRS",
      "service": "fusion",
      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
      "secret": false,
      "strategy": "default",
      "default": "0.0.0.0"
    },
    {
      "key": "AISOC_ALLOW_CLEARTEXT_KAFKA",
      "service": "fusion",
      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "PORT",
      "service": "enrichment",
      "description": "Port Railway's healthcheck probes (same as HTTP_PORT).",
      "secret": false,
      "strategy": "default",
      "default": "8082"
    },
    {
      "key": "HTTP_PORT",
      "service": "enrichment",
      "description": "Enrichment HTTP port (private only; this service has no authentication of its own).",
      "secret": false,
      "strategy": "default",
      "default": "8082"
    },
    {
      "key": "REDIS_URL",
      "service": "enrichment",
      "description": "Redis (database 2) for the enrichment cache.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "KAFKA_BOOTSTRAP_SERVERS",
      "service": "enrichment",
      "description": "Kafka-API broker.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "PORT",
      "service": "actions",
      "description": "Port the actions service listens on (fixed in the image's command).",
      "secret": false,
      "strategy": "default",
      "default": "8085"
    },
    {
      "key": "REDIS_URL",
      "service": "actions",
      "description": "Redis (database 5).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "SECRET_KEY",
      "service": "actions",
      "description": "Console token key.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "ENVIRONMENT",
      "service": "actions",
      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "CORE_API_URL",
      "service": "actions",
      "description": "Core API.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "DATABASE_URL",
      "service": "actions",
      "description": "Postgres as aisoc_app (action records).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_DEV_MODE",
      "service": "actions",
      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "AISOC_API_BASE_URL",
      "service": "actions",
      "description": "Core API (name read by the actions settings).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_MATURITY_TIER",
      "service": "actions",
      "description": "Autonomy tier L1-Notify (upstream default, set explicitly): anything above read-only impact waits for a human approval.",
      "secret": false,
      "strategy": "default",
      "default": "L1"
    },
    {
      "key": "AISOC_SERVICE_TOKEN",
      "service": "actions",
      "description": "Shared service token.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_CREDENTIAL_KEY",
      "service": "actions",
      "description": "Decrypts vault-encrypted connector credentials in action payloads.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "KAFKA_BOOTSTRAP_SERVERS",
      "service": "actions",
      "description": "Kafka-API broker.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_PUBLISHED_BIND_ADDRS",
      "service": "actions",
      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
      "secret": false,
      "strategy": "default",
      "default": "0.0.0.0"
    },
    {
      "key": "AISOC_ACTIONS_SERVICE_TOKEN",
      "service": "actions",
      "description": "Bearer required on POST /actions and approve/reject; without it every mutating route answers 503.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_ALLOW_CLEARTEXT_KAFKA",
      "service": "actions",
      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "AISOC_ALLOW_HIGH_BLAST_AUTO",
      "service": "actions",
      "description": "Break-glass for unattended high-blast-radius actions. Keep false.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "AISOC_ACTIONS_DEFAULT_DRY_RUN",
      "service": "actions",
      "description": "Dry-run by default unless an operator opts out (upstream default, set explicitly).",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "AISOC_ACTIONS_REQUIRE_APPROVER",
      "service": "actions",
      "description": "Approve/reject calls must carry a resolvable approver identity (separation of duties).",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "AISOC_FEATURE_MAILBOX_REMEDIATION",
      "service": "actions",
      "description": "Mailbox purge/restore/block verbs off (upstream default).",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "PORT",
      "service": "clickhouse",
      "description": "ClickHouse HTTP port; Railway's healthcheck probes /ping here.",
      "secret": false,
      "strategy": "default",
      "default": "8123"
    },
    {
      "key": "CLICKHOUSE_DB",
      "service": "clickhouse",
      "description": "Database created on first boot.",
      "secret": false,
      "strategy": "default",
      "default": "aisoc"
    },
    {
      "key": "CLICKHOUSE_USER",
      "service": "clickhouse",
      "description": "User created on first boot; replaces the passwordless default user.",
      "secret": false,
      "strategy": "default",
      "default": "aisoc"
    },
    {
      "key": "CLICKHOUSE_PASSWORD",
      "service": "clickhouse",
      "description": "Password of the ClickHouse user (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "RAILWAY_DEPLOYMENT_DRAINING_SECONDS",
      "service": "clickhouse",
      "description": "Allow a clean shutdown on redeploy.",
      "secret": false,
      "strategy": "default",
      "default": "30"
    },
    {
      "key": "CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT",
      "service": "clickhouse",
      "description": "Allow the user to manage access (as upstream).",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "PORT",
      "service": "agents",
      "description": "Port the agents service listens on (dual-stack).",
      "secret": false,
      "strategy": "default",
      "default": "8084"
    },
    {
      "key": "API_URL",
      "service": "agents",
      "description": "Core API (name read by the playbook engine).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "NEO4J_URI",
      "service": "agents",
      "description": "Neo4j entity graph.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "REDIS_URL",
      "service": "agents",
      "description": "Redis (database 4).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "NEO4J_USER",
      "service": "agents",
      "description": "Neo4j user.",
      "secret": false,
      "strategy": "default",
      "default": "neo4j"
    },
    {
      "key": "QDRANT_URL",
      "service": "agents",
      "description": "Qdrant vector store.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "SECRET_KEY",
      "service": "agents",
      "description": "Console token key.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "ENVIRONMENT",
      "service": "agents",
      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "CORE_API_URL",
      "service": "agents",
      "description": "Core API.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "DATABASE_URL",
      "service": "agents",
      "description": "Postgres as aisoc_app.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "OPENAI_MODEL",
      "service": "agents",
      "description": "Model alias for the 'explain this alert' path (resolved by the gateway).",
      "secret": false,
      "strategy": "default",
      "default": "aisoc-summary"
    },
    {
      "key": "REALTIME_URL",
      "service": "agents",
      "description": "Realtime gateway.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_API_URL",
      "service": "agents",
      "description": "Core API (name read by hunts, MCP registry and customer tools).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "METRICS_TOKEN",
      "service": "agents",
      "description": "Bearer required on this service's /metrics.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "OTEL_EXPORTER",
      "service": "agents",
      "description": "Tracing exporter off (no OpenTelemetry collector in this template).",
      "secret": false,
      "strategy": "default",
      "default": "none"
    },
    {
      "key": "AISOC_DEV_MODE",
      "service": "agents",
      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "AISOC_UEBA_URL",
      "service": "agents",
      "description": "UEBA service.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "NEO4J_PASSWORD",
      "service": "agents",
      "description": "Neo4j password.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "OPENAI_API_KEY",
      "service": "agents",
      "description": "Optional hosted-provider key; set on the litellm service.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "API_SERVICE_URL",
      "service": "agents",
      "description": "Core API (name read by the context and tool modules).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "ATTCK_DATA_PATH",
      "service": "agents",
      "description": "Where the full MITRE ATT&CK bundle is cached (on the volume). The image does not ship it.",
      "secret": false,
      "strategy": "default",
      "default": "/data/attack/enterprise-attack.json"
    },
    {
      "key": "CLICKHOUSE_HOST",
      "service": "agents",
      "description": "ClickHouse host (hunting agent).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "CLICKHOUSE_PORT",
      "service": "agents",
      "description": "ClickHouse native port.",
      "secret": false,
      "strategy": "default",
      "default": "9000"
    },
    {
      "key": "CLICKHOUSE_USER",
      "service": "agents",
      "description": "ClickHouse user.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "LLM_GATEWAY_URL",
      "service": "agents",
      "description": "LiteLLM gateway for every aisoc-* model alias.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "ANTHROPIC_API_KEY",
      "service": "agents",
      "description": "Optional hosted-provider key; set on the litellm service.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "FUSION_SERVICE_URL",
      "service": "agents",
      "description": "Fusion service.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "LITELLM_MASTER_KEY",
      "service": "agents",
      "description": "Bearer for the LiteLLM gateway.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "PLAYBOOK_STORE_DIR",
      "service": "agents",
      "description": "Where playbooks created in the console are saved (on the volume; upstream writes them into the container). The service creates the directory.",
      "secret": false,
      "strategy": "default",
      "default": "/data/playbooks"
    },
    {
      "key": "AISOC_SERVICE_TOKEN",
      "service": "agents",
      "description": "Shared service token.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "CLICKHOUSE_DATABASE",
      "service": "agents",
      "description": "ClickHouse database.",
      "secret": false,
      "strategy": "default",
      "default": "aisoc"
    },
    {
      "key": "CLICKHOUSE_PASSWORD",
      "service": "agents",
      "description": "ClickHouse password.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_ATTCK_CDN_FETCH",
      "service": "agents",
      "description": "Download the public MITRE ATT&CK bundle from MITRE's GitHub once and cache it at ATTCK_DATA_PATH (upstream default is off, for air-gapped installs; without it the agents use a small built-in technique list). Set 0 to keep the service from fetching it.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "AISOC_THREATINTEL_URL",
      "service": "agents",
      "description": "Threat-intel service.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "CONNECTORS_SERVICE_URL",
      "service": "agents",
      "description": "Connectors service.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "ENRICHMENT_SERVICE_URL",
      "service": "agents",
      "description": "Enrichment service.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_API_SERVICE_TOKEN",
      "service": "agents",
      "description": "Bearer presented to the API.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "CONSOLE_PUBLIC_BASE_URL",
      "service": "agents",
      "description": "Console origin for links.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "KAFKA_BOOTSTRAP_SERVERS",
      "service": "agents",
      "description": "Kafka-API broker (auto-triage consumes aisoc.alerts.fused).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "REALTIME_INTERNAL_TOKEN",
      "service": "agents",
      "description": "Bearer for realtime's /internal/* (playbook progress).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "KAFKA_TOPIC_ALERTS_FUSED",
      "service": "agents",
      "description": "Topic of fused alerts to triage.",
      "secret": false,
      "strategy": "default",
      "default": "aisoc.alerts.fused"
    },
    {
      "key": "AISOC_AGENTS_SERVICE_TOKEN",
      "service": "agents",
      "description": "This service's own credential (both directions with the API).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_PUBLISHED_BIND_ADDRS",
      "service": "agents",
      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
      "secret": false,
      "strategy": "default",
      "default": "0.0.0.0"
    },
    {
      "key": "AISOC_ACTIONS_SERVICE_TOKEN",
      "service": "agents",
      "description": "Bearer for the actions service (submits proposals; execution still needs approval).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_ALLOW_CLEARTEXT_KAFKA",
      "service": "agents",
      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "AISOC_PLAYBOOK_HTTP_EXECUTE",
      "service": "agents",
      "description": "Playbook HTTP steps are previewed, not sent (upstream default).",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "AISOC_PLAYBOOK_NOTIFY_EXECUTE",
      "service": "agents",
      "description": "Playbook notify steps are previewed, not sent (upstream default).",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "AISOC_PLAYBOOK_VERIFY_EXECUTE",
      "service": "agents",
      "description": "Playbook user-verification prompts are previewed, not sent (upstream default).",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "AISOC_PLAYBOOK_ACTIONS_EXECUTE",
      "service": "agents",
      "description": "Playbook action steps submit to the actions service as previews only (upstream default). Approval rules still apply when enabled.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "AISOC_INVESTIGATION_MAX_SECONDS",
      "service": "agents",
      "description": "Wall-clock budget of one investigation (upstream default).",
      "secret": false,
      "strategy": "default",
      "default": "120"
    },
    {
      "key": "PORT",
      "service": "litellm",
      "description": "Port the gateway listens on; Railway's healthcheck probes it.",
      "secret": false,
      "strategy": "default",
      "default": "4000"
    },
    {
      "key": "AISOC_LLM_API_BASE",
      "service": "litellm",
      "description": "API base for both models (the bundled Ollama). Set it to an empty value when you switch to a hosted provider.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "LITELLM_MASTER_KEY",
      "service": "litellm",
      "description": "Bearer every AiSOC service presents to the gateway (generated; upstream's compose default is a published literal).",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "AISOC_LLM_MODEL_DEEP",
      "service": "litellm",
      "description": "Model behind the investigation and report aliases.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_LLM_MODEL_FAST",
      "service": "litellm",
      "description": "Model behind the high-volume aliases (triage, summary, copilot, NL, hunt, detection). Any LiteLLM model string, e.g. openai/<model> with OPENAI_API_KEY set and AISOC_LLM_API_BASE emptied.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_LITELLM_REQUEST_TIMEOUT",
      "service": "litellm",
      "description": "Seconds the gateway waits for one generation. 300 suits CPU-only inference (upstream default 60, raised per upstream issue #1241 guidance).",
      "secret": false,
      "strategy": "default",
      "default": "300"
    },
    {
      "key": "REDISHOST",
      "service": "Redis",
      "description": "Private network hostname of the Redis service.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "REDISPORT",
      "service": "Redis",
      "description": "Port that Redis listens on.",
      "secret": false,
      "strategy": "default",
      "default": "6379"
    },
    {
      "key": "REDISUSER",
      "service": "Redis",
      "description": "Username for authenticating with Redis.",
      "secret": false,
      "strategy": "default",
      "default": "default"
    },
    {
      "key": "REDIS_URL",
      "service": "Redis",
      "description": "Connection string on the private network.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "REDISPASSWORD",
      "service": "Redis",
      "description": "Alias of REDIS_PASSWORD for clients that expect the unseparated name.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "REDIS_PASSWORD",
      "service": "Redis",
      "description": "Randomly generated password for authenticating with Redis.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "PGDATA",
      "service": "Postgres",
      "description": "Location where the database will be initialized.",
      "secret": false,
      "strategy": "default",
      "default": "/var/lib/postgresql/data/pgdata"
    },
    {
      "key": "PGHOST",
      "service": "Postgres",
      "description": "Railway private domain name.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "PGPORT",
      "service": "Postgres",
      "description": "Port to connect to Postgres.",
      "secret": false,
      "strategy": "default",
      "default": "5432"
    },
    {
      "key": "PGUSER",
      "service": "Postgres",
      "description": "Required variable for the Data panel.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "PGDATABASE",
      "service": "Postgres",
      "description": "Required variable for the Data panel.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "PGPASSWORD",
      "service": "Postgres",
      "description": "Required variable for the Data panel.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "POSTGRES_DB",
      "service": "Postgres",
      "description": "Database created on first boot (AiSOC's name for it).",
      "secret": false,
      "strategy": "default",
      "default": "aisoc"
    },
    {
      "key": "DATABASE_URL",
      "service": "Postgres",
      "description": "Owner connection URL (for the Data panel and admin work).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "POSTGRES_USER",
      "service": "Postgres",
      "description": "Owner/superuser role. Only the API's migration step and UEBA's migrations connect as it; every service runs as aisoc_app.",
      "secret": false,
      "strategy": "default",
      "default": "aisoc"
    },
    {
      "key": "SSL_CERT_DAYS",
      "service": "Postgres",
      "description": "SSL certificate expiry in days.",
      "secret": false,
      "strategy": "default",
      "default": "820"
    },
    {
      "key": "POSTGRES_PASSWORD",
      "service": "Postgres",
      "description": "Password of the owner role (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "RAILWAY_DEPLOYMENT_DRAINING_SECONDS",
      "service": "Postgres",
      "description": "Allow Postgres to cleanly shut down.",
      "secret": false,
      "strategy": "default",
      "default": "60"
    },
    {
      "key": "PORT",
      "service": "api",
      "description": "Port the API listens on (dual-stack); Railway's healthcheck probes it.",
      "secret": false,
      "strategy": "default",
      "default": "8000"
    },
    {
      "key": "LOG_LEVEL",
      "service": "api",
      "description": "Log level.",
      "secret": false,
      "strategy": "default",
      "default": "info"
    },
    {
      "key": "NEO4J_URI",
      "service": "api",
      "description": "Neo4j entity graph (direct Bolt).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "REDIS_URL",
      "service": "api",
      "description": "Redis (database 0) on the private network.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "JWT_SECRET",
      "service": "api",
      "description": "HS256 key for SAML/OIDC session tokens, shared with ingest (generated; required in production).",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "NEO4J_USER",
      "service": "api",
      "description": "Neo4j user.",
      "secret": false,
      "strategy": "default",
      "default": "neo4j"
    },
    {
      "key": "QDRANT_URL",
      "service": "api",
      "description": "Qdrant vector store.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "SECRET_KEY",
      "service": "api",
      "description": "HS256 key for console sessions, shared by every service that verifies a console token (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "ENVIRONMENT",
      "service": "api",
      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "DATABASE_URL",
      "service": "api",
      "description": "Runtime connection as aisoc_app, TLS required (Railway Postgres serves TLS).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "METRICS_TOKEN",
      "service": "api",
      "description": "Bearer required on /metrics outside development (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "OTEL_EXPORTER",
      "service": "api",
      "description": "Tracing exporter off (no OpenTelemetry collector in this template).",
      "secret": false,
      "strategy": "default",
      "default": "none"
    },
    {
      "key": "PASSKEY_RP_ID",
      "service": "api",
      "description": "WebAuthn relying-party id: the console host name. Update after adding a custom domain.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "AISOC_DEV_MODE",
      "service": "api",
      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "NEO4J_PASSWORD",
      "service": "api",
      "description": "Neo4j password.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "OPENAI_API_KEY",
      "service": "api",
      "description": "Optional hosted-provider key (empty = local model only). Set it on the litellm service; this references it.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "CLICKHOUSE_HOST",
      "service": "api",
      "description": "ClickHouse event lake host (native protocol).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "CLICKHOUSE_PORT",
      "service": "api",
      "description": "ClickHouse native port.",
      "secret": false,
      "strategy": "default",
      "default": "9000"
    },
    {
      "key": "CLICKHOUSE_USER",
      "service": "api",
      "description": "ClickHouse user.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "LLM_GATEWAY_URL",
      "service": "api",
      "description": "LiteLLM gateway that resolves every aisoc-* model alias.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_OLLAMA_URL",
      "service": "api",
      "description": "Local Ollama server, probed by the console's 'Deployment & AI' page.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_ADMIN_EMAIL",
      "service": "api",
      "description": "Sign-in e-mail of the first administrator, created by the pre-deploy step on the first deploy. Change it before deploying if you like (must be a syntactically valid address; .local/.test are rejected).",
      "secret": false,
      "strategy": "default",
      "default": "admin@aisoc.internal"
    },
    {
      "key": "AISOC_CONSOLE_URL",
      "service": "api",
      "description": "Console origin printed by the admin bootstrap and used in links.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "INGEST_PUBLIC_URL",
      "service": "api",
      "description": "Public ingest URL shown in the setup wizard's push-URL examples. The sample-data button also posts here. Update after adding a custom domain to ingest.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "REALTIME_BASE_URL",
      "service": "api",
      "description": "Realtime gateway for /internal/* fan-out.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AGENTS_SERVICE_URL",
      "service": "api",
      "description": "Agents service (investigations, playbooks).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_CORS_ORIGINS",
      "service": "api",
      "description": "Allowed browser origin (the console).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "FUSION_SERVICE_URL",
      "service": "api",
      "description": "Fusion service behind the API's authenticated fusion gateway (entity risk, ML status).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "INGEST_SERVICE_URL",
      "service": "api",
      "description": "Ingest service on the private network.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "LITELLM_MASTER_KEY",
      "service": "api",
      "description": "Bearer the API presents to the LiteLLM gateway.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "PASSKEY_RP_ORIGINS",
      "service": "api",
      "description": "WebAuthn origins as a JSON list (the console origin).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "RETRO_HUNT_ENABLED",
      "service": "api",
      "description": "Retro-hunt sweeps over stored history (cost and privacy decision; off as upstream).",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "AISOC_SERVICE_TOKEN",
      "service": "api",
      "description": "Shared bearer for service-to-service calls and connector pushes to ingest (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "CLICKHOUSE_DATABASE",
      "service": "api",
      "description": "ClickHouse database.",
      "secret": false,
      "strategy": "default",
      "default": "aisoc"
    },
    {
      "key": "CLICKHOUSE_PASSWORD",
      "service": "api",
      "description": "ClickHouse password.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_ADMIN_PASSWORD",
      "service": "api",
      "description": "Password of the first administrator (generated, 24 characters). Copy it from here to sign in; changing this variable later does not change an existing account (reset with `python -m app.scripts.bootstrap_admin --reset-password` in the api service shell).",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "AISOC_CREDENTIAL_KEY",
      "service": "api",
      "description": "Fernet key (32 bytes, url-safe base64) that encrypts connector credentials at rest; shared with connectors and actions (generated). Never change it after connectors are saved; rotate with AISOC_CREDENTIAL_KEY_ROTATION_FROM.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "AISOC_APP_DB_PASSWORD",
      "service": "api",
      "description": "Password of the aisoc_app runtime database role (DML only, row-level security applies). The pre-deploy migration step creates the role and sets this password (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "OAUTH_PUBLIC_BASE_URL",
      "service": "api",
      "description": "Base URL advertised as the OAuth redirect for connector sign-in (the console proxies /api/v1/oauth/callback to the API).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "AISOC_ACTIONS_BASE_URL",
      "service": "api",
      "description": "Actions service, where an approved response action is executed.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_RAILWAY_WAIT_FOR",
      "service": "api",
      "description": "Template helper: the pre-deploy step waits (up to 5 minutes in total, non-fatal) until these host:port pairs accept connections, so the API starts with its graph and lake connections available on a first deploy.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "CONNECTORS_SERVICE_URL",
      "service": "api",
      "description": "Connectors service (catalog, test connection, federated search).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "DATABASE_MIGRATION_URL",
      "service": "api",
      "description": "Owner connection used only by the pre-deploy migration runner for DDL, TLS required.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_API_SERVICE_TOKEN",
      "service": "api",
      "description": "Bearer a peer service presents to the API (agent customer tools, lake pivots, hunts) (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "AISOC_MIGRATIONS_STRICT",
      "service": "api",
      "description": "Fail the deploy if any SQL migration fails, instead of booting on a partial schema.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "CONSOLE_PUBLIC_BASE_URL",
      "service": "api",
      "description": "Console origin for share/invite links and OAuth return URLs. Update after adding a custom domain to web.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "KAFKA_BOOTSTRAP_SERVERS",
      "service": "api",
      "description": "Kafka-API broker (Redpanda) on the private network.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "REALTIME_INTERNAL_TOKEN",
      "service": "api",
      "description": "Bearer for realtime's /internal/* fan-out routes (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "THREATINTEL_SERVICE_URL",
      "service": "api",
      "description": "Threat-intel service (serves /api/v1/threat-intel/indicators).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_REALTIME_JWT_SECRET",
      "service": "api",
      "description": "HS256 key for realtime WebSocket/SSE tickets; the API mints, realtime verifies (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "AISOC_AGENTS_SERVICE_TOKEN",
      "service": "api",
      "description": "Bearer between the API and the agents service (triage context, MCP tools, SIEM writeback) (generated).",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "AISOC_PUBLISHED_BIND_ADDRS",
      "service": "api",
      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
      "secret": false,
      "strategy": "default",
      "default": "0.0.0.0"
    },
    {
      "key": "AISOC_ACTIONS_SERVICE_TOKEN",
      "service": "api",
      "description": "Bearer that gates every response action on the actions service (generated). Kept separate from the shared token on purpose.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "AISOC_ALLOW_CLEARTEXT_KAFKA",
      "service": "api",
      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "PORT",
      "service": "ueba",
      "description": "Port the UEBA service listens on (fixed in the image's command).",
      "secret": false,
      "strategy": "default",
      "default": "8004"
    },
    {
      "key": "REDIS_URL",
      "service": "ueba",
      "description": "Redis (database 8).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "ENVIRONMENT",
      "service": "ueba",
      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "CORE_API_URL",
      "service": "ueba",
      "description": "Core API.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "DATABASE_URL",
      "service": "ueba",
      "description": "Postgres as aisoc_app (runtime).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_DEV_MODE",
      "service": "ueba",
      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "CLICKHOUSE_URL",
      "service": "ueba",
      "description": "ClickHouse HTTP URL for baselines.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "OTEL_SDK_DISABLED",
      "service": "ueba",
      "description": "Tracing off (no OpenTelemetry collector in this template).",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "AISOC_SERVICE_TOKEN",
      "service": "ueba",
      "description": "Shared service token.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_APP_DB_PASSWORD",
      "service": "ueba",
      "description": "Runtime role password (as upstream passes it).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "DATABASE_MIGRATION_URL",
      "service": "ueba",
      "description": "Owner connection for this service's own Alembic chain, applied at start.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "KAFKA_BOOTSTRAP_SERVERS",
      "service": "ueba",
      "description": "Kafka-API broker (consumes aisoc.raw_events, publishes ueba.anomalies).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_PUBLISHED_BIND_ADDRS",
      "service": "ueba",
      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
      "secret": false,
      "strategy": "default",
      "default": "0.0.0.0"
    },
    {
      "key": "AISOC_ALLOW_CLEARTEXT_KAFKA",
      "service": "ueba",
      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "PORT",
      "service": "kafka",
      "description": "Redpanda admin API port; Railway's healthcheck probes /v1/status/ready here.",
      "secret": false,
      "strategy": "default",
      "default": "9644"
    },
    {
      "key": "RAILWAY_RUN_UID",
      "service": "kafka",
      "description": "Run as root so Redpanda can write the root-owned Railway volume.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "REDPANDA_MEMORY",
      "service": "kafka",
      "description": "Memory Redpanda may use (Seastar would otherwise size itself to the host). Raise with the service's RAM.",
      "secret": false,
      "strategy": "default",
      "default": "1G"
    },
    {
      "key": "RAILWAY_DEPLOYMENT_DRAINING_SECONDS",
      "service": "kafka",
      "description": "Allow a clean shutdown on redeploy.",
      "secret": false,
      "strategy": "default",
      "default": "30"
    },
    {
      "key": "PORT",
      "service": "realtime",
      "description": "Port the realtime gateway listens on (dual-stack).",
      "secret": false,
      "strategy": "default",
      "default": "8086"
    },
    {
      "key": "REDIS_URL",
      "service": "realtime",
      "description": "Redis (fan-out).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "ENVIRONMENT",
      "service": "realtime",
      "description": "Production posture (refuses a wildcard CORS origin). Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "REDIS_PASSWORD",
      "service": "realtime",
      "description": "Redis password.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_CORS_ORIGINS",
      "service": "realtime",
      "description": "Allowed browser origin (the console).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "KAFKA_BOOTSTRAP_SERVERS",
      "service": "realtime",
      "description": "Kafka-API broker (fused alerts, graph updates).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "REALTIME_INTERNAL_TOKEN",
      "service": "realtime",
      "description": "Guards /internal/*; unset answers 503.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_REALTIME_JWT_SECRET",
      "service": "realtime",
      "description": "Verifies realtime tickets minted by the API; unset rejects every connection.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_ALLOW_CLEARTEXT_KAFKA",
      "service": "realtime",
      "description": "Allow PLAINTEXT Kafka on Railway's encrypted private network.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "ENV",
      "service": "ingest",
      "description": "Read before ENVIRONMENT by ingest's mode check. Production requires JWT_SECRET and disables development shortcuts. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "PORT",
      "service": "ingest",
      "description": "Port Railway routes the public domain and healthcheck to (same as HTTP_PORT).",
      "secret": false,
      "strategy": "default",
      "default": "8080"
    },
    {
      "key": "HTTP_PORT",
      "service": "ingest",
      "description": "Ingest HTTP port.",
      "secret": false,
      "strategy": "default",
      "default": "8080"
    },
    {
      "key": "JWT_SECRET",
      "service": "ingest",
      "description": "Same JWT secret as the API (ingest refuses to start without one in production).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "ENVIRONMENT",
      "service": "ingest",
      "description": "Production posture. Do not change.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "DATABASE_DSN",
      "service": "ingest",
      "description": "Postgres (pgx DSN, TLS required) used to resolve minted push tokens and mount the /v1/inbox/* webhook routes.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "METRICS_PORT",
      "service": "ingest",
      "description": "Separate metrics listener port (private).",
      "secret": false,
      "strategy": "default",
      "default": "9090"
    },
    {
      "key": "AISOC_NEO4J_URI",
      "service": "ingest",
      "description": "Neo4j entity graph.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "ATTCK_DATA_PATH",
      "service": "ingest",
      "description": "Upstream's path for the ATT&CK bundle. The published image does not contain it, so technique enrichment at ingest is off (upstream behaviour, logged as a warning); detections and the agents still map techniques.",
      "secret": false,
      "strategy": "default",
      "default": "/app/data/enterprise-attack.json"
    },
    {
      "key": "AISOC_NEO4J_USER",
      "service": "ingest",
      "description": "Neo4j user.",
      "secret": false,
      "strategy": "default",
      "default": "neo4j"
    },
    {
      "key": "AISOC_CORS_ORIGINS",
      "service": "ingest",
      "description": "Allowed browser origin (the console). Telemetry senders are not browsers and are unaffected.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "AISOC_GRAPH_ENABLED",
      "service": "ingest",
      "description": "Write entities to the Neo4j graph at ingest (upstream 'full' profile).",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "AISOC_SERVICE_TOKEN",
      "service": "ingest",
      "description": "Shared service token; accepted on /v1/ingest only together with an X-Tenant-ID header naming an active tenant.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_NEO4J_PASSWORD",
      "service": "ingest",
      "description": "Neo4j password.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "AISOC_RAILWAY_WAIT_URL",
      "service": "ingest",
      "description": "Template helper: ingest waits up to 3 minutes for Neo4j at start, because its graph writer disables itself for the life of the process if Neo4j is not reachable within 60 s.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "KAFKA_BOOTSTRAP_SERVERS",
      "service": "ingest",
      "description": "Kafka-API broker (Redpanda).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "AISOC_ALLOW_CLEARTEXT_KAFKA",
      "service": "ingest",
      "description": "Allow PLAINTEXT Kafka on Railway's encrypted private network.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    }
  ],
  "deploy": {
    "mcp": {
      "server": "railway",
      "tool": "deploy_template",
      "args": {
        "template_code": "aisoc-v20-ai-security-operations-center"
      }
    },
    "cli": "railway deploy --template aisoc-v20-ai-security-operations-center",
    "api": {
      "method": "POST",
      "path": "/graphql/v2",
      "body": {
        "query": "mutation templateDeploy($input: TemplateDeployV2Input!) { templateDeployV2(input: $input) { projectId workflowId } }",
        "variables": {
          "input": {
            "templateId": "2089fc66-aff5-4d27-95ea-c7e3fb380f8c",
            "serializedConfig": {
              "buckets": {},
              "services": {
                "09c838d7-54b5-5aac-a42c-426dac34e8ad": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/plug.svg",
                  "name": "connectors",
                  "deploy": {
                    "healthcheckPath": "/readyz",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 900
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-connectors:v20.3.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port the connectors service listens on (dual-stack); the API's connectors URL points here.",
                      "defaultValue": "8003"
                    },
                    "REDIS_URL": {
                      "isOptional": true,
                      "description": "Redis (database 6).",
                      "defaultValue": "redis://default:${{Redis.REDIS_PASSWORD}}@${{Redis.RAILWAY_PRIVATE_DOMAIN}}:6379/6"
                    },
                    "SECRET_KEY": {
                      "isOptional": true,
                      "description": "Console token key (the API forwards console tokens here).",
                      "defaultValue": "${{api.SECRET_KEY}}"
                    },
                    "ENVIRONMENT": {
                      "isOptional": true,
                      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
                      "defaultValue": "production"
                    },
                    "CORE_API_URL": {
                      "isOptional": true,
                      "description": "Core API.",
                      "defaultValue": "http://${{api.RAILWAY_PRIVATE_DOMAIN}}:8000"
                    },
                    "DATABASE_URL": {
                      "isOptional": true,
                      "description": "Postgres as aisoc_app.",
                      "defaultValue": "postgresql+asyncpg://aisoc_app:${{api.AISOC_APP_DB_PASSWORD}}@${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:5432/${{Postgres.POSTGRES_DB}}"
                    },
                    "AISOC_DEV_MODE": {
                      "isOptional": true,
                      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
                      "defaultValue": "0"
                    },
                    "INGEST_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Ingest service on the private network (polled events are pushed here).",
                      "defaultValue": "http://${{ingest.RAILWAY_PRIVATE_DOMAIN}}:8080"
                    },
                    "AISOC_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Shared service token (also used to push polled events to ingest).",
                      "defaultValue": "${{api.AISOC_SERVICE_TOKEN}}"
                    },
                    "AISOC_CREDENTIAL_KEY": {
                      "isOptional": true,
                      "description": "Decrypts stored connector credentials at poll time (the scheduler refuses to start without it).",
                      "defaultValue": "${{api.AISOC_CREDENTIAL_KEY}}"
                    },
                    "KAFKA_BOOTSTRAP_SERVERS": {
                      "isOptional": true,
                      "description": "Kafka-API broker.",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092"
                    },
                    "AISOC_PUBLISHED_BIND_ADDRS": {
                      "isOptional": true,
                      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
                      "defaultValue": "0.0.0.0"
                    },
                    "AISOC_ALLOW_CLEARTEXT_KAFKA": {
                      "isOptional": true,
                      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  }
                },
                "0e9f76a9-903e-5b12-aad6-ef5274c5e7d0": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/qdrant.svg",
                  "name": "qdrant",
                  "deploy": {
                    "healthcheckPath": "/readyz",
                    "restartPolicyType": "ON_FAILURE",
                    "healthcheckTimeout": 300,
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "qdrant/qdrant:v1.19.2"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Qdrant HTTP port; Railway's healthcheck probes /readyz here.",
                      "defaultValue": "6333"
                    },
                    "QDRANT__SERVICE__HOST": {
                      "isOptional": true,
                      "description": "Listen on IPv4 and IPv6.",
                      "defaultValue": "::"
                    },
                    "QDRANT__TELEMETRY_DISABLED": {
                      "isOptional": true,
                      "description": "No usage telemetry.",
                      "defaultValue": "true"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "e09b1439-0c93-51a9-b853-a5cf216bff04": {
                      "mountPath": "/qdrant/storage"
                    }
                  }
                },
                "19e91999-2e0e-59b9-be1f-e3f3fc5a1b3a": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/opensearch.svg",
                  "name": "opensearch",
                  "build": {
                    "builder": "DOCKERFILE"
                  },
                  "deploy": {
                    "healthcheckPath": "/_cluster/health",
                    "restartPolicyType": "ON_FAILURE",
                    "healthcheckTimeout": 600,
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "repo": "baranberkay96/aisoc-railway",
                    "branch": "main",
                    "rootDirectory": "/services/opensearch"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "OpenSearch HTTP port; Railway's healthcheck probes it.",
                      "defaultValue": "9200"
                    },
                    "node.name": {
                      "isOptional": true,
                      "description": "Node name (as upstream).",
                      "defaultValue": "aisoc-node-1"
                    },
                    "cluster.name": {
                      "isOptional": true,
                      "description": "Cluster name (as upstream).",
                      "defaultValue": "aisoc-cluster"
                    },
                    "network.host": {
                      "isOptional": true,
                      "description": "Bind address (Java listens dual-stack). 0.0.0.0, not '::' (Rule: settings are passed through unquoted).",
                      "defaultValue": "0.0.0.0"
                    },
                    "discovery.type": {
                      "isOptional": true,
                      "description": "Single-node cluster (also skips the production bootstrap checks).",
                      "defaultValue": "single-node"
                    },
                    "OPENSEARCH_JAVA_OPTS": {
                      "isOptional": true,
                      "description": "JVM heap (as upstream). The process settles near 1 GB RSS with this heap.",
                      "defaultValue": "-Xms512m -Xmx512m"
                    },
                    "bootstrap.memory_lock": {
                      "isOptional": true,
                      "description": "memlock ulimits cannot be raised on Railway (upstream sets true with an unlimited memlock).",
                      "defaultValue": "false"
                    },
                    "node.store.allow_mmap": {
                      "isOptional": true,
                      "description": "Do not depend on the host's vm.max_map_count.",
                      "defaultValue": "false"
                    },
                    "DISABLE_SECURITY_PLUGIN": {
                      "isOptional": true,
                      "description": "Security plugin off as upstream: reachable only on the private network, and the threatintel client connects without credentials.",
                      "defaultValue": "true"
                    },
                    "DISABLE_INSTALL_DEMO_CONFIG": {
                      "isOptional": true,
                      "description": "No demo certificates or users (as upstream).",
                      "defaultValue": "true"
                    },
                    "RAILWAY_DEPLOYMENT_DRAINING_SECONDS": {
                      "isOptional": true,
                      "description": "Allow a clean shutdown on redeploy.",
                      "defaultValue": "30"
                    },
                    "DISABLE_PERFORMANCE_ANALYZER_AGENT_CLI": {
                      "isOptional": true,
                      "description": "Skip the performance-analyzer side process (saves memory; AiSOC does not use it).",
                      "defaultValue": "true"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "65aee52a-d17f-5e22-a519-a5beff57cdc1": {
                      "mountPath": "/usr/share/opensearch/data"
                    }
                  }
                },
                "3b44e9be-60a9-5707-9337-7875d1995e39": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/ollama.svg",
                  "name": "ollama",
                  "build": {
                    "builder": "DOCKERFILE"
                  },
                  "deploy": {
                    "healthcheckPath": "/",
                    "restartPolicyType": "ON_FAILURE",
                    "healthcheckTimeout": 900,
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "repo": "baranberkay96/aisoc-railway",
                    "branch": "main",
                    "rootDirectory": "/services/ollama"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port Ollama listens on; Railway's healthcheck probes it.",
                      "defaultValue": "11434"
                    },
                    "OLLAMA_HOST": {
                      "isOptional": true,
                      "description": "Listen on IPv4 and IPv6.",
                      "defaultValue": "[::]:11434"
                    },
                    "OLLAMA_BASE_MODEL": {
                      "isOptional": true,
                      "description": "Model the CPU-tuned copy is created from.",
                      "defaultValue": "llama3.2:3b-instruct-q4_K_M"
                    },
                    "OLLAMA_KEEP_ALIVE": {
                      "isOptional": true,
                      "description": "Unload the model after 30 idle minutes (frees about 2.5 GB).",
                      "defaultValue": "30m"
                    },
                    "OLLAMA_NUM_THREAD": {
                      "isOptional": true,
                      "description": "Optional override for the inference thread count. Empty = the service's CPU quota, capped at 8.",
                      "defaultValue": ""
                    },
                    "OLLAMA_PULL_MODELS": {
                      "isOptional": true,
                      "description": "Models pulled on first boot (comma-separated). Upstream's pinned CPU model, about 2 GB.",
                      "defaultValue": "llama3.2:3b-instruct-q4_K_M"
                    },
                    "OLLAMA_NUM_PARALLEL": {
                      "isOptional": true,
                      "description": "One generation at a time (CPU); other requests queue.",
                      "defaultValue": "1"
                    },
                    "OLLAMA_DEFAULT_MODEL": {
                      "isOptional": true,
                      "description": "CPU-tuned copy (explicit thread count and context) that the LiteLLM aliases use. Recreated on every start.",
                      "defaultValue": "aisoc-llama3.2:3b"
                    },
                    "OLLAMA_KV_CACHE_TYPE": {
                      "isOptional": true,
                      "description": "8-bit KV cache (about half the memory of f16).",
                      "defaultValue": "q8_0"
                    },
                    "OLLAMA_CONTEXT_LENGTH": {
                      "isOptional": true,
                      "description": "Context window of the tuned model (tokens).",
                      "defaultValue": "8192"
                    },
                    "OLLAMA_FLASH_ATTENTION": {
                      "isOptional": true,
                      "description": "Flash attention on (smaller KV cache).",
                      "defaultValue": "1"
                    },
                    "OLLAMA_MAX_LOADED_MODELS": {
                      "isOptional": true,
                      "description": "Keep at most one model in memory.",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "0d2a16ef-434e-52a4-917e-35c79d161ff2": {
                      "mountPath": "/root/.ollama"
                    }
                  }
                },
                "3fc9f6ac-c4d8-596c-a28c-c011a71ea815": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/neo4j.svg",
                  "name": "neo4j",
                  "deploy": {
                    "healthcheckPath": "/",
                    "restartPolicyType": "ON_FAILURE",
                    "healthcheckTimeout": 600,
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "neo4j:5.15.0-community"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Neo4j HTTP port; Railway's healthcheck probes it.",
                      "defaultValue": "7474"
                    },
                    "NEO4J_AUTH": {
                      "isOptional": true,
                      "description": "Initial credentials, applied on first boot.",
                      "defaultValue": "neo4j/${{AISOC_NEO4J_PASSWORD}}"
                    },
                    "NEO4J_PLUGINS": {
                      "isOptional": true,
                      "description": "APOC core (bundled in the image, as upstream).",
                      "defaultValue": "[\"apoc\"]"
                    },
                    "AISOC_NEO4J_PASSWORD": {
                      "isOptional": true,
                      "description": "Password of the neo4j user (generated).",
                      "defaultValue": "{{AISOC_NEO4J_PASSWORD}}"
                    },
                    "NEO4J_server_memory_heap_max__size": {
                      "isOptional": true,
                      "description": "JVM heap limit.",
                      "defaultValue": "512m"
                    },
                    "NEO4J_server_memory_pagecache_size": {
                      "isOptional": true,
                      "description": "Page cache (Neo4j would otherwise size it from host memory).",
                      "defaultValue": "256m"
                    },
                    "RAILWAY_DEPLOYMENT_DRAINING_SECONDS": {
                      "isOptional": true,
                      "description": "Allow a clean shutdown on redeploy.",
                      "defaultValue": "30"
                    },
                    "NEO4J_server_memory_heap_initial__size": {
                      "isOptional": true,
                      "description": "JVM heap start (upstream values, Neo4j 5 setting names).",
                      "defaultValue": "256m"
                    },
                    "NEO4J_dbms_security_procedures_unrestricted": {
                      "isOptional": true,
                      "description": "Allow APOC procedures (as upstream).",
                      "defaultValue": "apoc.*"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "514fb1b0-2136-52e6-b40c-f80b63fb7094": {
                      "mountPath": "/data"
                    }
                  }
                },
                "46660770-75cf-5b35-ae82-06868b208217": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/globe.svg",
                  "name": "threatintel",
                  "deploy": {
                    "healthcheckPath": "/readyz",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 900
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-threatintel:v20.3.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port the threat-intel service listens on (fixed in the image's command).",
                      "defaultValue": "8005"
                    },
                    "MISP_URL": {
                      "isOptional": true,
                      "description": "Optional: MISP base URL to pull indicators from.",
                      "defaultValue": ""
                    },
                    "NEO4J_URI": {
                      "isOptional": true,
                      "description": "Neo4j entity graph.",
                      "defaultValue": "bolt://${{neo4j.RAILWAY_PRIVATE_DOMAIN}}:7687"
                    },
                    "REDIS_URL": {
                      "isOptional": true,
                      "description": "Redis (database 7).",
                      "defaultValue": "redis://default:${{Redis.REDIS_PASSWORD}}@${{Redis.RAILWAY_PRIVATE_DOMAIN}}:6379/7"
                    },
                    "NEO4J_USER": {
                      "isOptional": true,
                      "description": "Neo4j user.",
                      "defaultValue": "neo4j"
                    },
                    "SECRET_KEY": {
                      "isOptional": true,
                      "description": "Console token key.",
                      "defaultValue": "${{api.SECRET_KEY}}"
                    },
                    "ENVIRONMENT": {
                      "isOptional": true,
                      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
                      "defaultValue": "production"
                    },
                    "OTX_API_KEY": {
                      "isOptional": true,
                      "description": "Optional: AlienVault OTX key for an additional indicator feed. The CISA KEV feed needs no key.",
                      "defaultValue": ""
                    },
                    "QDRANT_HOST": {
                      "isOptional": true,
                      "description": "Qdrant host (this service reads host and port, not a URL).",
                      "defaultValue": "${{qdrant.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "QDRANT_PORT": {
                      "isOptional": true,
                      "description": "Qdrant HTTP port.",
                      "defaultValue": "6333"
                    },
                    "MISP_API_KEY": {
                      "isOptional": true,
                      "description": "Optional: MISP API key.",
                      "defaultValue": ""
                    },
                    "AISOC_DEV_MODE": {
                      "isOptional": true,
                      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
                      "defaultValue": "0"
                    },
                    "NEO4J_PASSWORD": {
                      "isOptional": true,
                      "description": "Neo4j password.",
                      "defaultValue": "${{neo4j.AISOC_NEO4J_PASSWORD}}"
                    },
                    "OPENSEARCH_HOST": {
                      "isOptional": true,
                      "description": "OpenSearch host (IOC and actor full-text index).",
                      "defaultValue": "${{opensearch.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "OPENSEARCH_PORT": {
                      "isOptional": true,
                      "description": "OpenSearch HTTP port.",
                      "defaultValue": "9200"
                    },
                    "AISOC_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Shared service token (the API presents it when it proxies indicators).",
                      "defaultValue": "${{api.AISOC_SERVICE_TOKEN}}"
                    },
                    "KAFKA_BOOTSTRAP_SERVERS": {
                      "isOptional": true,
                      "description": "Kafka-API broker.",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092"
                    },
                    "AISOC_PUBLISHED_BIND_ADDRS": {
                      "isOptional": true,
                      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
                      "defaultValue": "0.0.0.0"
                    },
                    "AISOC_ALLOW_CLEARTEXT_KAFKA": {
                      "isOptional": true,
                      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  }
                },
                "4e2eede1-abe4-5c55-a3b9-337fb4171656": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/monitor.svg",
                  "name": "web",
                  "deploy": {
                    "healthcheckPath": "/api/health",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 600
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-web:v20.3.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port the Next.js console listens on (the image starts it with -p 3000); Railway routes the public domain and its healthcheck here.",
                      "defaultValue": "3000"
                    },
                    "API_URL": {
                      "isOptional": true,
                      "description": "Core API on the private network. The console proxies /api/v1/* and /auth/* here server-side, so the API needs no public domain.",
                      "defaultValue": "http://${{api.RAILWAY_PRIVATE_DOMAIN}}:8000"
                    },
                    "NODE_ENV": {
                      "isOptional": true,
                      "description": "Next.js production mode.",
                      "defaultValue": "production"
                    },
                    "AGENTS_URL": {
                      "isOptional": true,
                      "description": "Agents service on the private network (copilot, hunts, playbooks routes).",
                      "defaultValue": "http://${{agents.RAILWAY_PRIVATE_DOMAIN}}:8084"
                    },
                    "REALTIME_URL": {
                      "isOptional": true,
                      "description": "Realtime gateway on the private network (WebSocket /ws and SSE /sse are proxied here).",
                      "defaultValue": "http://${{realtime.RAILWAY_PRIVATE_DOMAIN}}:8086"
                    },
                    "ENRICHMENT_URL": {
                      "isOptional": true,
                      "description": "Deliberately unreachable. The console has no caller of /api/v1/enrichment/*, and that rewrite would otherwise expose the unauthenticated enrichment service through the public console.",
                      "defaultValue": "http://127.0.0.1:9"
                    },
                    "AISOC_DEMO_MODE": {
                      "isOptional": true,
                      "description": "Demo mode off: the console never shows seeded demo data or the demo banner. Keep false.",
                      "defaultValue": "false"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {
                      "<hasDomain>:3000": {
                        "port": 3000
                      }
                    }
                  }
                },
                "5535ffcd-1a69-53b4-bb4a-cd63df277056": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/git-merge.svg",
                  "name": "fusion",
                  "deploy": {
                    "healthcheckPath": "/readyz",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 900
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-fusion:v20.3.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port fusion listens on (fixed in the image's command); Railway's healthcheck probes it.",
                      "defaultValue": "8003"
                    },
                    "REDIS_URL": {
                      "isOptional": true,
                      "description": "Redis (database 3).",
                      "defaultValue": "redis://default:${{Redis.REDIS_PASSWORD}}@${{Redis.RAILWAY_PRIVATE_DOMAIN}}:6379/3"
                    },
                    "SECRET_KEY": {
                      "isOptional": true,
                      "description": "Console token key (verifies console callers).",
                      "defaultValue": "${{api.SECRET_KEY}}"
                    },
                    "ENVIRONMENT": {
                      "isOptional": true,
                      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
                      "defaultValue": "production"
                    },
                    "DATABASE_URL": {
                      "isOptional": true,
                      "description": "Postgres as aisoc_app (alerts, outbox).",
                      "defaultValue": "postgresql+asyncpg://aisoc_app:${{api.AISOC_APP_DB_PASSWORD}}@${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:5432/${{Postgres.POSTGRES_DB}}"
                    },
                    "AISOC_DEV_MODE": {
                      "isOptional": true,
                      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
                      "defaultValue": "0"
                    },
                    "CLICKHOUSE_HOST": {
                      "isOptional": true,
                      "description": "ClickHouse host.",
                      "defaultValue": "${{clickhouse.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "CLICKHOUSE_PORT": {
                      "isOptional": true,
                      "description": "ClickHouse native port.",
                      "defaultValue": "9000"
                    },
                    "CLICKHOUSE_USER": {
                      "isOptional": true,
                      "description": "ClickHouse user.",
                      "defaultValue": "${{clickhouse.CLICKHOUSE_USER}}"
                    },
                    "AISOC_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Shared service token.",
                      "defaultValue": "${{api.AISOC_SERVICE_TOKEN}}"
                    },
                    "CLICKHOUSE_DATABASE": {
                      "isOptional": true,
                      "description": "ClickHouse database.",
                      "defaultValue": "aisoc"
                    },
                    "CLICKHOUSE_PASSWORD": {
                      "isOptional": true,
                      "description": "ClickHouse password.",
                      "defaultValue": "${{clickhouse.CLICKHOUSE_PASSWORD}}"
                    },
                    "AISOC_LAKE_SPOOL_DIR": {
                      "isOptional": true,
                      "description": "On-disk spool for lake rows not yet written (on the volume, so committed Kafka offsets are never lost on redeploy).",
                      "defaultValue": "/data/lake-spool"
                    },
                    "ENRICHMENT_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Enrichment service for fuse-time IOC enrichment.",
                      "defaultValue": "http://${{enrichment.RAILWAY_PRIVATE_DOMAIN}}:8082"
                    },
                    "KAFKA_BOOTSTRAP_SERVERS": {
                      "isOptional": true,
                      "description": "Kafka-API broker (Redpanda).",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092"
                    },
                    "AISOC_LAKE_WRITER_ENABLED": {
                      "isOptional": true,
                      "description": "Archive every event to the ClickHouse lake (upstream 'full' profile).",
                      "defaultValue": "true"
                    },
                    "AISOC_PUBLISHED_BIND_ADDRS": {
                      "isOptional": true,
                      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
                      "defaultValue": "0.0.0.0"
                    },
                    "AISOC_ALLOW_CLEARTEXT_KAFKA": {
                      "isOptional": true,
                      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "f5fb39e8-8a15-550e-bbd3-43852540ea5c": {
                      "mountPath": "/data"
                    }
                  }
                },
                "5733c4c3-8dbd-57e1-9e53-ac517444b8da": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/search-check.svg",
                  "name": "enrichment",
                  "deploy": {
                    "healthcheckPath": "/health",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 600
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-enrichment:v20.3.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port Railway's healthcheck probes (same as HTTP_PORT).",
                      "defaultValue": "8082"
                    },
                    "HTTP_PORT": {
                      "isOptional": true,
                      "description": "Enrichment HTTP port (private only; this service has no authentication of its own).",
                      "defaultValue": "8082"
                    },
                    "REDIS_URL": {
                      "isOptional": true,
                      "description": "Redis (database 2) for the enrichment cache.",
                      "defaultValue": "redis://default:${{Redis.REDIS_PASSWORD}}@${{Redis.RAILWAY_PRIVATE_DOMAIN}}:6379/2"
                    },
                    "SHODAN_API_KEY": {
                      "isOptional": true,
                      "description": "Optional: Shodan key for host context.",
                      "defaultValue": ""
                    },
                    "ABUSEIPDB_API_KEY": {
                      "isOptional": true,
                      "description": "Optional: AbuseIPDB key for IP reputation.",
                      "defaultValue": ""
                    },
                    "GREYNOISE_API_KEY": {
                      "isOptional": true,
                      "description": "Optional: GreyNoise key for IP context.",
                      "defaultValue": ""
                    },
                    "VIRUSTOTAL_API_KEY": {
                      "isOptional": true,
                      "description": "Optional: VirusTotal key for IOC enrichment.",
                      "defaultValue": ""
                    },
                    "KAFKA_BOOTSTRAP_SERVERS": {
                      "isOptional": true,
                      "description": "Kafka-API broker.",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  }
                },
                "57a891aa-d629-5a25-b140-05387fb813d8": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/zap.svg",
                  "name": "actions",
                  "deploy": {
                    "healthcheckPath": "/readyz",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 900
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-actions:v20.3.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port the actions service listens on (fixed in the image's command).",
                      "defaultValue": "8085"
                    },
                    "REDIS_URL": {
                      "isOptional": true,
                      "description": "Redis (database 5).",
                      "defaultValue": "redis://default:${{Redis.REDIS_PASSWORD}}@${{Redis.RAILWAY_PRIVATE_DOMAIN}}:6379/5"
                    },
                    "SECRET_KEY": {
                      "isOptional": true,
                      "description": "Console token key.",
                      "defaultValue": "${{api.SECRET_KEY}}"
                    },
                    "ENVIRONMENT": {
                      "isOptional": true,
                      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
                      "defaultValue": "production"
                    },
                    "CORE_API_URL": {
                      "isOptional": true,
                      "description": "Core API.",
                      "defaultValue": "http://${{api.RAILWAY_PRIVATE_DOMAIN}}:8000"
                    },
                    "DATABASE_URL": {
                      "isOptional": true,
                      "description": "Postgres as aisoc_app (action records).",
                      "defaultValue": "postgresql+asyncpg://aisoc_app:${{api.AISOC_APP_DB_PASSWORD}}@${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:5432/${{Postgres.POSTGRES_DB}}"
                    },
                    "AISOC_DEV_MODE": {
                      "isOptional": true,
                      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
                      "defaultValue": "0"
                    },
                    "AISOC_API_BASE_URL": {
                      "isOptional": true,
                      "description": "Core API (name read by the actions settings).",
                      "defaultValue": "http://${{api.RAILWAY_PRIVATE_DOMAIN}}:8000"
                    },
                    "AISOC_MATURITY_TIER": {
                      "isOptional": true,
                      "description": "Autonomy tier L1-Notify (upstream default, set explicitly): anything above read-only impact waits for a human approval.",
                      "defaultValue": "L1"
                    },
                    "AISOC_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Shared service token.",
                      "defaultValue": "${{api.AISOC_SERVICE_TOKEN}}"
                    },
                    "AISOC_CREDENTIAL_KEY": {
                      "isOptional": true,
                      "description": "Decrypts vault-encrypted connector credentials in action payloads.",
                      "defaultValue": "${{api.AISOC_CREDENTIAL_KEY}}"
                    },
                    "KAFKA_BOOTSTRAP_SERVERS": {
                      "isOptional": true,
                      "description": "Kafka-API broker.",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092"
                    },
                    "AISOC_PUBLISHED_BIND_ADDRS": {
                      "isOptional": true,
                      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
                      "defaultValue": "0.0.0.0"
                    },
                    "AISOC_ACTIONS_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Bearer required on POST /actions and approve/reject; without it every mutating route answers 503.",
                      "defaultValue": "${{api.AISOC_ACTIONS_SERVICE_TOKEN}}"
                    },
                    "AISOC_ALLOW_CLEARTEXT_KAFKA": {
                      "isOptional": true,
                      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
                      "defaultValue": "1"
                    },
                    "AISOC_ALLOW_HIGH_BLAST_AUTO": {
                      "isOptional": true,
                      "description": "Break-glass for unattended high-blast-radius actions. Keep false.",
                      "defaultValue": "false"
                    },
                    "AISOC_ACTIONS_DEFAULT_DRY_RUN": {
                      "isOptional": true,
                      "description": "Dry-run by default unless an operator opts out (upstream default, set explicitly).",
                      "defaultValue": "true"
                    },
                    "AISOC_ACTIONS_REQUIRE_APPROVER": {
                      "isOptional": true,
                      "description": "Approve/reject calls must carry a resolvable approver identity (separation of duties).",
                      "defaultValue": "true"
                    },
                    "AISOC_FEATURE_MAILBOX_REMEDIATION": {
                      "isOptional": true,
                      "description": "Mailbox purge/restore/block verbs off (upstream default).",
                      "defaultValue": "false"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  }
                },
                "5fefb1c2-422f-5113-b113-88764f1dd7f7": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/clickhouse.svg",
                  "name": "clickhouse",
                  "build": {
                    "builder": "DOCKERFILE"
                  },
                  "deploy": {
                    "healthcheckPath": "/ping",
                    "restartPolicyType": "ON_FAILURE",
                    "healthcheckTimeout": 600,
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "repo": "baranberkay96/aisoc-railway",
                    "branch": "main",
                    "rootDirectory": "/services/clickhouse"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "ClickHouse HTTP port; Railway's healthcheck probes /ping here.",
                      "defaultValue": "8123"
                    },
                    "CLICKHOUSE_DB": {
                      "isOptional": true,
                      "description": "Database created on first boot.",
                      "defaultValue": "aisoc"
                    },
                    "CLICKHOUSE_USER": {
                      "isOptional": true,
                      "description": "User created on first boot; replaces the passwordless default user.",
                      "defaultValue": "aisoc"
                    },
                    "CLICKHOUSE_PASSWORD": {
                      "isOptional": true,
                      "description": "Password of the ClickHouse user (generated).",
                      "defaultValue": "{{CLICKHOUSE_PASSWORD}}"
                    },
                    "RAILWAY_DEPLOYMENT_DRAINING_SECONDS": {
                      "isOptional": true,
                      "description": "Allow a clean shutdown on redeploy.",
                      "defaultValue": "30"
                    },
                    "CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT": {
                      "isOptional": true,
                      "description": "Allow the user to manage access (as upstream).",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "4bf9e2ab-e053-593c-a570-073450f121d3": {
                      "mountPath": "/var/lib/clickhouse"
                    }
                  }
                },
                "7f0036d5-87c2-5c3a-be6f-08f2c460bdfe": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/bot.svg",
                  "name": "agents",
                  "deploy": {
                    "healthcheckPath": "/readyz",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 900
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-agents:v20.3.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port the agents service listens on (dual-stack).",
                      "defaultValue": "8084"
                    },
                    "API_URL": {
                      "isOptional": true,
                      "description": "Core API (name read by the playbook engine).",
                      "defaultValue": "http://${{api.RAILWAY_PRIVATE_DOMAIN}}:8000"
                    },
                    "NEO4J_URI": {
                      "isOptional": true,
                      "description": "Neo4j entity graph.",
                      "defaultValue": "bolt://${{neo4j.RAILWAY_PRIVATE_DOMAIN}}:7687"
                    },
                    "REDIS_URL": {
                      "isOptional": true,
                      "description": "Redis (database 4).",
                      "defaultValue": "redis://default:${{Redis.REDIS_PASSWORD}}@${{Redis.RAILWAY_PRIVATE_DOMAIN}}:6379/4"
                    },
                    "NEO4J_USER": {
                      "isOptional": true,
                      "description": "Neo4j user.",
                      "defaultValue": "neo4j"
                    },
                    "QDRANT_URL": {
                      "isOptional": true,
                      "description": "Qdrant vector store.",
                      "defaultValue": "http://${{qdrant.RAILWAY_PRIVATE_DOMAIN}}:6333"
                    },
                    "SECRET_KEY": {
                      "isOptional": true,
                      "description": "Console token key.",
                      "defaultValue": "${{api.SECRET_KEY}}"
                    },
                    "ENVIRONMENT": {
                      "isOptional": true,
                      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
                      "defaultValue": "production"
                    },
                    "CORE_API_URL": {
                      "isOptional": true,
                      "description": "Core API.",
                      "defaultValue": "http://${{api.RAILWAY_PRIVATE_DOMAIN}}:8000"
                    },
                    "DATABASE_URL": {
                      "isOptional": true,
                      "description": "Postgres as aisoc_app.",
                      "defaultValue": "postgresql+asyncpg://aisoc_app:${{api.AISOC_APP_DB_PASSWORD}}@${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:5432/${{Postgres.POSTGRES_DB}}"
                    },
                    "OPENAI_MODEL": {
                      "isOptional": true,
                      "description": "Model alias for the 'explain this alert' path (resolved by the gateway).",
                      "defaultValue": "aisoc-summary"
                    },
                    "REALTIME_URL": {
                      "isOptional": true,
                      "description": "Realtime gateway.",
                      "defaultValue": "http://${{realtime.RAILWAY_PRIVATE_DOMAIN}}:8086"
                    },
                    "AISOC_API_URL": {
                      "isOptional": true,
                      "description": "Core API (name read by hunts, MCP registry and customer tools).",
                      "defaultValue": "http://${{api.RAILWAY_PRIVATE_DOMAIN}}:8000"
                    },
                    "METRICS_TOKEN": {
                      "isOptional": true,
                      "description": "Bearer required on this service's /metrics.",
                      "defaultValue": "${{api.METRICS_TOKEN}}"
                    },
                    "OTEL_EXPORTER": {
                      "isOptional": true,
                      "description": "Tracing exporter off (no OpenTelemetry collector in this template).",
                      "defaultValue": "none"
                    },
                    "AISOC_DEV_MODE": {
                      "isOptional": true,
                      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
                      "defaultValue": "0"
                    },
                    "AISOC_UEBA_URL": {
                      "isOptional": true,
                      "description": "UEBA service.",
                      "defaultValue": "http://${{ueba.RAILWAY_PRIVATE_DOMAIN}}:8004"
                    },
                    "NEO4J_PASSWORD": {
                      "isOptional": true,
                      "description": "Neo4j password.",
                      "defaultValue": "${{neo4j.AISOC_NEO4J_PASSWORD}}"
                    },
                    "OPENAI_API_KEY": {
                      "isOptional": true,
                      "description": "Optional hosted-provider key; set on the litellm service.",
                      "defaultValue": "${{litellm.OPENAI_API_KEY}}"
                    },
                    "API_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Core API (name read by the context and tool modules).",
                      "defaultValue": "http://${{api.RAILWAY_PRIVATE_DOMAIN}}:8000"
                    },
                    "ATTCK_DATA_PATH": {
                      "isOptional": true,
                      "description": "Where the full MITRE ATT&CK bundle is cached (on the volume). The image does not ship it.",
                      "defaultValue": "/data/attack/enterprise-attack.json"
                    },
                    "CLICKHOUSE_HOST": {
                      "isOptional": true,
                      "description": "ClickHouse host (hunting agent).",
                      "defaultValue": "${{clickhouse.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "CLICKHOUSE_PORT": {
                      "isOptional": true,
                      "description": "ClickHouse native port.",
                      "defaultValue": "9000"
                    },
                    "CLICKHOUSE_USER": {
                      "isOptional": true,
                      "description": "ClickHouse user.",
                      "defaultValue": "${{clickhouse.CLICKHOUSE_USER}}"
                    },
                    "LLM_GATEWAY_URL": {
                      "isOptional": true,
                      "description": "LiteLLM gateway for every aisoc-* model alias.",
                      "defaultValue": "http://${{litellm.RAILWAY_PRIVATE_DOMAIN}}:4000/v1"
                    },
                    "ANTHROPIC_API_KEY": {
                      "isOptional": true,
                      "description": "Optional hosted-provider key; set on the litellm service.",
                      "defaultValue": "${{litellm.ANTHROPIC_API_KEY}}"
                    },
                    "FUSION_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Fusion service.",
                      "defaultValue": "http://${{fusion.RAILWAY_PRIVATE_DOMAIN}}:8003"
                    },
                    "LITELLM_MASTER_KEY": {
                      "isOptional": true,
                      "description": "Bearer for the LiteLLM gateway.",
                      "defaultValue": "${{litellm.LITELLM_MASTER_KEY}}"
                    },
                    "PLAYBOOK_STORE_DIR": {
                      "isOptional": true,
                      "description": "Where playbooks created in the console are saved (on the volume; upstream writes them into the container). The service creates the directory.",
                      "defaultValue": "/data/playbooks"
                    },
                    "AISOC_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Shared service token.",
                      "defaultValue": "${{api.AISOC_SERVICE_TOKEN}}"
                    },
                    "CLICKHOUSE_DATABASE": {
                      "isOptional": true,
                      "description": "ClickHouse database.",
                      "defaultValue": "aisoc"
                    },
                    "CLICKHOUSE_PASSWORD": {
                      "isOptional": true,
                      "description": "ClickHouse password.",
                      "defaultValue": "${{clickhouse.CLICKHOUSE_PASSWORD}}"
                    },
                    "AISOC_ATTCK_CDN_FETCH": {
                      "isOptional": true,
                      "description": "Download the public MITRE ATT&CK bundle from MITRE's GitHub once and cache it at ATTCK_DATA_PATH (upstream default is off, for air-gapped installs; without it the agents use a small built-in technique list). Set 0 to keep the service from fetching it.",
                      "defaultValue": "1"
                    },
                    "AISOC_THREATINTEL_URL": {
                      "isOptional": true,
                      "description": "Threat-intel service.",
                      "defaultValue": "http://${{threatintel.RAILWAY_PRIVATE_DOMAIN}}:8005"
                    },
                    "CONNECTORS_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Connectors service.",
                      "defaultValue": "http://${{connectors.RAILWAY_PRIVATE_DOMAIN}}:8003"
                    },
                    "ENRICHMENT_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Enrichment service.",
                      "defaultValue": "http://${{enrichment.RAILWAY_PRIVATE_DOMAIN}}:8082"
                    },
                    "AISOC_API_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Bearer presented to the API.",
                      "defaultValue": "${{api.AISOC_API_SERVICE_TOKEN}}"
                    },
                    "CONSOLE_PUBLIC_BASE_URL": {
                      "isOptional": true,
                      "description": "Console origin for links.",
                      "defaultValue": "https://${{web.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "KAFKA_BOOTSTRAP_SERVERS": {
                      "isOptional": true,
                      "description": "Kafka-API broker (auto-triage consumes aisoc.alerts.fused).",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092"
                    },
                    "REALTIME_INTERNAL_TOKEN": {
                      "isOptional": true,
                      "description": "Bearer for realtime's /internal/* (playbook progress).",
                      "defaultValue": "${{api.REALTIME_INTERNAL_TOKEN}}"
                    },
                    "KAFKA_TOPIC_ALERTS_FUSED": {
                      "isOptional": true,
                      "description": "Topic of fused alerts to triage.",
                      "defaultValue": "aisoc.alerts.fused"
                    },
                    "AISOC_AGENTS_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "This service's own credential (both directions with the API).",
                      "defaultValue": "${{api.AISOC_AGENTS_SERVICE_TOKEN}}"
                    },
                    "AISOC_PUBLISHED_BIND_ADDRS": {
                      "isOptional": true,
                      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
                      "defaultValue": "0.0.0.0"
                    },
                    "AISOC_ACTIONS_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Bearer for the actions service (submits proposals; execution still needs approval).",
                      "defaultValue": "${{api.AISOC_ACTIONS_SERVICE_TOKEN}}"
                    },
                    "AISOC_ALLOW_CLEARTEXT_KAFKA": {
                      "isOptional": true,
                      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
                      "defaultValue": "1"
                    },
                    "AISOC_PLAYBOOK_HTTP_EXECUTE": {
                      "isOptional": true,
                      "description": "Playbook HTTP steps are previewed, not sent (upstream default).",
                      "defaultValue": "0"
                    },
                    "AISOC_PLAYBOOK_NOTIFY_EXECUTE": {
                      "isOptional": true,
                      "description": "Playbook notify steps are previewed, not sent (upstream default).",
                      "defaultValue": "0"
                    },
                    "AISOC_PLAYBOOK_VERIFY_EXECUTE": {
                      "isOptional": true,
                      "description": "Playbook user-verification prompts are previewed, not sent (upstream default).",
                      "defaultValue": "0"
                    },
                    "AISOC_PLAYBOOK_ACTIONS_EXECUTE": {
                      "isOptional": true,
                      "description": "Playbook action steps submit to the actions service as previews only (upstream default). Approval rules still apply when enabled.",
                      "defaultValue": "0"
                    },
                    "AISOC_INVESTIGATION_MAX_SECONDS": {
                      "isOptional": true,
                      "description": "Wall-clock budget of one investigation (upstream default).",
                      "defaultValue": "120"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "972a1856-0f33-5d8b-b099-843a83806431": {
                      "mountPath": "/data"
                    }
                  }
                },
                "838d7631-6eef-532f-9ae6-49a5dcb878b6": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/brain-circuit.svg",
                  "name": "litellm",
                  "build": {
                    "builder": "DOCKERFILE"
                  },
                  "deploy": {
                    "healthcheckPath": "/health/liveliness",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 600
                  },
                  "source": {
                    "repo": "baranberkay96/aisoc-railway",
                    "branch": "main",
                    "rootDirectory": "/services/litellm"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port the gateway listens on; Railway's healthcheck probes it.",
                      "defaultValue": "4000"
                    },
                    "OPENAI_API_KEY": {
                      "isOptional": true,
                      "description": "Optional: hosted-provider key (OpenAI or any OpenAI-compatible provider). Empty = the bundled local model answers.",
                      "defaultValue": ""
                    },
                    "ANTHROPIC_API_KEY": {
                      "isOptional": true,
                      "description": "Optional: Anthropic key, for anthropic/<model> strings.",
                      "defaultValue": ""
                    },
                    "AISOC_LLM_API_BASE": {
                      "isOptional": true,
                      "description": "API base for both models (the bundled Ollama). Set it to an empty value when you switch to a hosted provider.",
                      "defaultValue": "http://${{ollama.RAILWAY_PRIVATE_DOMAIN}}:11434"
                    },
                    "LITELLM_MASTER_KEY": {
                      "isOptional": true,
                      "description": "Bearer every AiSOC service presents to the gateway (generated; upstream's compose default is a published literal).",
                      "defaultValue": "{{LITELLM_MASTER_KEY}}"
                    },
                    "AISOC_LLM_MODEL_DEEP": {
                      "isOptional": true,
                      "description": "Model behind the investigation and report aliases.",
                      "defaultValue": "ollama_chat/${{ollama.OLLAMA_DEFAULT_MODEL}}"
                    },
                    "AISOC_LLM_MODEL_FAST": {
                      "isOptional": true,
                      "description": "Model behind the high-volume aliases (triage, summary, copilot, NL, hunt, detection). Any LiteLLM model string, e.g. openai/<model> with OPENAI_API_KEY set and AISOC_LLM_API_BASE emptied.",
                      "defaultValue": "ollama_chat/${{ollama.OLLAMA_DEFAULT_MODEL}}"
                    },
                    "AISOC_LITELLM_REQUEST_TIMEOUT": {
                      "isOptional": true,
                      "description": "Seconds the gateway waits for one generation. 300 suits CPU-only inference (upstream default 60, raised per upstream issue #1241 guidance).",
                      "defaultValue": "300"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  }
                },
                "937e29ab-01c4-576c-967c-056ba7a0641e": {
                  "icon": "https://cdn.sanity.io/images/sy1jschh/production/0ce0bfdcfbdbf69662b1116671f97c2dd788b655-157x157.svg",
                  "name": "Redis",
                  "deploy": {
                    "startCommand": "/bin/sh -c \"rm -rf $RAILWAY_VOLUME_MOUNT_PATH/lost+found/ && exec docker-entrypoint.sh redis-server --requirepass $REDIS_PASSWORD --save 60 1 --dir $RAILWAY_VOLUME_MOUNT_PATH\"",
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "redis:8.2.11"
                  },
                  "variables": {
                    "REDISHOST": {
                      "isOptional": true,
                      "description": "Private network hostname of the Redis service.",
                      "defaultValue": "${{RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "REDISPORT": {
                      "isOptional": true,
                      "description": "Port that Redis listens on.",
                      "defaultValue": "6379"
                    },
                    "REDISUSER": {
                      "isOptional": true,
                      "description": "Username for authenticating with Redis.",
                      "defaultValue": "default"
                    },
                    "REDIS_URL": {
                      "isOptional": true,
                      "description": "Connection string on the private network.",
                      "defaultValue": "redis://${{ REDISUSER }}:${{ REDIS_PASSWORD }}@${{ REDISHOST }}:${{ REDISPORT }}"
                    },
                    "REDISPASSWORD": {
                      "isOptional": true,
                      "description": "Alias of REDIS_PASSWORD for clients that expect the unseparated name.",
                      "defaultValue": "${{REDIS_PASSWORD}}"
                    },
                    "REDIS_PASSWORD": {
                      "isOptional": true,
                      "description": "Randomly generated password for authenticating with Redis.",
                      "defaultValue": "{{REDIS_PASSWORD}}"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "d25462d6-ac45-5453-bd88-91459d8484a8": {
                      "mountPath": "/data"
                    }
                  }
                },
                "9f98007f-5820-504f-b7c0-b81d97434fc1": {
                  "icon": "https://devicons.railway.app/i/postgresql.svg",
                  "name": "Postgres",
                  "deploy": {
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "ghcr.io/railwayapp-templates/postgres-ssl:18"
                  },
                  "variables": {
                    "PGDATA": {
                      "isOptional": true,
                      "description": "Location where the database will be initialized.",
                      "defaultValue": "/var/lib/postgresql/data/pgdata"
                    },
                    "PGHOST": {
                      "isOptional": true,
                      "description": "Railway private domain name.",
                      "defaultValue": "${{RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "PGPORT": {
                      "isOptional": true,
                      "description": "Port to connect to Postgres.",
                      "defaultValue": "5432"
                    },
                    "PGUSER": {
                      "isOptional": true,
                      "description": "Required variable for the Data panel.",
                      "defaultValue": "${{ POSTGRES_USER }}"
                    },
                    "PGDATABASE": {
                      "isOptional": true,
                      "description": "Required variable for the Data panel.",
                      "defaultValue": "${{POSTGRES_DB}}"
                    },
                    "PGPASSWORD": {
                      "isOptional": true,
                      "description": "Required variable for the Data panel.",
                      "defaultValue": "${{POSTGRES_PASSWORD}}"
                    },
                    "POSTGRES_DB": {
                      "isOptional": true,
                      "description": "Database created on first boot (AiSOC's name for it).",
                      "defaultValue": "aisoc"
                    },
                    "DATABASE_URL": {
                      "isOptional": true,
                      "description": "Owner connection URL (for the Data panel and admin work).",
                      "defaultValue": "postgresql://${{PGUSER}}:${{POSTGRES_PASSWORD}}@${{RAILWAY_PRIVATE_DOMAIN}}:5432/${{PGDATABASE}}"
                    },
                    "POSTGRES_USER": {
                      "isOptional": true,
                      "description": "Owner/superuser role. Only the API's migration step and UEBA's migrations connect as it; every service runs as aisoc_app.",
                      "defaultValue": "aisoc"
                    },
                    "SSL_CERT_DAYS": {
                      "isOptional": true,
                      "description": "SSL certificate expiry in days.",
                      "defaultValue": "820"
                    },
                    "POSTGRES_PASSWORD": {
                      "isOptional": true,
                      "description": "Password of the owner role (generated).",
                      "defaultValue": "{{POSTGRES_PASSWORD}}"
                    },
                    "RAILWAY_DEPLOYMENT_DRAINING_SECONDS": {
                      "isOptional": true,
                      "description": "Allow Postgres to cleanly shut down.",
                      "defaultValue": "60"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "e70a505d-16b6-5b5a-a10d-86d1088389d4": {
                      "mountPath": "/var/lib/postgresql/data"
                    }
                  }
                },
                "b27e6154-2e5a-5b27-9a74-8cd5093d0bca": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/server.svg",
                  "name": "api",
                  "deploy": {
                    "healthcheckPath": "/readyz",
                    "preDeployCommand": [
                      "/bin/bash -c 'n=0; until python -m app.scripts.run_migrations; do n=$((n+1)); if [ \"$n\" -ge 5 ]; then echo \"[railway] database migrations failed 5 times, giving up\" >&2; exit 1; fi; echo \"[railway] migrations failed (Postgres may still be starting), retry $n/5 in 15s\" >&2; sleep 15; done; python -m app.scripts.bootstrap_admin || exit 1; end=$((SECONDS+300)); for hp in ${AISOC_RAILWAY_WAIT_FOR:-}; do until (exec 3<>\"/dev/tcp/${hp%:*}/${hp##*:}\") 2>/dev/null; do if [ \"$SECONDS\" -ge \"$end\" ]; then echo \"[railway] $hp not reachable yet, continuing\" >&2; break; fi; sleep 5; done; done; exit 0'"
                    ],
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 900,
                    "preDeployTimeoutSeconds": 1800
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-core-api:v20.3.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port the API listens on (dual-stack); Railway's healthcheck probes it.",
                      "defaultValue": "8000"
                    },
                    "LOG_LEVEL": {
                      "isOptional": true,
                      "description": "Log level.",
                      "defaultValue": "info"
                    },
                    "NEO4J_URI": {
                      "isOptional": true,
                      "description": "Neo4j entity graph (direct Bolt).",
                      "defaultValue": "bolt://${{neo4j.RAILWAY_PRIVATE_DOMAIN}}:7687"
                    },
                    "REDIS_URL": {
                      "isOptional": true,
                      "description": "Redis (database 0) on the private network.",
                      "defaultValue": "redis://default:${{Redis.REDIS_PASSWORD}}@${{Redis.RAILWAY_PRIVATE_DOMAIN}}:6379/0"
                    },
                    "JWT_SECRET": {
                      "isOptional": true,
                      "description": "HS256 key for SAML/OIDC session tokens, shared with ingest (generated; required in production).",
                      "defaultValue": "{{JWT_SECRET}}"
                    },
                    "NEO4J_USER": {
                      "isOptional": true,
                      "description": "Neo4j user.",
                      "defaultValue": "neo4j"
                    },
                    "QDRANT_URL": {
                      "isOptional": true,
                      "description": "Qdrant vector store.",
                      "defaultValue": "http://${{qdrant.RAILWAY_PRIVATE_DOMAIN}}:6333"
                    },
                    "SECRET_KEY": {
                      "isOptional": true,
                      "description": "HS256 key for console sessions, shared by every service that verifies a console token (generated).",
                      "defaultValue": "{{SECRET_KEY}}"
                    },
                    "ENVIRONMENT": {
                      "isOptional": true,
                      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
                      "defaultValue": "production"
                    },
                    "DATABASE_URL": {
                      "isOptional": true,
                      "description": "Runtime connection as aisoc_app, TLS required (Railway Postgres serves TLS).",
                      "defaultValue": "postgresql+asyncpg://aisoc_app:${{AISOC_APP_DB_PASSWORD}}@${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:5432/${{Postgres.POSTGRES_DB}}?sslmode=require"
                    },
                    "METRICS_TOKEN": {
                      "isOptional": true,
                      "description": "Bearer required on /metrics outside development (generated).",
                      "defaultValue": "{{METRICS_TOKEN}}"
                    },
                    "OTEL_EXPORTER": {
                      "isOptional": true,
                      "description": "Tracing exporter off (no OpenTelemetry collector in this template).",
                      "defaultValue": "none"
                    },
                    "PASSKEY_RP_ID": {
                      "isOptional": true,
                      "description": "WebAuthn relying-party id: the console host name. Update after adding a custom domain.",
                      "defaultValue": "${{web.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "AISOC_DEV_MODE": {
                      "isOptional": true,
                      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
                      "defaultValue": "0"
                    },
                    "NEO4J_PASSWORD": {
                      "isOptional": true,
                      "description": "Neo4j password.",
                      "defaultValue": "${{neo4j.AISOC_NEO4J_PASSWORD}}"
                    },
                    "OPENAI_API_KEY": {
                      "isOptional": true,
                      "description": "Optional hosted-provider key (empty = local model only). Set it on the litellm service; this references it.",
                      "defaultValue": "${{litellm.OPENAI_API_KEY}}"
                    },
                    "CLICKHOUSE_HOST": {
                      "isOptional": true,
                      "description": "ClickHouse event lake host (native protocol).",
                      "defaultValue": "${{clickhouse.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "CLICKHOUSE_PORT": {
                      "isOptional": true,
                      "description": "ClickHouse native port.",
                      "defaultValue": "9000"
                    },
                    "CLICKHOUSE_USER": {
                      "isOptional": true,
                      "description": "ClickHouse user.",
                      "defaultValue": "${{clickhouse.CLICKHOUSE_USER}}"
                    },
                    "LLM_GATEWAY_URL": {
                      "isOptional": true,
                      "description": "LiteLLM gateway that resolves every aisoc-* model alias.",
                      "defaultValue": "http://${{litellm.RAILWAY_PRIVATE_DOMAIN}}:4000/v1"
                    },
                    "AISOC_OLLAMA_URL": {
                      "isOptional": true,
                      "description": "Local Ollama server, probed by the console's 'Deployment & AI' page.",
                      "defaultValue": "http://${{ollama.RAILWAY_PRIVATE_DOMAIN}}:11434"
                    },
                    "AISOC_ADMIN_EMAIL": {
                      "isOptional": true,
                      "description": "Sign-in e-mail of the first administrator, created by the pre-deploy step on the first deploy. Change it before deploying if you like (must be a syntactically valid address; .local/.test are rejected).",
                      "defaultValue": "admin@aisoc.internal"
                    },
                    "AISOC_CONSOLE_URL": {
                      "isOptional": true,
                      "description": "Console origin printed by the admin bootstrap and used in links.",
                      "defaultValue": "https://${{web.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "INGEST_PUBLIC_URL": {
                      "isOptional": true,
                      "description": "Public ingest URL shown in the setup wizard's push-URL examples. The sample-data button also posts here. Update after adding a custom domain to ingest.",
                      "defaultValue": "https://${{ingest.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "REALTIME_BASE_URL": {
                      "isOptional": true,
                      "description": "Realtime gateway for /internal/* fan-out.",
                      "defaultValue": "http://${{realtime.RAILWAY_PRIVATE_DOMAIN}}:8086"
                    },
                    "AGENTS_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Agents service (investigations, playbooks).",
                      "defaultValue": "http://${{agents.RAILWAY_PRIVATE_DOMAIN}}:8084"
                    },
                    "AISOC_CORS_ORIGINS": {
                      "isOptional": true,
                      "description": "Allowed browser origin (the console).",
                      "defaultValue": "https://${{web.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "FUSION_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Fusion service behind the API's authenticated fusion gateway (entity risk, ML status).",
                      "defaultValue": "http://${{fusion.RAILWAY_PRIVATE_DOMAIN}}:8003"
                    },
                    "INGEST_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Ingest service on the private network.",
                      "defaultValue": "http://${{ingest.RAILWAY_PRIVATE_DOMAIN}}:8080"
                    },
                    "LITELLM_MASTER_KEY": {
                      "isOptional": true,
                      "description": "Bearer the API presents to the LiteLLM gateway.",
                      "defaultValue": "${{litellm.LITELLM_MASTER_KEY}}"
                    },
                    "PASSKEY_RP_ORIGINS": {
                      "isOptional": true,
                      "description": "WebAuthn origins as a JSON list (the console origin).",
                      "defaultValue": "[\"https://${{web.RAILWAY_PUBLIC_DOMAIN}}\"]"
                    },
                    "RETRO_HUNT_ENABLED": {
                      "isOptional": true,
                      "description": "Retro-hunt sweeps over stored history (cost and privacy decision; off as upstream).",
                      "defaultValue": "false"
                    },
                    "AISOC_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Shared bearer for service-to-service calls and connector pushes to ingest (generated).",
                      "defaultValue": "{{AISOC_SERVICE_TOKEN}}"
                    },
                    "CLICKHOUSE_DATABASE": {
                      "isOptional": true,
                      "description": "ClickHouse database.",
                      "defaultValue": "aisoc"
                    },
                    "CLICKHOUSE_PASSWORD": {
                      "isOptional": true,
                      "description": "ClickHouse password.",
                      "defaultValue": "${{clickhouse.CLICKHOUSE_PASSWORD}}"
                    },
                    "AISOC_ADMIN_PASSWORD": {
                      "isOptional": true,
                      "description": "Password of the first administrator (generated, 24 characters). Copy it from here to sign in; changing this variable later does not change an existing account (reset with `python -m app.scripts.bootstrap_admin --reset-password` in the api service shell).",
                      "defaultValue": "{{AISOC_ADMIN_PASSWORD}}"
                    },
                    "AISOC_CREDENTIAL_KEY": {
                      "isOptional": true,
                      "description": "Fernet key (32 bytes, url-safe base64) that encrypts connector credentials at rest; shared with connectors and actions (generated). Never change it after connectors are saved; rotate with AISOC_CREDENTIAL_KEY_ROTATION_FROM.",
                      "defaultValue": "{{AISOC_CREDENTIAL_KEY}}"
                    },
                    "AISOC_APP_DB_PASSWORD": {
                      "isOptional": true,
                      "description": "Password of the aisoc_app runtime database role (DML only, row-level security applies). The pre-deploy migration step creates the role and sets this password (generated).",
                      "defaultValue": "{{AISOC_APP_DB_PASSWORD}}"
                    },
                    "OAUTH_PUBLIC_BASE_URL": {
                      "isOptional": true,
                      "description": "Base URL advertised as the OAuth redirect for connector sign-in (the console proxies /api/v1/oauth/callback to the API).",
                      "defaultValue": "https://${{web.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "AISOC_ACTIONS_BASE_URL": {
                      "isOptional": true,
                      "description": "Actions service, where an approved response action is executed.",
                      "defaultValue": "http://${{actions.RAILWAY_PRIVATE_DOMAIN}}:8085"
                    },
                    "AISOC_RAILWAY_WAIT_FOR": {
                      "isOptional": true,
                      "description": "Template helper: the pre-deploy step waits (up to 5 minutes in total, non-fatal) until these host:port pairs accept connections, so the API starts with its graph and lake connections available on a first deploy.",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092 ${{neo4j.RAILWAY_PRIVATE_DOMAIN}}:7687 ${{clickhouse.RAILWAY_PRIVATE_DOMAIN}}:9000 ${{qdrant.RAILWAY_PRIVATE_DOMAIN}}:6333"
                    },
                    "CONNECTORS_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Connectors service (catalog, test connection, federated search).",
                      "defaultValue": "http://${{connectors.RAILWAY_PRIVATE_DOMAIN}}:8003"
                    },
                    "DATABASE_MIGRATION_URL": {
                      "isOptional": true,
                      "description": "Owner connection used only by the pre-deploy migration runner for DDL, TLS required.",
                      "defaultValue": "postgresql+asyncpg://${{Postgres.POSTGRES_USER}}:${{Postgres.POSTGRES_PASSWORD}}@${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:5432/${{Postgres.POSTGRES_DB}}?sslmode=require"
                    },
                    "AISOC_API_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Bearer a peer service presents to the API (agent customer tools, lake pivots, hunts) (generated).",
                      "defaultValue": "{{AISOC_API_SERVICE_TOKEN}}"
                    },
                    "AISOC_MIGRATIONS_STRICT": {
                      "isOptional": true,
                      "description": "Fail the deploy if any SQL migration fails, instead of booting on a partial schema.",
                      "defaultValue": "1"
                    },
                    "CONSOLE_PUBLIC_BASE_URL": {
                      "isOptional": true,
                      "description": "Console origin for share/invite links and OAuth return URLs. Update after adding a custom domain to web.",
                      "defaultValue": "https://${{web.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "KAFKA_BOOTSTRAP_SERVERS": {
                      "isOptional": true,
                      "description": "Kafka-API broker (Redpanda) on the private network.",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092"
                    },
                    "REALTIME_INTERNAL_TOKEN": {
                      "isOptional": true,
                      "description": "Bearer for realtime's /internal/* fan-out routes (generated).",
                      "defaultValue": "{{REALTIME_INTERNAL_TOKEN}}"
                    },
                    "THREATINTEL_SERVICE_URL": {
                      "isOptional": true,
                      "description": "Threat-intel service (serves /api/v1/threat-intel/indicators).",
                      "defaultValue": "http://${{threatintel.RAILWAY_PRIVATE_DOMAIN}}:8005"
                    },
                    "AISOC_REALTIME_JWT_SECRET": {
                      "isOptional": true,
                      "description": "HS256 key for realtime WebSocket/SSE tickets; the API mints, realtime verifies (generated).",
                      "defaultValue": "{{AISOC_REALTIME_JWT_SECRET}}"
                    },
                    "AISOC_AGENTS_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Bearer between the API and the agents service (triage context, MCP tools, SIEM writeback) (generated).",
                      "defaultValue": "{{AISOC_AGENTS_SERVICE_TOKEN}}"
                    },
                    "AISOC_PUBLISHED_BIND_ADDRS": {
                      "isOptional": true,
                      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
                      "defaultValue": "0.0.0.0"
                    },
                    "AISOC_ACTIONS_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Bearer that gates every response action on the actions service (generated). Kept separate from the shared token on purpose.",
                      "defaultValue": "{{AISOC_ACTIONS_SERVICE_TOKEN}}"
                    },
                    "AISOC_ALLOW_CLEARTEXT_KAFKA": {
                      "isOptional": true,
                      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  }
                },
                "c0521272-a675-5249-b52e-beb920c7d87f": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/user-search.svg",
                  "name": "ueba",
                  "deploy": {
                    "healthcheckPath": "/readyz",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 900
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-ueba:v20.3.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port the UEBA service listens on (fixed in the image's command).",
                      "defaultValue": "8004"
                    },
                    "REDIS_URL": {
                      "isOptional": true,
                      "description": "Redis (database 8).",
                      "defaultValue": "redis://default:${{Redis.REDIS_PASSWORD}}@${{Redis.RAILWAY_PRIVATE_DOMAIN}}:6379/8"
                    },
                    "ENVIRONMENT": {
                      "isOptional": true,
                      "description": "Production posture: the development auth bypass is unreachable and insecure defaults refuse to boot. Do not change.",
                      "defaultValue": "production"
                    },
                    "CORE_API_URL": {
                      "isOptional": true,
                      "description": "Core API.",
                      "defaultValue": "http://${{api.RAILWAY_PRIVATE_DOMAIN}}:8000"
                    },
                    "DATABASE_URL": {
                      "isOptional": true,
                      "description": "Postgres as aisoc_app (runtime).",
                      "defaultValue": "postgresql+asyncpg://aisoc_app:${{api.AISOC_APP_DB_PASSWORD}}@${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:5432/${{Postgres.POSTGRES_DB}}"
                    },
                    "AISOC_DEV_MODE": {
                      "isOptional": true,
                      "description": "Development mode off: routes never fall back to an anonymous or demo principal. Do not change.",
                      "defaultValue": "0"
                    },
                    "CLICKHOUSE_URL": {
                      "isOptional": true,
                      "description": "ClickHouse HTTP URL for baselines.",
                      "defaultValue": "http://${{clickhouse.CLICKHOUSE_USER}}:${{clickhouse.CLICKHOUSE_PASSWORD}}@${{clickhouse.RAILWAY_PRIVATE_DOMAIN}}:8123/aisoc"
                    },
                    "OTEL_SDK_DISABLED": {
                      "isOptional": true,
                      "description": "Tracing off (no OpenTelemetry collector in this template).",
                      "defaultValue": "true"
                    },
                    "AISOC_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Shared service token.",
                      "defaultValue": "${{api.AISOC_SERVICE_TOKEN}}"
                    },
                    "AISOC_APP_DB_PASSWORD": {
                      "isOptional": true,
                      "description": "Runtime role password (as upstream passes it).",
                      "defaultValue": "${{api.AISOC_APP_DB_PASSWORD}}"
                    },
                    "DATABASE_MIGRATION_URL": {
                      "isOptional": true,
                      "description": "Owner connection for this service's own Alembic chain, applied at start.",
                      "defaultValue": "postgresql+asyncpg://${{Postgres.POSTGRES_USER}}:${{Postgres.POSTGRES_PASSWORD}}@${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:5432/${{Postgres.POSTGRES_DB}}"
                    },
                    "KAFKA_BOOTSTRAP_SERVERS": {
                      "isOptional": true,
                      "description": "Kafka-API broker (consumes aisoc.raw_events, publishes ueba.anomalies).",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092"
                    },
                    "AISOC_PUBLISHED_BIND_ADDRS": {
                      "isOptional": true,
                      "description": "Tells the service it is reachable from other hosts, so the developer auth shim stays refused even if dev mode were switched on.",
                      "defaultValue": "0.0.0.0"
                    },
                    "AISOC_ALLOW_CLEARTEXT_KAFKA": {
                      "isOptional": true,
                      "description": "Allow PLAINTEXT Kafka in production: the broker is reachable only over Railway's private network, which Railway encrypts. Set KAFKA_SECURITY_PROTOCOL instead if you bring a TLS broker.",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  }
                },
                "db320880-bca8-5a72-9247-70125c2b4b59": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/kafka-light.svg",
                  "name": "kafka",
                  "deploy": {
                    "startCommand": "/bin/sh -c \"rm -rf /var/lib/redpanda/data/lost+found && exec rpk redpanda start --kafka-addr internal://[::]:9092 --advertise-kafka-addr internal://$RAILWAY_PRIVATE_DOMAIN:9092 --pandaproxy-addr internal://[::]:8082 --advertise-pandaproxy-addr internal://$RAILWAY_PRIVATE_DOMAIN:8082 --schema-registry-addr internal://[::]:8081 --rpc-addr 0.0.0.0:33145 --advertise-rpc-addr $RAILWAY_PRIVATE_DOMAIN:33145 --mode dev-container --smp 1 --memory $REDPANDA_MEMORY --reserve-memory 0M --default-log-level=info\"",
                    "healthcheckPath": "/v1/status/ready",
                    "restartPolicyType": "ON_FAILURE",
                    "healthcheckTimeout": 600,
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "redpandadata/redpanda:v24.3.18"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Redpanda admin API port; Railway's healthcheck probes /v1/status/ready here.",
                      "defaultValue": "9644"
                    },
                    "RAILWAY_RUN_UID": {
                      "isOptional": true,
                      "description": "Run as root so Redpanda can write the root-owned Railway volume.",
                      "defaultValue": "0"
                    },
                    "REDPANDA_MEMORY": {
                      "isOptional": true,
                      "description": "Memory Redpanda may use (Seastar would otherwise size itself to the host). Raise with the service's RAM.",
                      "defaultValue": "1G"
                    },
                    "RAILWAY_DEPLOYMENT_DRAINING_SECONDS": {
                      "isOptional": true,
                      "description": "Allow a clean shutdown on redeploy.",
                      "defaultValue": "30"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "edb2dafb-fe99-5cc8-b35e-2e0f5cbd9af8": {
                      "mountPath": "/var/lib/redpanda/data"
                    }
                  }
                },
                "e4b7b1f2-41a8-566e-8fdd-42b16f40b4a8": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/radio-tower.svg",
                  "name": "realtime",
                  "deploy": {
                    "healthcheckPath": "/healthz",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 600
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-realtime:v20.3.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": true,
                      "description": "Port the realtime gateway listens on (dual-stack).",
                      "defaultValue": "8086"
                    },
                    "REDIS_URL": {
                      "isOptional": true,
                      "description": "Redis (fan-out).",
                      "defaultValue": "redis://default:${{Redis.REDIS_PASSWORD}}@${{Redis.RAILWAY_PRIVATE_DOMAIN}}:6379"
                    },
                    "ENVIRONMENT": {
                      "isOptional": true,
                      "description": "Production posture (refuses a wildcard CORS origin). Do not change.",
                      "defaultValue": "production"
                    },
                    "REDIS_PASSWORD": {
                      "isOptional": true,
                      "description": "Redis password.",
                      "defaultValue": "${{Redis.REDIS_PASSWORD}}"
                    },
                    "AISOC_CORS_ORIGINS": {
                      "isOptional": true,
                      "description": "Allowed browser origin (the console).",
                      "defaultValue": "https://${{web.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "KAFKA_BOOTSTRAP_SERVERS": {
                      "isOptional": true,
                      "description": "Kafka-API broker (fused alerts, graph updates).",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092"
                    },
                    "REALTIME_INTERNAL_TOKEN": {
                      "isOptional": true,
                      "description": "Guards /internal/*; unset answers 503.",
                      "defaultValue": "${{api.REALTIME_INTERNAL_TOKEN}}"
                    },
                    "AISOC_REALTIME_JWT_SECRET": {
                      "isOptional": true,
                      "description": "Verifies realtime tickets minted by the API; unset rejects every connection.",
                      "defaultValue": "${{api.AISOC_REALTIME_JWT_SECRET}}"
                    },
                    "AISOC_ALLOW_CLEARTEXT_KAFKA": {
                      "isOptional": true,
                      "description": "Allow PLAINTEXT Kafka on Railway's encrypted private network.",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  }
                },
                "fb690ff4-9890-57c4-9786-7ff88b1e7e7c": {
                  "icon": "https://cdn.jsdelivr.net/npm/lucide-static@1.59.0/icons/inbox.svg",
                  "name": "ingest",
                  "deploy": {
                    "startCommand": "/bin/sh -c 'i=0; while [ \"$i\" -lt 36 ]; do wget -q -T 3 -O /dev/null \"$AISOC_RAILWAY_WAIT_URL\" && break; i=$((i+1)); sleep 5; done; exec ./ingest-service'",
                    "healthcheckPath": "/readyz",
                    "restartPolicyType": "ALWAYS",
                    "healthcheckTimeout": 900
                  },
                  "source": {
                    "image": "ghcr.io/beenuar/aisoc-ingest:v20.3.0"
                  },
                  "variables": {
                    "ENV": {
                      "isOptional": true,
                      "description": "Read before ENVIRONMENT by ingest's mode check. Production requires JWT_SECRET and disables development shortcuts. Do not change.",
                      "defaultValue": "production"
                    },
                    "PORT": {
                      "isOptional": true,
                      "description": "Port Railway routes the public domain and healthcheck to (same as HTTP_PORT).",
                      "defaultValue": "8080"
                    },
                    "HTTP_PORT": {
                      "isOptional": true,
                      "description": "Ingest HTTP port.",
                      "defaultValue": "8080"
                    },
                    "JWT_SECRET": {
                      "isOptional": true,
                      "description": "Same JWT secret as the API (ingest refuses to start without one in production).",
                      "defaultValue": "${{api.JWT_SECRET}}"
                    },
                    "ENVIRONMENT": {
                      "isOptional": true,
                      "description": "Production posture. Do not change.",
                      "defaultValue": "production"
                    },
                    "DATABASE_DSN": {
                      "isOptional": true,
                      "description": "Postgres (pgx DSN, TLS required) used to resolve minted push tokens and mount the /v1/inbox/* webhook routes.",
                      "defaultValue": "postgres://aisoc_app:${{api.AISOC_APP_DB_PASSWORD}}@${{Postgres.RAILWAY_PRIVATE_DOMAIN}}:5432/${{Postgres.POSTGRES_DB}}?sslmode=require"
                    },
                    "METRICS_PORT": {
                      "isOptional": true,
                      "description": "Separate metrics listener port (private).",
                      "defaultValue": "9090"
                    },
                    "AISOC_NEO4J_URI": {
                      "isOptional": true,
                      "description": "Neo4j entity graph.",
                      "defaultValue": "bolt://${{neo4j.RAILWAY_PRIVATE_DOMAIN}}:7687"
                    },
                    "ATTCK_DATA_PATH": {
                      "isOptional": true,
                      "description": "Upstream's path for the ATT&CK bundle. The published image does not contain it, so technique enrichment at ingest is off (upstream behaviour, logged as a warning); detections and the agents still map techniques.",
                      "defaultValue": "/app/data/enterprise-attack.json"
                    },
                    "AISOC_NEO4J_USER": {
                      "isOptional": true,
                      "description": "Neo4j user.",
                      "defaultValue": "neo4j"
                    },
                    "AISOC_CORS_ORIGINS": {
                      "isOptional": true,
                      "description": "Allowed browser origin (the console). Telemetry senders are not browsers and are unaffected.",
                      "defaultValue": "https://${{web.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "AISOC_GRAPH_ENABLED": {
                      "isOptional": true,
                      "description": "Write entities to the Neo4j graph at ingest (upstream 'full' profile).",
                      "defaultValue": "true"
                    },
                    "AISOC_SERVICE_TOKEN": {
                      "isOptional": true,
                      "description": "Shared service token; accepted on /v1/ingest only together with an X-Tenant-ID header naming an active tenant.",
                      "defaultValue": "${{api.AISOC_SERVICE_TOKEN}}"
                    },
                    "AISOC_NEO4J_PASSWORD": {
                      "isOptional": true,
                      "description": "Neo4j password.",
                      "defaultValue": "${{neo4j.AISOC_NEO4J_PASSWORD}}"
                    },
                    "AISOC_RAILWAY_WAIT_URL": {
                      "isOptional": true,
                      "description": "Template helper: ingest waits up to 3 minutes for Neo4j at start, because its graph writer disables itself for the life of the process if Neo4j is not reachable within 60 s.",
                      "defaultValue": "http://${{neo4j.RAILWAY_PRIVATE_DOMAIN}}:7474/"
                    },
                    "KAFKA_BOOTSTRAP_SERVERS": {
                      "isOptional": true,
                      "description": "Kafka-API broker (Redpanda).",
                      "defaultValue": "${{kafka.RAILWAY_PRIVATE_DOMAIN}}:9092"
                    },
                    "AISOC_ALLOW_CLEARTEXT_KAFKA": {
                      "isOptional": true,
                      "description": "Allow PLAINTEXT Kafka on Railway's encrypted private network.",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {
                      "<hasDomain>:8080": {
                        "port": 8080
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "post_deploy": {
    "healthcheck": {
      "service": "web",
      "method": "GET",
      "path": "/api/health",
      "expect_status": 200
    }
  },
  "resources": {
    "expected_services": 20,
    "needs_volume": true
  },
  "generated_at": "2026-10-30T16:14:57.468Z",
  "generator_version": "0.1.0"
}
