Compare commits

1 Commits

Author SHA1 Message Date
Renovate Bot 3267dcab2a chore(deps): update actions/checkout action to v6 2026-06-01 06:11:54 +00:00
2 changed files with 76 additions and 137 deletions
+65 -121
View File
@@ -22,22 +22,15 @@ name: Build & Deploy played game (reusable)
# Notes on reliability: # Notes on reliability:
# - act keys its remote-action cache by repo URL alone (not by full # - act keys its remote-action cache by repo URL alone (not by full
# ref), so every concurrent game build on home-runner-1 shares the # ref), so every concurrent game build on home-runner-1 shares the
# same /root/.cache/act/<hash> dir for any `uses:` remote action. # same /root/.cache/act/<hash> dir for setup-buildx-action +
# Two jobs racing on that dir corrupt the checked-out tree # build-push-action. Two builds racing on that dir corrupt the
# ("worktree contains unstaged changes") and the next read of # checked-out tree ("worktree contains unstaged changes") and the
# dist/index.js throws → step exits 1. Pinning to patch tags didn't # next read of dist/index.js throws → step exits 1. Pinning to
# help because the cache key ignores the ref. THE FIX, applied to # patch tags didn't help because the cache key ignores the ref.
# BOTH build and deploy jobs: no `uses:` of any third-party action # Fix: do buildx setup + build via inline `docker buildx ...`
# at all — buildx setup/build/login is inline `docker buildx ...`, # shell, so nothing needs to be cloned from GitHub at runtime.
# and the SSH deploy is inline `ssh`/`scp` (the deploy jobs used to # - Registry login is also an inline shell step. One fewer remote-
# use appleboy/ssh-action, which raced exactly like the build # action download = one fewer failure point per job.
# actions and broke concurrent multi-game deploys). The only
# `uses:` left is actions/checkout in the build jobs.
# - Secrets are written to the server as FILES via scp (never piped
# through a step that lets `docker compose up` auto-create a missing
# bind-mount source as a root-owned directory — that poisons
# ~/<id>-secrets/step-provisioner-password.txt and breaks cert-init
# with "is a directory" on every subsequent deploy).
on: on:
workflow_call: workflow_call:
@@ -46,17 +39,12 @@ on:
description: Game slug (must match @played/games-registry's GAMES, e.g. relayer) description: Game slug (must match @played/games-registry's GAMES, e.g. relayer)
type: string type: string
required: true required: true
deploy_host:
description: Box IP to deploy to (passed by deploy-all). Blank = the PLAYED_HOST secret.
type: string
required: false
default: ""
jobs: jobs:
build-api-core: build-api-core:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4.3.1 - uses: actions/checkout@v6.0.2
- name: Set up Docker Buildx - name: Set up Docker Buildx
# Inline replacement for docker/setup-buildx-action. The job # Inline replacement for docker/setup-buildx-action. The job
@@ -126,86 +114,57 @@ jobs:
deploy-api-core: deploy-api-core:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: build-api-core needs: build-api-core
env:
PLAYED_HOST: ${{ inputs.deploy_host || secrets.PLAYED_HOST }}
PLAYED_USER: ${{ secrets.PLAYED_USER }}
PLAYED_PORT: ${{ secrets.PLAYED_PORT }}
GAME_ID: ${{ inputs.game-id }}
steps: steps:
- name: Set up SSH
# Writes the deploy key + tiny ssh/scp wrappers. No remote action
# is used anywhere in this job, so there is nothing to race on
# home-runner-1's shared act remote-action cache.
env:
PLAYED_SSH_KEY: ${{ secrets.PLAYED_SSH_KEY }}
run: |
install -m 700 -d /tmp/ssh
printf '%s\n' "$PLAYED_SSH_KEY" > /tmp/ssh/key
chmod 600 /tmp/ssh/key
cat > /tmp/ssh/run <<EOF
#!/usr/bin/env bash
exec ssh -i /tmp/ssh/key -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/tmp/ssh/known_hosts -o ConnectTimeout=20 -p "${PLAYED_PORT:-22}" "${PLAYED_USER}@${PLAYED_HOST}" "\$@"
EOF
cat > /tmp/ssh/put <<EOF
#!/usr/bin/env bash
exec scp -i /tmp/ssh/key -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/tmp/ssh/known_hosts -o ConnectTimeout=20 -P "${PLAYED_PORT:-22}" "\$1" "${PLAYED_USER}@${PLAYED_HOST}:\$2"
EOF
chmod +x /tmp/ssh/run /tmp/ssh/put
- name: Write secrets to server - name: Write secrets to server
uses: appleboy/ssh-action@v1.2.5
with:
host: ${{ secrets.PLAYED_HOST }}
username: ${{ secrets.PLAYED_USER }}
port: ${{ secrets.PLAYED_PORT }}
key: ${{ secrets.PLAYED_SSH_KEY }}
envs: BUILD_ENV,STEP_CA_PROVISIONER_PASSWORD,GAME_ID
script: |
mkdir -p ~/${GAME_ID}-secrets
printf '%s' "$BUILD_ENV" > ~/${GAME_ID}-secrets/.env
printf '%s' "$STEP_CA_PROVISIONER_PASSWORD" > ~/${GAME_ID}-secrets/step-provisioner-password.txt
chmod 644 ~/${GAME_ID}-secrets/.env
chmod 600 ~/${GAME_ID}-secrets/step-provisioner-password.txt
env: env:
BUILD_ENV: ${{ secrets.BUILD_ENV }} BUILD_ENV: ${{ secrets.BUILD_ENV }}
STEP_CA_PROVISIONER_PASSWORD: ${{ secrets.STEP_CA_PROVISIONER_PASSWORD }} STEP_CA_PROVISIONER_PASSWORD: ${{ secrets.STEP_CA_PROVISIONER_PASSWORD }}
run: | GAME_ID: ${{ inputs.game-id }}
test -n "$BUILD_ENV" || { echo "::error::BUILD_ENV secret is empty — refusing to deploy"; exit 1; }
test -n "$STEP_CA_PROVISIONER_PASSWORD" || { echo "::error::STEP_CA_PROVISIONER_PASSWORD secret is empty"; exit 1; }
printf '%s' "$BUILD_ENV" > /tmp/env.prod
printf '%s' "$STEP_CA_PROVISIONER_PASSWORD" > /tmp/prov.txt
# Pre-create the secrets dir, then push the two secrets as FILES.
# This guarantees the bind-mount sources exist as files before any
# `compose up`, so Docker never auto-creates them as root-owned
# directories (which breaks cert-init + blocks future deploys).
/tmp/ssh/run "mkdir -p ~/${GAME_ID}-secrets"
/tmp/ssh/put /tmp/env.prod "${GAME_ID}-secrets/.env"
/tmp/ssh/put /tmp/prov.txt "${GAME_ID}-secrets/step-provisioner-password.txt"
/tmp/ssh/run "chmod 644 ~/${GAME_ID}-secrets/.env && chmod 600 ~/${GAME_ID}-secrets/step-provisioner-password.txt"
rm -f /tmp/env.prod /tmp/prov.txt
- name: Pull and start api-core - name: Pull and start api-core
env: uses: appleboy/ssh-action@v1.2.5
REGISTRY_USER: ${{ secrets.REGISTRY_USER }} with:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} host: ${{ secrets.PLAYED_HOST }}
run: | username: ${{ secrets.PLAYED_USER }}
# Hand the registry token to the box via a transient 0600 file port: ${{ secrets.PLAYED_PORT }}
# (kept out of process args / the run log); the remote script key: ${{ secrets.PLAYED_SSH_KEY }}
# reads it then deletes it. envs: GAME_ID
printf '%s' "$REGISTRY_TOKEN" > /tmp/reg_token script: |
/tmp/ssh/put /tmp/reg_token ".played_reg_token" docker login git.unom.io -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_TOKEN }}
rm -f /tmp/reg_token cd ~/${GAME_ID}
/tmp/ssh/run "chmod 600 ~/.played_reg_token"
/tmp/ssh/run "GAME_ID='${GAME_ID}' REGISTRY_USER='${REGISTRY_USER}' bash -s" <<'REMOTE'
set -euo pipefail
TOKEN="$(cat ~/.played_reg_token)"; rm -f ~/.played_reg_token
printf '%s' "$TOKEN" | docker login git.unom.io -u "$REGISTRY_USER" --password-stdin
# Clone-if-absent so a freshly provisioned box self-installs the repo.
[ -d "$HOME/$GAME_ID/.git" ] || git clone "https://${REGISTRY_USER}:${TOKEN}@git.unom.io/played/${GAME_ID}.git" "$HOME/$GAME_ID"
cd "$HOME/$GAME_ID"
git fetch origin main git fetch origin main
git reset --hard origin/main git reset --hard origin/main
EF="--env-file $HOME/${GAME_ID}-secrets/.env" docker compose -f compose.production.yml --env-file ~/${GAME_ID}-secrets/.env pull api-core
docker compose -f compose.production.yml $EF pull api-core docker compose -f compose.production.yml --env-file ~/${GAME_ID}-secrets/.env up -d --no-build api-core
docker compose -f compose.production.yml $EF up -d --no-build api-core env:
REMOTE GAME_ID: ${{ inputs.game-id }}
- name: Wait for api-core to be healthy - name: Wait for api-core to be healthy
run: | uses: appleboy/ssh-action@v1.2.5
/tmp/ssh/run "GAME_ID='${GAME_ID}' bash -s" <<'REMOTE' with:
set -euo pipefail host: ${{ secrets.PLAYED_HOST }}
cd "$HOME/$GAME_ID" username: ${{ secrets.PLAYED_USER }}
EF="--env-file $HOME/${GAME_ID}-secrets/.env" port: ${{ secrets.PLAYED_PORT }}
key: ${{ secrets.PLAYED_SSH_KEY }}
envs: GAME_ID
script: |
cd ~/${GAME_ID}
echo "Waiting for api-core to be ready..." echo "Waiting for api-core to be ready..."
for i in $(seq 1 30); do for i in $(seq 1 30); do
if docker compose -f compose.production.yml $EF ps api-core | grep -q "(healthy)"; then if docker compose -f compose.production.yml --env-file ~/${GAME_ID}-secrets/.env ps api-core | grep -q "(healthy)"; then
echo "api-core is healthy" echo "api-core is healthy"
exit 0 exit 0
fi fi
@@ -213,9 +172,9 @@ jobs:
sleep 5 sleep 5
done done
echo "api-core did not become healthy in time" echo "api-core did not become healthy in time"
docker compose -f compose.production.yml $EF logs api-core --tail 80 || true
exit 1 exit 1
REMOTE env:
GAME_ID: ${{ inputs.game-id }}
build-web: build-web:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -224,7 +183,7 @@ jobs:
# with build-api-core + deploy-api-core. deploy-web below still gates on # with build-api-core + deploy-api-core. deploy-web below still gates on
# deploy-api-core so the runtime sequence is preserved. # deploy-api-core so the runtime sequence is preserved.
steps: steps:
- uses: actions/checkout@v4.3.1 - uses: actions/checkout@v6.0.2
- name: Set up Docker Buildx - name: Set up Docker Buildx
# See the build-api-core step of the same name above for why # See the build-api-core step of the same name above for why
@@ -290,34 +249,19 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
# Both gates: image must be built AND api-core must be live before web flips. # Both gates: image must be built AND api-core must be live before web flips.
needs: [build-web, deploy-api-core] needs: [build-web, deploy-api-core]
env:
PLAYED_HOST: ${{ inputs.deploy_host || secrets.PLAYED_HOST }}
PLAYED_USER: ${{ secrets.PLAYED_USER }}
PLAYED_PORT: ${{ secrets.PLAYED_PORT }}
GAME_ID: ${{ inputs.game-id }}
steps: steps:
- name: Set up SSH
env:
PLAYED_SSH_KEY: ${{ secrets.PLAYED_SSH_KEY }}
run: |
install -m 700 -d /tmp/ssh
printf '%s\n' "$PLAYED_SSH_KEY" > /tmp/ssh/key
chmod 600 /tmp/ssh/key
cat > /tmp/ssh/run <<EOF
#!/usr/bin/env bash
exec ssh -i /tmp/ssh/key -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/tmp/ssh/known_hosts -o ConnectTimeout=20 -p "${PLAYED_PORT:-22}" "${PLAYED_USER}@${PLAYED_HOST}" "\$@"
EOF
chmod +x /tmp/ssh/run
- name: Pull and start web - name: Pull and start web
# No registry login needed here: the box's docker auth (baked at uses: appleboy/ssh-action@v1.2.5
# provision + refreshed by deploy-api-core) already covers `pull`, with:
# and the repo was already cloned by deploy-api-core. host: ${{ secrets.PLAYED_HOST }}
run: | username: ${{ secrets.PLAYED_USER }}
/tmp/ssh/run "GAME_ID='${GAME_ID}' bash -s" <<'REMOTE' port: ${{ secrets.PLAYED_PORT }}
set -euo pipefail key: ${{ secrets.PLAYED_SSH_KEY }}
cd "$HOME/$GAME_ID" envs: GAME_ID
EF="--env-file $HOME/${GAME_ID}-secrets/.env" script: |
docker compose -f compose.production.yml $EF pull web docker login git.unom.io -u ${{ secrets.REGISTRY_USER }} -p ${{ secrets.REGISTRY_TOKEN }}
docker compose -f compose.production.yml $EF up -d --no-build web cd ~/${GAME_ID}
REMOTE docker compose -f compose.production.yml --env-file ~/${GAME_ID}-secrets/.env pull web
docker compose -f compose.production.yml --env-file ~/${GAME_ID}-secrets/.env up -d --no-build web
env:
GAME_ID: ${{ inputs.game-id }}
-5
View File
@@ -6,11 +6,6 @@
"labels": ["dependencies"], "labels": ["dependencies"],
"platformAutomerge": true, "platformAutomerge": true,
"packageRules": [ "packageRules": [
{
"description": "Pin kysely to 0.28.x fleet-wide. 0.29.x dropped the DEFAULT_MIGRATION_LOCK_TABLE exports that @better-auth/kysely-adapter bundles, breaking every game's api-core build. Do NOT allow >=0.29 until better-auth ships a 0.29-safe adapter.",
"matchPackageNames": ["kysely"],
"allowedVersions": "<0.29.0"
},
{ {
"description": "Bump the internal @played/* packages together. Manual merge — 0.x bumps can be breaking and merging redeploys the game.", "description": "Bump the internal @played/* packages together. Manual merge — 0.x bumps can be breaking and merging redeploys the game.",
"matchPackageNames": ["/^@played//"], "matchPackageNames": ["/^@played//"],