Skip to content

housekeeper: remove dead createJob server function (#127) #27

housekeeper: remove dead createJob server function (#127)

housekeeper: remove dead createJob server function (#127) #27

Workflow file for this run

# 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 ✔"