Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ GitHub Actions is the source of truth for CI. The single workflow at `.github/wo
bash scripts/ci.sh
```

The workflow detects changes with `scripts/ci-detect-changes.sh`, runs the Bun/Turbo workspace checks, runs Swift tests/builds for `services/mcp-gateway`, and conditionally deploys changed production services to Railway from `main`.
The workflow detects changes with `scripts/ci-detect-changes.sh`, runs the Bun/Turbo workspace checks, runs Swift tests/builds for `services/mcp-gateway`, deploys tested `dev` commits to Railway Development, and exposes Production only through a protected manual dispatch from `main`.

### Deployment

Expand Down
21 changes: 9 additions & 12 deletions docs/runbooks/supabase-to-railway-dev.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Supabase to Railway development migration

This runbook records the completed development cutover from Fly and Supabase to Railway.
Production remains unchanged.
This runbook records the completed development cutover from Fly, Supabase, and Vercel to Railway.
Production is documented separately and is also Railway-only.

## Deprecation status

Expand All @@ -14,11 +14,8 @@ Verified on 2026-08-10:
- The guarded migration artifacts show identical source and target table inventories and row counts.
- Railway Gateway uses discrete Railway Postgres variables and has no `DATABASE_URL` or
`SUPABASE_DB_URL` binding.
- The Fly app `my-context-protocol-dev-gateway` has been scaled to zero machines. Keep the app and
release history temporarily for rollback, but do not deploy `dev` to it.

The Supabase development project is a rollback snapshot only. Pause it after confirming the
cutover evidence above; do not delete it until the rollback retention decision is explicit.
- The former Fly app, Supabase project, and Vercel deployment were permanently removed after the
Railway cutover was verified. They are not rollback targets.

## Railway development stack

Expand Down Expand Up @@ -125,8 +122,8 @@ with `NEXT_PUBLIC_APP_URL=https://testing.mycontextprotocol.dev` and

## Rollback

Before public dev DNS changes, rollback is simply using the old Fly/Vercel review URLs. After a dev
DNS cutover, use Marque to reverse the Web CNAME to Vercel and replace the Railway Gateway CNAMEs
with the retained Fly A/AAAA values. Restore the old wildcard ACME record if Fly must issue or renew
its development certificate. Keep Supabase as a rollback snapshot; do not allow both databases to
accept review writes and then assume they remain interchangeable.
For application failures, restore the previous successful Railway Gateway and Web deployments,
Gateway first and then Web. Keep additive database migrations in place during an application
rollback. Restore Railway Postgres from a backup or point-in-time recovery only for confirmed data
corruption and only after separate approval. The retired Fly, Supabase, and Vercel resources no
longer exist and must not be referenced as rollback targets.
3 changes: 2 additions & 1 deletion docs/test-plans/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
| MCP gateway | `cd services/mcp-gateway && swift test --enable-swift-testing --disable-xctest --no-parallel -Xswiftc -warnings-as-errors` | `CI / Test and build` |
| MCP gateway release build | `cd services/mcp-gateway && swift build -c release --product App -Xswiftc -warnings-as-errors` | `CI / Test and build` |
| Railway config | `jq empty railway/gateway.json railway/web.json` | `CI / Test and build` |
| Production deployment | `bash scripts/railway-deploy-production.sh main all` | Conditional Railway deploy after CI on `main` |
| Development deployment | `bash scripts/railway-deploy-development.sh dev all "$(git rev-parse HEAD)"` | Automatic Railway deploy after CI on `dev` |
| Production deployment | `bash scripts/railway-deploy-production.sh main all "$(git rev-parse HEAD)"` | Protected manual Railway deploy after CI on `main` |

CI uses `scripts/ci-detect-changes.sh` for path detection and `scripts/ci.sh` as the shared local/GitHub entrypoint.
3 changes: 3 additions & 0 deletions scripts/ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ cd "$ROOT"
export CI="${CI:-true}"
export NEXT_PUBLIC_APP_ENV="${NEXT_PUBLIC_APP_ENV:-test}"

echo "==> Railway deployment authentication"
bash scripts/test-railway-deploy.sh

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
Expand Down
7 changes: 6 additions & 1 deletion scripts/railway-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,14 @@ esac

