Skip to content

Deploy Server

Deploy Server #23

Workflow file for this run

name: Deploy Server
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
IMAGE: ghcr.io/vicplusplus/arrow-thing-api
on:
release:
types: [published]
workflow_dispatch:
concurrency:
group: deploy-server
cancel-in-progress: false
permissions:
contents: read
packages: write
jobs:
# Gate: ensure CI passed for this commit before deploying
ci-check:
runs-on: ubuntu-latest
# Skip CI gate for manual triggers — CI only runs on PRs
if: github.event_name != 'workflow_dispatch'
steps:
- name: Verify CI passed
env:
GH_TOKEN: ${{ github.token }}
run: |
SHA=${{ github.sha }}
echo "Checking CI status for $SHA..."
# Wait up to 5 minutes for CI to report
for i in $(seq 1 10); do
STATUS=$(gh api "repos/${{ github.repository }}/commits/$SHA/status" --jq '.state')
if [ "$STATUS" = "success" ]; then
echo "CI passed"
exit 0
elif [ "$STATUS" = "failure" ] || [ "$STATUS" = "error" ]; then
echo "CI failed (state: $STATUS) — aborting deploy"
exit 1
fi
echo "Attempt $i: CI state is '$STATUS' — waiting 30s..."
sleep 30
done
echo "CI did not report success within timeout — aborting deploy"
exit 1
build-and-push:
runs-on: ubuntu-latest
needs: ci-check
if: ${{ !cancelled() && (needs.ci-check.result == 'success' || needs.ci-check.result == 'skipped') }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Log in to GitHub Container Registry
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
context: .
file: server/Dockerfile
push: true
tags: |
${{ env.IMAGE }}:latest
${{ env.IMAGE }}:${{ github.sha }}
deploy:
runs-on: ubuntu-latest
needs: build-and-push
if: ${{ !cancelled() && needs.build-and-push.result == 'success' }}
environment: production
steps:
- name: Deploy to VPS
run: |
mkdir -p ~/.ssh
echo "${{ secrets.VPS_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
echo "${{ secrets.VPS_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
ssh -i ~/.ssh/id_ed25519 deploy@${{ secrets.VPS_HOST }} \
"cd /home/deploy/arrow-thing/repo && \
git fetch origin main && \
git reset --hard origin/main && \
./server/deploy/setup.sh && \
cd /home/deploy/arrow-thing && \
docker compose pull && \
docker compose up -d --remove-orphans"
- name: Health check
run: |
echo "Waiting for container to be ready..."
sleep 5
for i in $(seq 1 6); do
STATUS=$(curl -o /dev/null -s -w "%{http_code}" https://api.arrow-thing.com/health)
if [ "$STATUS" = "200" ]; then
echo "Health check passed (HTTP $STATUS)"
exit 0
fi
echo "Attempt $i: HTTP $STATUS — retrying in 10s..."
sleep 10
done
echo "Health check failed after all retries"
exit 1
- name: Smoke tests
run: |
API="https://api.arrow-thing.com"
FAILURES=0
smoke() {
local METHOD=$1 ENDPOINT=$2 EXPECTED=$3 BODY=$4
if [ -n "$BODY" ]; then
RESP=$(curl -s -o /tmp/smoke_body -w "%{http_code}" -X "$METHOD" "$API$ENDPOINT" \
-H "Content-Type: application/json" -d "$BODY")
else
RESP=$(curl -s -o /tmp/smoke_body -w "%{http_code}" -X "$METHOD" "$API$ENDPOINT")
fi
if [ "$RESP" = "$EXPECTED" ]; then
echo "PASS: $METHOD $ENDPOINT → $RESP"
else
echo "FAIL: $METHOD $ENDPOINT → $RESP (expected $EXPECTED)"
cat /tmp/smoke_body
echo
FAILURES=$((FAILURES + 1))
fi
}
# Unauthenticated endpoints return expected status codes
smoke GET /api/auth/me 401
smoke POST /api/auth/login 401 '{"email":"smoke-nonexistent@test.invalid","password":"smoke12345678"}'
smoke POST /api/auth/forgot-password 200 '{"email":"smoke-nonexistent@test.invalid"}'
# Registration validation rejects bad input
smoke POST /api/auth/register 400 '{"email":"not-an-email","password":"short","displayName":""}'
# Admin endpoints reject missing key
smoke POST /api/admin/lock-account 401 '{"email":"anyone@test.invalid"}'
smoke POST /api/admin/unlock-account 401 '{"email":"anyone@test.invalid"}'
if [ "$FAILURES" -gt 0 ]; then
echo "Smoke tests failed: $FAILURES failure(s)"
exit 1
fi
echo "All smoke tests passed"