{
  "manifest_version": "1.0.0",
  "template": {
    "id": "2a339fa0-68a9-4674-a7e5-84e2582b14f9",
    "slug": "agent-switch",
    "name": "Agent Switch",
    "description": "Team server for AI coding agents in Slack, Teams, Discord or Mattermost",
    "url": "https://railway.com/deploy/agent-switch",
    "upstream": {
      "image": "mattermost/mattermost-team-edition:11.9.0"
    }
  },
  "status": "unvalidated",
  "validated_at": null,
  "success_rate_30d": null,
  "services": [
    {
      "name": "postgres",
      "source": {
        "image": "ghcr.io/railwayapp-templates/postgres-ssl:16"
      },
      "needs_volume": true,
      "volume_mount_path": "/var/lib/postgresql/data",
      "http": false
    },
    {
      "name": "mattermost",
      "source": {
        "image": "mattermost/mattermost-team-edition:11.9.0"
      },
      "needs_volume": true,
      "volume_mount_path": "/mattermost/data",
      "http": true
    },
    {
      "name": "setup",
      "source": {
        "image": "ghcr.io/sandbox-quantum/setup:0.31.0"
      },
      "needs_volume": false,
      "http": false
    },
    {
      "name": "switch",
      "source": {
        "image": "ghcr.io/sandbox-quantum/switch-core:0.31.0"
      },
      "needs_volume": false,
      "http": true
    }
  ],
  "required_inputs": [
    {
      "key": "PGDATA",
      "service": "postgres",
      "description": "Data directory inside the volume.",
      "secret": false,
      "strategy": "default",
      "default": "/var/lib/postgresql/data/pgdata"
    },
    {
      "key": "POSTGRES_DB",
      "service": "postgres",
      "description": "Switch's database. The mattermost one is created by switch at boot.",
      "secret": false,
      "strategy": "default",
      "default": "switch"
    },
    {
      "key": "POSTGRES_USER",
      "service": "postgres",
      "description": "Superuser. Switch runs migrations as it (DB_OWNER_USER), never requests.",
      "secret": false,
      "strategy": "default",
      "default": "postgres"
    },
    {
      "key": "SSL_CERT_DAYS",
      "service": "postgres",
      "description": "Lifetime of the self-signed certificate.",
      "secret": false,
      "strategy": "default",
      "default": "820"
    },
    {
      "key": "POSTGRES_PASSWORD",
      "service": "postgres",
      "description": "Superuser password.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "RAILWAY_DEPLOYMENT_DRAINING_SECONDS",
      "service": "postgres",
      "description": "Time Postgres gets to shut down cleanly.",
      "secret": false,
      "strategy": "default",
      "default": "60"
    },
    {
      "key": "PORT",
      "service": "mattermost",
      "description": "Mattermost port.",
      "secret": false,
      "strategy": "default",
      "default": "8065"
    },
    {
      "key": "RAILWAY_RUN_UID",
      "service": "mattermost",
      "description": "Run as root so Mattermost can write its volume.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "MM_SQLSETTINGS_DATASOURCE",
      "service": "mattermost",
      "description": "Mattermost database (created by switch at boot).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "MM_SQLSETTINGS_DRIVERNAME",
      "service": "mattermost",
      "description": "Database driver.",
      "secret": false,
      "strategy": "default",
      "default": "postgres"
    },
    {
      "key": "MM_SERVICESETTINGS_SITEURL",
      "service": "mattermost",
      "description": "Public Mattermost URL.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "MM_LOGSETTINGS_ENABLESENTRY",
      "service": "mattermost",
      "description": "No crash reporting to Mattermost.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "MM_PLUGINSETTINGS_PLUGINSTATES",
      "service": "mattermost",
      "description": "Copilot and Calls cannot work in this stack, so both are off.",
      "secret": false,
      "strategy": "default",
      "default": "{\"mattermost-ai\":{\"Enable\":false},\"com.mattermost.calls\":{\"Enable\":false}}"
    },
    {
      "key": "MM_LOGSETTINGS_ENABLEDIAGNOSTICS",
      "service": "mattermost",
      "description": "No Mattermost telemetry.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "MM_TEAMSETTINGS_ENABLEOPENSERVER",
      "service": "mattermost",
      "description": "Off: only the admin creates accounts or sends invite links.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "MM_SERVICESETTINGS_ENABLELOCALMODE",
      "service": "mattermost",
      "description": "From upstream compose.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "MM_DISPLAYSETTINGS_CUSTOMURLSCHEMES",
      "service": "mattermost",
      "description": "Makes agents' switchdash:// session links clickable.",
      "secret": false,
      "strategy": "default",
      "default": "switchdash"
    },
    {
      "key": "MM_TEAMSETTINGS_TEAMMATENAMEDISPLAY",
      "service": "mattermost",
      "description": "From upstream compose.",
      "secret": false,
      "strategy": "default",
      "default": "full_name"
    },
    {
      "key": "MM_SERVICESETTINGS_ENABLEONBOARDINGFLOW",
      "service": "mattermost",
      "description": "The onboarding modal blocks the room view embedded in Switch Console.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "MM_PLUGINSETTINGS_ENABLEREMOTEMARKETPLACE",
      "service": "mattermost",
      "description": "From upstream compose.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "MM_SERVICESETTINGS_ENABLESECURITYFIXALERT",
      "service": "mattermost",
      "description": "From upstream compose.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "MM_SERVICESETTINGS_ENABLEUSERACCESSTOKENS",
      "service": "mattermost",
      "description": "The Switch bridge authenticates with access tokens.",
      "secret": true,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "MM_ANNOUNCEMENTSETTINGS_USERNOTICESENABLED",
      "service": "mattermost",
      "description": "From upstream compose.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "MM_ANNOUNCEMENTSETTINGS_ADMINNOTICESENABLED",
      "service": "mattermost",
      "description": "From upstream compose.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "MM_SERVICESETTINGS_ENABLEBOTACCOUNTCREATION",
      "service": "mattermost",
      "description": "Agents post as bot accounts.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "MM_SERVICESETTINGS_ALLOWEDUNTRUSTEDINTERNALCONNECTIONS",
      "service": "mattermost",
      "description": "Lets message buttons call switch over the private network.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "SWITCH_URL",
      "service": "setup",
      "description": "Switch URL the seeder signs in to.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "MATTERMOST_URL",
      "service": "setup",
      "description": "Mattermost URL the seeder uses.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "MATTERMOST_USER",
      "service": "setup",
      "description": "Your Mattermost username, linked to the dashboard admin.",
      "secret": false,
      "strategy": "default",
      "default": "owner"
    },
    {
      "key": "GATEWAY_ADMIN_EMAIL",
      "service": "setup",
      "description": "Dashboard admin email.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "MATTERMOST_TEAM_NAME",
      "service": "setup",
      "description": "Mattermost team the bridge uses.",
      "secret": false,
      "strategy": "default",
      "default": "switch"
    },
    {
      "key": "MATTERMOST_ADMIN_USER",
      "service": "setup",
      "description": "Mattermost system admin username.",
      "secret": false,
      "strategy": "default",
      "default": "admin"
    },
    {
      "key": "MATTERMOST_PUBLIC_URL",
      "service": "setup",
      "description": "Mattermost URL people open.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "MATTERMOST_USER_EMAIL",
      "service": "setup",
      "description": "Email of your Mattermost account.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "GATEWAY_ADMIN_PASSWORD",
      "service": "setup",
      "description": "Dashboard admin password.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "MATTERMOST_USER_PASSWORD",
      "service": "setup",
      "description": "Password of your Mattermost account.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "MATTERMOST_ADMIN_PASSWORD",
      "service": "setup",
      "description": "Mattermost system admin password.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "MATTERMOST_URL_FOR_SWITCH",
      "service": "setup",
      "description": "Mattermost URL switch connects to.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "MATTERMOST_CALLBACK_BASE_URL",
      "service": "setup",
      "description": "Where Mattermost delivers button presses.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "PORT",
      "service": "switch",
      "description": "Port the API and dashboard listen on.",
      "secret": false,
      "strategy": "default",
      "default": "8000"
    },
    {
      "key": "DB_HOST",
      "service": "switch",
      "description": "Postgres host.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "DB_NAME",
      "service": "switch",
      "description": "Switch database.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "DB_PORT",
      "service": "switch",
      "description": "Postgres port.",
      "secret": false,
      "strategy": "default",
      "default": "5432"
    },
    {
      "key": "DB_USER",
      "service": "switch",
      "description": "Unprivileged runtime role every request runs as.",
      "secret": false,
      "strategy": "default",
      "default": "switch_app"
    },
    {
      "key": "DB_PASSWORD",
      "service": "switch",
      "description": "Runtime role password.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "SECRET_KEYS",
      "service": "switch",
      "description": "Master key. Keep a copy: losing it makes stored agent API keys unreadable.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "SERVER_HOST",
      "service": "switch",
      "description": "IPv6 any; the boot shim makes it serve IPv4 too.",
      "secret": false,
      "strategy": "default",
      "default": "::"
    },
    {
      "key": "SERVER_PORT",
      "service": "switch",
      "description": "Switch API and dashboard port.",
      "secret": false,
      "strategy": "default",
      "default": "8000"
    },
    {
      "key": "SWITCH_BOOT",
      "service": "switch",
      "description": "Boot shim: creates the database role, then starts Switch. Do not edit.",
      "secret": false,
      "strategy": "default",
      "default": "\"\"\"switch-core boot shim for Railway, run as `printenv SWITCH_BOOT > /tmp/boot.py && exec python /tmp/boot.py`.\n\nDoes the two things compose does outside the image, then hands over to switch-core:\n1. init-db: create the `switch` and `mattermost` databases and the unprivileged\n   DB_USER runtime role (switch-core refuses to serve as the schema owner).\n   Idempotent; ALTER ROLE re-applies a changed DB_PASSWORD.\n2. Dual-stack listen: Railway's private network is IPv6, its healthchecker IPv4.\n   asyncio forces IPV6_V6ONLY=1 on a `::` socket, so coerce it to 0 and one\n   socket serves both.\n\"\"\"\nimport asyncio\nimport os\nimport runpy\nimport socket\n\nimport asyncpg\n\n_setsockopt = socket.socket.setsockopt\n\n\ndef _dual_stack(self, level, opt, value, *rest):\n    if level == socket.IPPROTO_IPV6 and opt == socket.IPV6_V6ONLY:\n        value = 0\n    return _setsockopt(self, level, opt, value, *rest)\n\n\nsocket.socket.setsockopt = _dual_stack\n\n\nasync def init_db():\n    e = os.environ\n    for _ in range(90):\n        try:\n            conn = await asyncpg.connect(host=e[\"DB_HOST\"], port=int(e[\"DB_PORT\"]), user=e[\"DB_OWNER_USER\"],\n                                         password=e[\"DB_OWNER_PASSWORD\"], database=\"postgres\")\n            break\n        except (OSError, asyncpg.PostgresError) as err:\n            print(f\"switch-boot: waiting for postgres: {err}\", flush=True)\n            await asyncio.sleep(2)\n    else:\n        raise SystemExit(\"switch-boot: postgres never answered\")\n    try:\n        for db in (e[\"DB_NAME\"], \"mattermost\"):\n            if not await conn.fetchval(\"SELECT 1 FROM pg_database WHERE datname = $1\", db):\n                await conn.execute(f'CREATE DATABASE \"{db}\"')\n        role = e[\"DB_USER\"]\n        password = e[\"DB_PASSWORD\"].replace(\"'\", \"''\")\n        if not await conn.fetchval(\"SELECT 1 FROM pg_roles WHERE rolname = $1\", role):\n            await conn.execute(f\"CREATE ROLE \\\"{role}\\\" LOGIN PASSWORD '{password}' \"\n                               \"NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS\")\n        await conn.execute(f\"ALTER ROLE \\\"{role}\\\" PASSWORD '{password}'\")\n    finally:\n        await conn.close()\n    print(\"switch-boot: databases and runtime role ready\", flush=True)\n\n\nasyncio.run(init_db())\nrunpy.run_module(\"switch_core.main\", run_name=\"__main__\")\n"
    },
    {
      "key": "DB_OWNER_USER",
      "service": "switch",
      "description": "Schema owner, used only for migrations and grants.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "ID_SERVER_NAME",
      "service": "switch",
      "description": "Suffix of every account and room id. Cannot be changed after first boot.",
      "secret": false,
      "strategy": "default",
      "default": "switch.local"
    },
    {
      "key": "DB_OWNER_PASSWORD",
      "service": "switch",
      "description": "Schema owner password.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "FRONTEND_BASE_URL",
      "service": "switch",
      "description": "Dashboard URL.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "GATEWAY_PUBLIC_URL",
      "service": "switch",
      "description": "Public API URL (Switch Console server URL).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "GATEWAY_ADMIN_EMAIL",
      "service": "switch",
      "description": "Email you sign in to the Switch dashboard with.",
      "secret": false,
      "strategy": "ask_user"
    },
    {
      "key": "MESSAGING_PUBLIC_URL",
      "service": "switch",
      "description": "URL Slack, Teams, Discord and Telegram call back to.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "FEATURE_FLAGS_ENABLED",
      "service": "switch",
      "description": "Enabled Switch features.",
      "secret": false,
      "strategy": "default",
      "default": "agent_management,agent_chats,switch_buddy,follow_the_conversation,service_connections"
    },
    {
      "key": "GATEWAY_COOKIE_SECURE",
      "service": "switch",
      "description": "Railway serves https, so session cookies are Secure.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "GATEWAY_ADMIN_PASSWORD",
      "service": "switch",
      "description": "Dashboard password for the admin email. Read it here after deploy.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "CONTROLLER_TOKEN_SECRET",
      "service": "switch",
      "description": "Signs the tokens agent controllers use.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "AGENT_REGISTRATION_TOKEN",
      "service": "switch",
      "description": "Admin registration key agents register against.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "COLLABORATION_CALLBACK_HOST",
      "service": "switch",
      "description": "Listener for chat app button callbacks (private network only).",
      "secret": false,
      "strategy": "default",
      "default": "::"
    },
    {
      "key": "OUTBOUND_ALLOWED_PRIVATE_HOSTS",
      "service": "switch",
      "description": "Private hosts Switch may call.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    }
  ],
  "deploy": {
    "mcp": {
      "server": "railway",
      "tool": "deploy_template",
      "args": {
        "template_code": "agent-switch"
      }
    },
    "cli": "railway deploy --template agent-switch",
    "api": {
      "method": "POST",
      "path": "/graphql/v2",
      "body": {
        "query": "mutation templateDeploy($input: TemplateDeployV2Input!) { templateDeployV2(input: $input) { projectId workflowId } }",
        "variables": {
          "input": {
            "templateId": "2a339fa0-68a9-4674-a7e5-84e2582b14f9",
            "serializedConfig": {
              "buckets": {},
              "services": {
                "0f455fda-9ef3-45f9-9732-820fa495533c": {
                  "icon": "https://cdn.simpleicons.org/postgresql",
                  "name": "postgres",
                  "deploy": {
                    "restartPolicyType": "ALWAYS"
                  },
                  "source": {
                    "image": "ghcr.io/railwayapp-templates/postgres-ssl:16"
                  },
                  "variables": {
                    "PGDATA": {
                      "isOptional": false,
                      "description": "Data directory inside the volume.",
                      "defaultValue": "/var/lib/postgresql/data/pgdata"
                    },
                    "POSTGRES_DB": {
                      "isOptional": false,
                      "description": "Switch's database. The mattermost one is created by switch at boot.",
                      "defaultValue": "switch"
                    },
                    "POSTGRES_USER": {
                      "isOptional": false,
                      "description": "Superuser. Switch runs migrations as it (DB_OWNER_USER), never requests.",
                      "defaultValue": "postgres"
                    },
                    "SSL_CERT_DAYS": {
                      "isOptional": false,
                      "description": "Lifetime of the self-signed certificate.",
                      "defaultValue": "820"
                    },
                    "POSTGRES_PASSWORD": {
                      "isOptional": false,
                      "description": "Superuser password.",
                      "defaultValue": "{{POSTGRES_PASSWORD}}"
                    },
                    "RAILWAY_DEPLOYMENT_DRAINING_SECONDS": {
                      "isOptional": false,
                      "description": "Time Postgres gets to shut down cleanly.",
                      "defaultValue": "60"
                    }
                  },
                  "volumeMounts": {
                    "a894a98b-ceeb-4c9b-8308-f8d483f07438": {
                      "mountPath": "/var/lib/postgresql/data"
                    }
                  }
                },
                "1fb83c06-cfa6-46e4-9831-23ee7e2e71d8": {
                  "icon": "https://cdn.simpleicons.org/mattermost",
                  "name": "mattermost",
                  "deploy": {
                    "healthcheckPath": "/api/v4/system/ping",
                    "restartPolicyType": "ALWAYS"
                  },
                  "source": {
                    "image": "mattermost/mattermost-team-edition:11.9.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": false,
                      "description": "Mattermost port.",
                      "defaultValue": "8065"
                    },
                    "RAILWAY_RUN_UID": {
                      "isOptional": false,
                      "description": "Run as root so Mattermost can write its volume.",
                      "defaultValue": "0"
                    },
                    "MM_SQLSETTINGS_DATASOURCE": {
                      "isOptional": false,
                      "description": "Mattermost database (created by switch at boot).",
                      "defaultValue": "postgres://${{postgres.POSTGRES_USER}}:${{postgres.POSTGRES_PASSWORD}}@${{postgres.RAILWAY_PRIVATE_DOMAIN}}:5432/mattermost?sslmode=disable"
                    },
                    "MM_SQLSETTINGS_DRIVERNAME": {
                      "isOptional": false,
                      "description": "Database driver.",
                      "defaultValue": "postgres"
                    },
                    "MM_SERVICESETTINGS_SITEURL": {
                      "isOptional": false,
                      "description": "Public Mattermost URL.",
                      "defaultValue": "https://${{RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "MM_LOGSETTINGS_ENABLESENTRY": {
                      "isOptional": false,
                      "description": "No crash reporting to Mattermost.",
                      "defaultValue": "false"
                    },
                    "MM_PLUGINSETTINGS_PLUGINSTATES": {
                      "isOptional": false,
                      "description": "Copilot and Calls cannot work in this stack, so both are off.",
                      "defaultValue": "{\"mattermost-ai\":{\"Enable\":false},\"com.mattermost.calls\":{\"Enable\":false}}"
                    },
                    "MM_LOGSETTINGS_ENABLEDIAGNOSTICS": {
                      "isOptional": false,
                      "description": "No Mattermost telemetry.",
                      "defaultValue": "false"
                    },
                    "MM_TEAMSETTINGS_ENABLEOPENSERVER": {
                      "isOptional": false,
                      "description": "Off: only the admin creates accounts or sends invite links.",
                      "defaultValue": "false"
                    },
                    "MM_SERVICESETTINGS_ENABLELOCALMODE": {
                      "isOptional": false,
                      "description": "From upstream compose.",
                      "defaultValue": "true"
                    },
                    "MM_DISPLAYSETTINGS_CUSTOMURLSCHEMES": {
                      "isOptional": false,
                      "description": "Makes agents' switchdash:// session links clickable.",
                      "defaultValue": "switchdash"
                    },
                    "MM_TEAMSETTINGS_TEAMMATENAMEDISPLAY": {
                      "isOptional": false,
                      "description": "From upstream compose.",
                      "defaultValue": "full_name"
                    },
                    "MM_SERVICESETTINGS_ENABLEONBOARDINGFLOW": {
                      "isOptional": false,
                      "description": "The onboarding modal blocks the room view embedded in Switch Console.",
                      "defaultValue": "false"
                    },
                    "MM_PLUGINSETTINGS_ENABLEREMOTEMARKETPLACE": {
                      "isOptional": false,
                      "description": "From upstream compose.",
                      "defaultValue": "false"
                    },
                    "MM_SERVICESETTINGS_ENABLESECURITYFIXALERT": {
                      "isOptional": false,
                      "description": "From upstream compose.",
                      "defaultValue": "false"
                    },
                    "MM_SERVICESETTINGS_ENABLEUSERACCESSTOKENS": {
                      "isOptional": false,
                      "description": "The Switch bridge authenticates with access tokens.",
                      "defaultValue": "true"
                    },
                    "MM_ANNOUNCEMENTSETTINGS_USERNOTICESENABLED": {
                      "isOptional": false,
                      "description": "From upstream compose.",
                      "defaultValue": "false"
                    },
                    "MM_ANNOUNCEMENTSETTINGS_ADMINNOTICESENABLED": {
                      "isOptional": false,
                      "description": "From upstream compose.",
                      "defaultValue": "false"
                    },
                    "MM_SERVICESETTINGS_ENABLEBOTACCOUNTCREATION": {
                      "isOptional": false,
                      "description": "Agents post as bot accounts.",
                      "defaultValue": "true"
                    },
                    "MM_SERVICESETTINGS_ALLOWEDUNTRUSTEDINTERNALCONNECTIONS": {
                      "isOptional": false,
                      "description": "Lets message buttons call switch over the private network.",
                      "defaultValue": "${{switch.RAILWAY_PRIVATE_DOMAIN}}"
                    }
                  },
                  "networking": {
                    "serviceDomains": {
                      "<hasDomain>:8065": {
                        "port": 8065
                      }
                    }
                  },
                  "volumeMounts": {
                    "e1910c26-c9f8-4c06-8797-e5e171ae3bc9": {
                      "mountPath": "/mattermost/data"
                    }
                  }
                },
                "610b3e7e-3849-45a8-bfe7-f3b39e07ea84": {
                  "icon": "https://cdn.prod.website-files.com/6a26f9dd113918ff685f6913/6ac805606fcbcbc3e2ac7d8a_switch-webclip.png",
                  "name": "setup",
                  "deploy": {
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "ghcr.io/sandbox-quantum/setup:0.31.0"
                  },
                  "variables": {
                    "SWITCH_URL": {
                      "isOptional": false,
                      "description": "Switch URL the seeder signs in to.",
                      "defaultValue": "https://${{switch.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "MATTERMOST_URL": {
                      "isOptional": false,
                      "description": "Mattermost URL the seeder uses.",
                      "defaultValue": "http://${{mattermost.RAILWAY_PRIVATE_DOMAIN}}:8065"
                    },
                    "MATTERMOST_USER": {
                      "isOptional": false,
                      "description": "Your Mattermost username, linked to the dashboard admin.",
                      "defaultValue": "owner"
                    },
                    "GATEWAY_ADMIN_EMAIL": {
                      "isOptional": false,
                      "description": "Dashboard admin email.",
                      "defaultValue": "${{switch.GATEWAY_ADMIN_EMAIL}}"
                    },
                    "MATTERMOST_TEAM_NAME": {
                      "isOptional": false,
                      "description": "Mattermost team the bridge uses.",
                      "defaultValue": "switch"
                    },
                    "MATTERMOST_ADMIN_USER": {
                      "isOptional": false,
                      "description": "Mattermost system admin username.",
                      "defaultValue": "admin"
                    },
                    "MATTERMOST_PUBLIC_URL": {
                      "isOptional": false,
                      "description": "Mattermost URL people open.",
                      "defaultValue": "https://${{mattermost.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "MATTERMOST_USER_EMAIL": {
                      "isOptional": false,
                      "description": "Email of your Mattermost account.",
                      "defaultValue": "${{switch.GATEWAY_ADMIN_EMAIL}}"
                    },
                    "GATEWAY_ADMIN_PASSWORD": {
                      "isOptional": false,
                      "description": "Dashboard admin password.",
                      "defaultValue": "${{switch.GATEWAY_ADMIN_PASSWORD}}"
                    },
                    "MATTERMOST_USER_PASSWORD": {
                      "isOptional": false,
                      "description": "Password of your Mattermost account.",
                      "defaultValue": "{{MATTERMOST_USER_PASSWORD}}"
                    },
                    "MATTERMOST_ADMIN_PASSWORD": {
                      "isOptional": false,
                      "description": "Mattermost system admin password.",
                      "defaultValue": "{{MATTERMOST_ADMIN_PASSWORD}}"
                    },
                    "MATTERMOST_URL_FOR_SWITCH": {
                      "isOptional": false,
                      "description": "Mattermost URL switch connects to.",
                      "defaultValue": "http://${{mattermost.RAILWAY_PRIVATE_DOMAIN}}:8065"
                    },
                    "MATTERMOST_CALLBACK_BASE_URL": {
                      "isOptional": false,
                      "description": "Where Mattermost delivers button presses.",
                      "defaultValue": "http://${{switch.RAILWAY_PRIVATE_DOMAIN}}:8081"
                    }
                  }
                },
                "c9dcfb64-103d-493d-ae92-61a3b4a7bb22": {
                  "icon": "https://cdn.prod.website-files.com/6a26f9dd113918ff685f6913/6ac805606fcbcbc3e2ac7d8a_switch-webclip.png",
                  "name": "switch",
                  "deploy": {
                    "startCommand": "/bin/sh -c \"printenv SWITCH_BOOT > /tmp/boot.py && exec python /tmp/boot.py\"",
                    "healthcheckPath": "/health/ready",
                    "restartPolicyType": "ALWAYS"
                  },
                  "source": {
                    "image": "ghcr.io/sandbox-quantum/switch-core:0.31.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": false,
                      "description": "Port the API and dashboard listen on.",
                      "defaultValue": "8000"
                    },
                    "DB_HOST": {
                      "isOptional": false,
                      "description": "Postgres host.",
                      "defaultValue": "${{postgres.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "DB_NAME": {
                      "isOptional": false,
                      "description": "Switch database.",
                      "defaultValue": "${{postgres.POSTGRES_DB}}"
                    },
                    "DB_PORT": {
                      "isOptional": false,
                      "description": "Postgres port.",
                      "defaultValue": "5432"
                    },
                    "DB_USER": {
                      "isOptional": false,
                      "description": "Unprivileged runtime role every request runs as.",
                      "defaultValue": "switch_app"
                    },
                    "DB_PASSWORD": {
                      "isOptional": false,
                      "description": "Runtime role password.",
                      "defaultValue": "{{DB_PASSWORD}}"
                    },
                    "SECRET_KEYS": {
                      "isOptional": false,
                      "description": "Master key. Keep a copy: losing it makes stored agent API keys unreadable.",
                      "defaultValue": "{{SECRET_KEYS}}"
                    },
                    "SERVER_HOST": {
                      "isOptional": false,
                      "description": "IPv6 any; the boot shim makes it serve IPv4 too.",
                      "defaultValue": "::"
                    },
                    "SERVER_PORT": {
                      "isOptional": false,
                      "description": "Switch API and dashboard port.",
                      "defaultValue": "8000"
                    },
                    "SWITCH_BOOT": {
                      "isOptional": false,
                      "description": "Boot shim: creates the database role, then starts Switch. Do not edit.",
                      "defaultValue": "\"\"\"switch-core boot shim for Railway, run as `printenv SWITCH_BOOT > /tmp/boot.py && exec python /tmp/boot.py`.\n\nDoes the two things compose does outside the image, then hands over to switch-core:\n1. init-db: create the `switch` and `mattermost` databases and the unprivileged\n   DB_USER runtime role (switch-core refuses to serve as the schema owner).\n   Idempotent; ALTER ROLE re-applies a changed DB_PASSWORD.\n2. Dual-stack listen: Railway's private network is IPv6, its healthchecker IPv4.\n   asyncio forces IPV6_V6ONLY=1 on a `::` socket, so coerce it to 0 and one\n   socket serves both.\n\"\"\"\nimport asyncio\nimport os\nimport runpy\nimport socket\n\nimport asyncpg\n\n_setsockopt = socket.socket.setsockopt\n\n\ndef _dual_stack(self, level, opt, value, *rest):\n    if level == socket.IPPROTO_IPV6 and opt == socket.IPV6_V6ONLY:\n        value = 0\n    return _setsockopt(self, level, opt, value, *rest)\n\n\nsocket.socket.setsockopt = _dual_stack\n\n\nasync def init_db():\n    e = os.environ\n    for _ in range(90):\n        try:\n            conn = await asyncpg.connect(host=e[\"DB_HOST\"], port=int(e[\"DB_PORT\"]), user=e[\"DB_OWNER_USER\"],\n                                         password=e[\"DB_OWNER_PASSWORD\"], database=\"postgres\")\n            break\n        except (OSError, asyncpg.PostgresError) as err:\n            print(f\"switch-boot: waiting for postgres: {err}\", flush=True)\n            await asyncio.sleep(2)\n    else:\n        raise SystemExit(\"switch-boot: postgres never answered\")\n    try:\n        for db in (e[\"DB_NAME\"], \"mattermost\"):\n            if not await conn.fetchval(\"SELECT 1 FROM pg_database WHERE datname = $1\", db):\n                await conn.execute(f'CREATE DATABASE \"{db}\"')\n        role = e[\"DB_USER\"]\n        password = e[\"DB_PASSWORD\"].replace(\"'\", \"''\")\n        if not await conn.fetchval(\"SELECT 1 FROM pg_roles WHERE rolname = $1\", role):\n            await conn.execute(f\"CREATE ROLE \\\"{role}\\\" LOGIN PASSWORD '{password}' \"\n                               \"NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS\")\n        await conn.execute(f\"ALTER ROLE \\\"{role}\\\" PASSWORD '{password}'\")\n    finally:\n        await conn.close()\n    print(\"switch-boot: databases and runtime role ready\", flush=True)\n\n\nasyncio.run(init_db())\nrunpy.run_module(\"switch_core.main\", run_name=\"__main__\")\n"
                    },
                    "DB_OWNER_USER": {
                      "isOptional": false,
                      "description": "Schema owner, used only for migrations and grants.",
                      "defaultValue": "${{postgres.POSTGRES_USER}}"
                    },
                    "ID_SERVER_NAME": {
                      "isOptional": false,
                      "description": "Suffix of every account and room id. Cannot be changed after first boot.",
                      "defaultValue": "switch.local"
                    },
                    "DB_OWNER_PASSWORD": {
                      "isOptional": false,
                      "description": "Schema owner password.",
                      "defaultValue": "${{postgres.POSTGRES_PASSWORD}}"
                    },
                    "FRONTEND_BASE_URL": {
                      "isOptional": false,
                      "description": "Dashboard URL.",
                      "defaultValue": "https://${{RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "GATEWAY_PUBLIC_URL": {
                      "isOptional": false,
                      "description": "Public API URL (Switch Console server URL).",
                      "defaultValue": "https://${{RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "GATEWAY_ADMIN_EMAIL": {
                      "isOptional": false,
                      "description": "Email you sign in to the Switch dashboard with.",
                      "defaultValue": "{{GATEWAY_ADMIN_EMAIL}}"
                    },
                    "MESSAGING_PUBLIC_URL": {
                      "isOptional": false,
                      "description": "URL Slack, Teams, Discord and Telegram call back to.",
                      "defaultValue": "https://${{RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "FEATURE_FLAGS_ENABLED": {
                      "isOptional": false,
                      "description": "Enabled Switch features.",
                      "defaultValue": "agent_management,agent_chats,switch_buddy,follow_the_conversation,service_connections"
                    },
                    "GATEWAY_COOKIE_SECURE": {
                      "isOptional": false,
                      "description": "Railway serves https, so session cookies are Secure.",
                      "defaultValue": "true"
                    },
                    "GATEWAY_ADMIN_PASSWORD": {
                      "isOptional": false,
                      "description": "Dashboard password for the admin email. Read it here after deploy.",
                      "defaultValue": "{{GATEWAY_ADMIN_PASSWORD}}"
                    },
                    "CONTROLLER_TOKEN_SECRET": {
                      "isOptional": false,
                      "description": "Signs the tokens agent controllers use.",
                      "defaultValue": "{{CONTROLLER_TOKEN_SECRET}}"
                    },
                    "AGENT_REGISTRATION_TOKEN": {
                      "isOptional": false,
                      "description": "Admin registration key agents register against.",
                      "defaultValue": "{{AGENT_REGISTRATION_TOKEN}}"
                    },
                    "COLLABORATION_CALLBACK_HOST": {
                      "isOptional": false,
                      "description": "Listener for chat app button callbacks (private network only).",
                      "defaultValue": "::"
                    },
                    "OUTBOUND_ALLOWED_PRIVATE_HOSTS": {
                      "isOptional": false,
                      "description": "Private hosts Switch may call.",
                      "defaultValue": "${{mattermost.RAILWAY_PRIVATE_DOMAIN}}"
                    }
                  },
                  "networking": {
                    "serviceDomains": {
                      "<hasDomain>:8000": {
                        "port": 8000
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "post_deploy": {
    "healthcheck": {
      "service": "mattermost",
      "method": "GET",
      "path": "/api/v4/system/ping",
      "expect_status": 200
    }
  },
  "resources": {
    "expected_services": 4,
    "needs_volume": true
  },
  "generated_at": "2026-10-27T10:14:46.328Z",
  "generator_version": "0.1.0"
}