[[ "$EXPECTED_SHA" =~ ^[0-9a-f]{40}$ ]] || fail "Expected a full lowercase 40-character Git SHA."
[ "$PROJECT_ID" = "$EXPECTED_PROJECT_ID" ] || fail "Refusing to deploy an unexpected Railway project ID."
[ -n "${RAILWAY_TOKEN:-}" ] || fail "Missing environment-scoped RAILWAY_TOKEN."
command -v railway >/dev/null 2>&1 || fail "Install the Railway CLI before deploying."

if [ -n "${GITHUB_ACTIONS:-}" ]; then
[ -n "${RAILWAY_TOKEN:-}" ] || fail "Missing environment-scoped RAILWAY_TOKEN."
else
railway whoami >/dev/null 2>&1 || fail "The local Railway CLI is not authenticated."
fi

cd "$ROOT"
ACTUAL_SHA="$(git rev-parse HEAD)"
[ "$ACTUAL_SHA" = "$EXPECTED_SHA" ] || fail "HEAD does not match the requested deployment SHA."
Expand Down
60 changes: 60 additions & 0 deletions scripts/test-railway-deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
FIXTURE="$(mktemp -d)"
trap 'rm -rf "$FIXTURE"' EXIT

mkdir -p "$FIXTURE/repo/scripts" "$FIXTURE/bin"
cp "$ROOT/scripts/railway-deploy.sh" "$FIXTURE/repo/scripts/railway-deploy.sh"

printf '%s\n' \
'#!/usr/bin/env bash' \
'set -euo pipefail' \
'case "${1:-}" in' \
' whoami) exit "${STUB_WHOAMI_EXIT:-0}" ;;' \
' up) printf "%s\\n" "$*" >> "${STUB_RAILWAY_LOG:?}" ;;' \
' *) exit 2 ;;' \
'esac' > "$FIXTURE/bin/railway"
chmod +x "$FIXTURE/bin/railway" "$FIXTURE/repo/scripts/railway-deploy.sh"

git -C "$FIXTURE/repo" init -q -b dev
git -C "$FIXTURE/repo" config user.email "ci@example.invalid"
git -C "$FIXTURE/repo" config user.name "CI"
git -C "$FIXTURE/repo" config commit.gpgSign false
git -C "$FIXTURE/repo" add scripts/railway-deploy.sh
git -C "$FIXTURE/repo" commit -q -m "fixture"
SHA="$(git -C "$FIXTURE/repo" rev-parse HEAD)"
git -C "$FIXTURE/repo" update-ref refs/remotes/origin/dev "$SHA"

export PATH="$FIXTURE/bin:$PATH"
export STUB_RAILWAY_LOG="$FIXTURE/railway.log"

(
cd "$FIXTURE/repo"
env -u GITHUB_ACTIONS -u RAILWAY_TOKEN \
scripts/railway-deploy.sh dev dev Gateway "$SHA"
)
grep -F -- "--environment dev --service Gateway" "$STUB_RAILWAY_LOG" >/dev/null

if (
cd "$FIXTURE/repo"
GITHUB_ACTIONS=1 GITHUB_REF_NAME=dev GITHUB_SHA="$SHA" \
env -u RAILWAY_TOKEN scripts/railway-deploy.sh dev dev Gateway "$SHA"
) 2>"$FIXTURE/ci-error.log"; then
echo "Expected CI deployment without RAILWAY_TOKEN to fail." >&2
exit 1
fi
grep -F "Missing environment-scoped RAILWAY_TOKEN." "$FIXTURE/ci-error.log" >/dev/null

if (
cd "$FIXTURE/repo"
STUB_WHOAMI_EXIT=1 env -u GITHUB_ACTIONS -u RAILWAY_TOKEN \
scripts/railway-deploy.sh dev dev Gateway "$SHA"
) 2>"$FIXTURE/local-error.log"; then
echo "Expected unauthenticated local deployment to fail." >&2
exit 1
fi
grep -F "The local Railway CLI is not authenticated." "$FIXTURE/local-error.log" >/dev/null

echo "Railway deployment authentication tests passed."
4 changes: 2 additions & 2 deletions services/mcp-gateway/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,11 @@ Verified custom domains remain stored when an account loses Pro, but runtime rou
Deploy production from the repository root:

```bash
bash scripts/railway-deploy-production.sh main Gateway
bash scripts/railway-deploy-production.sh main Gateway "$(git rev-parse HEAD)"
```

GitHub Actions uses the same script on `main` and expects a production-scoped
`RAILWAY_PRODUCTION_TOKEN` repository secret.
`RAILWAY_PRODUCTION_TOKEN` secret in the protected `production` GitHub environment.

### Troubleshooting

Expand Down
Loading