housekeeper: remove dead createJob server function (#127) #27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Deploy Loopany to PRODUCTION — app "loopany-prod", https://loopany.ai. | |
| # | |
| # ⚠️ TEMPORARY: prod currently AUTO-DEPLOYS on every push to main (see the `push` | |
| # trigger below). This is a deliberate, temporary override of the manual-only rule | |
| # — REVERT it (drop the `push` block, leaving `workflow_dispatch` only) once the | |
| # temporary window is over. Rationale for the normal manual-only stance is kept | |
| # below and still applies while this override is active: watch each auto-deploy. | |
| # | |
| # Manual-only rationale (why prod is normally NOT auto-deployed): the | |
| # single-scheduler invariant makes an accidental double-target dangerous, so prod | |
| # is normally NOT auto-deployed on push to main (that's staging's job, deploy.yml → | |
| # loopany-testing) NOR on a `v*` tag (that tag is publish-daemon.yml's npm-publish | |
| # trigger — sharing it would deploy prod on every daemon release). | |
| # | |
| # Migrations apply on container boot: scripts/prestart.mjs runs the postgres-js | |
| # migrator over the DIRECT Supabase URL (DIRECT_DATABASE_URL) whenever | |
| # DATABASE_URL is set, so a bad migration fails the deploy before it serves. | |
| name: Deploy Prod (Fly) | |
| on: | |
| # ⚠️ TEMPORARY auto-deploy on merge to main — remove this whole `push` block to | |
| # restore the manual-only (workflow_dispatch) invariant. Mirrors staging's | |
| # paths-ignore so a doc-only change doesn't needlessly re-migrate/re-deploy prod. | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - 'packages/daemon/**' | |
| - 'README.md' | |
| - 'AGENTS.md' | |
| - 'CLAUDE.md' | |
| - 'CONTRIBUTING.md' | |
| - 'docs/**' | |
| workflow_dispatch: {} | |
| # Distinct group from staging's `fly-deploy` so neither cancels the other's | |
| # in-flight deploy. Never cancel a prod deploy mid-flight. | |
| concurrency: | |
| group: fly-deploy-prod | |
| cancel-in-progress: false | |
| jobs: | |
| deploy: | |
| name: flyctl deploy → loopany-prod | |
| runs-on: ubuntu-latest | |
| # Add required reviewers to this GitHub Environment for a manual promote gate. | |
| environment: production | |
| steps: | |
| # Fail LOUD before flyctl if the prod token is empty. GitHub interpolates a | |
| # missing/rotted secret to the empty string (no error), which flyctl then | |
| # rejects with an opaque "no access token available" — this turns that silent | |
| # secret rot into an actionable failure at the very first step. | |
| - name: Preflight — require FLY_API_TOKEN_PROD | |
| env: | |
| FLY_API_TOKEN_PROD: ${{ secrets.FLY_API_TOKEN_PROD }} | |
| run: | | |
| if [ -z "$FLY_API_TOKEN_PROD" ]; then | |
| echo "::error::FLY_API_TOKEN_PROD is empty or unset — set the prod Fly token in repo secrets before deploying." >&2 | |
| exit 1 | |
| fi | |
| - uses: actions/checkout@v7 | |
| - uses: superfly/flyctl-actions/setup-flyctl@master | |
| - name: Deploy | |
| # --ha=false: exactly ONE machine. Fly's default HA creates a second | |
| # machine, which would run a second in-process scheduler → double-fire. | |
| # GIT_SHA / BUILT_AT bake the pushed commit into the image so /api/health | |
| # can report it (smoke check below asserts it matches). | |
| run: | | |
| flyctl deploy --remote-only --ha=false -c fly.prod.toml -a loopany-prod \ | |
| --build-arg GIT_SHA=${{ github.sha }} \ | |
| --build-arg BUILT_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" | |
| env: | |
| # Separate token from staging's FLY_API_TOKEN (set it in repo secrets). | |
| FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN_PROD }} | |
| # Post-deploy smoke: prove the promoted image actually serves loopany.ai AND | |
| # is the commit we just pushed. A container that boots green but 500s, or a | |
| # promote that silently didn't take, fails the run here instead of passing. | |
| - name: Smoke — /api/health serves the pushed SHA | |
| run: | | |
| expected='${{ github.sha }}' | |
| got='' | |
| for attempt in 1 2 3 4 5; do | |
| if body="$(curl -fsS --max-time 15 https://loopany.ai/api/health)"; then | |
| echo "health: $body" | |
| got="$(printf '%s' "$body" | sed -n 's/.*"sha":"\([^"]*\)".*/\1/p')" | |
| [ "$got" = "$expected" ] && break | |
| echo "health served sha=$got, want $expected (attempt $attempt) — old container likely still draining, retrying in 10s…" >&2 | |
| else | |
| echo "health not ready (attempt $attempt), retrying in 10s…" >&2 | |
| fi | |
| sleep 10 | |
| done | |
| if [ "$got" != "$expected" ]; then | |
| echo "::error::prod /api/health sha=$got but pushed sha=$expected — the deploy did not take (or /api/health never returned 2xx)." >&2 | |
| exit 1 | |
| fi | |
| echo "prod is serving $got ✔" |