/api/health now reports REVISION alongside status, and the CI wait step checks for this deploy's own short SHA specifically - a stray old container that never got torn down (or a build that silently kept serving stale code) would still 200 on a plain health check but now won't match.
135 lines
5.9 KiB
YAML
135 lines
5.9 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
pull_request:
|
|
|
|
jobs:
|
|
check:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# No ruby-version input: the action auto-detects it from
|
|
# .ruby-version when the input is omitted (not "file", which isn't
|
|
# a recognized value and fails with "Unknown engine file").
|
|
- uses: ruby/setup-ruby@v1
|
|
with:
|
|
bundler-cache: true
|
|
|
|
- name: Ruby syntax check
|
|
run: find app.rb config lib bin -name "*.rb" -print0 | xargs -0 -n1 ruby -c
|
|
|
|
- name: App loads without errors
|
|
run: bundle exec ruby -e 'require_relative "app"'
|
|
|
|
- name: Ruby tests
|
|
run: bundle exec rake test
|
|
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "22"
|
|
|
|
- name: JS syntax check
|
|
run: find public/js -name "*.js" -print0 | xargs -0 -n1 node --check
|
|
|
|
# Handled by a separate, native (non-Docker) runner registered with the
|
|
# single "cube-host" label (see docs/deployment.adoc) — it runs directly
|
|
# on the server, so it has podman/git/etc. from the host, no socket/bind
|
|
# mount tricks needed. The Docker-based runner keeps handling `check`.
|
|
deploy:
|
|
needs: check
|
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
runs-on: cube-host
|
|
steps:
|
|
# Not actions/checkout@v4: keeping this plain-git checkout from when
|
|
# the job ran in a Node-less container — no reason to add a Node
|
|
# dependency back in for something this already does.
|
|
- name: Checkout
|
|
run: |
|
|
rm -rf .git * .[!.]* 2>/dev/null
|
|
git init -q
|
|
git remote add origin "${{ gitea.server_url }}/${{ gitea.repository }}.git"
|
|
git -c http.extraHeader="Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
|
fetch --depth 1 origin "${{ gitea.sha }}"
|
|
git checkout -q FETCH_HEAD
|
|
|
|
# Read by app.rb at boot and shown (as a hover tooltip on the topbar
|
|
# brand) so it's possible to tell which commit is actually live
|
|
# without digging through `tea actions runs list` — written here,
|
|
# before .git gets dropped below, since that's otherwise the only
|
|
# place the commit is recorded.
|
|
- name: Write REVISION
|
|
run: git rev-parse --short HEAD > REVISION
|
|
|
|
# Clears out everything in the target except data/ (uploaded GPX
|
|
# files, offline.pmtiles) and copies the fresh checkout over, dropping
|
|
# .git afterwards. Dotfiles like .env (deployment secrets) are left
|
|
# alone too — the glob below doesn't match them — and neither is ever
|
|
# part of the git checkout in the first place, since both are
|
|
# gitignored.
|
|
- name: Sync to /srv/podman/maptool
|
|
run: |
|
|
mkdir -p /srv/podman/maptool
|
|
for f in /srv/podman/maptool/*; do
|
|
[ "$(basename "$f")" = "data" ] && continue
|
|
rm -rf "$f"
|
|
done
|
|
cp -a . /srv/podman/maptool/
|
|
rm -rf /srv/podman/maptool/.git
|
|
|
|
# Regenerated on every deploy from repo secrets (Settings -> Actions
|
|
# -> Secrets) rather than created once by hand on the server — see
|
|
# docs/deployment.adoc. Values are masked in the job log by Gitea
|
|
# automatically since they come from `secrets`. Secret names here
|
|
# can't start with "GITEA_"/"GITHUB_" — Gitea reserves those prefixes
|
|
# — hence OAUTH_CLIENT_* rather than GITEA_OAUTH_CLIENT_*; the .env
|
|
# var names on the right of `=` are unaffected, still what app.rb
|
|
# actually reads.
|
|
- name: Write .env
|
|
run: |
|
|
cat > /srv/podman/maptool/.env <<EOF
|
|
SESSION_SECRET=${{ secrets.SESSION_SECRET }}
|
|
GITEA_OAUTH_CLIENT_ID=${{ secrets.OAUTH_CLIENT_ID }}
|
|
GITEA_OAUTH_CLIENT_SECRET=${{ secrets.OAUTH_CLIENT_SECRET }}
|
|
MAPTOOL_ALLOWED_GITEA_USER=${{ secrets.MAPTOOL_ALLOWED_GITEA_USER }}
|
|
LIVETRACK_WEBHOOK_SECRET=${{ secrets.LIVETRACK_WEBHOOK_SECRET }}
|
|
EOF
|
|
chmod 600 /srv/podman/maptool/.env
|
|
|
|
# podman-compose@maptool.service (/etc/systemd/system/podman-compose@.service)
|
|
# owns the container lifecycle — its ExecStartPre already tears down
|
|
# the previous containers/networks/volumes on every start, and its
|
|
# ExecStart runs `podman compose up --build` (mirroring
|
|
# docker-compose@.service's own `--build`), so a plain restart always
|
|
# picks up the code just synced above while still reusing unchanged
|
|
# layers (apt-get/bundle install) as cache — unlike explicitly
|
|
# removing the image first, which would delete that cache along with
|
|
# it. sudo is needed since the unit is root's, not gitea-runner's own
|
|
# (gitea-runner has passwordless sudo for this on cube-host).
|
|
- name: Deploy
|
|
run: sudo systemctl restart podman-compose@maptool.service
|
|
|
|
# systemctl restart above only confirms the process forked
|
|
# (Type=simple, no readiness protocol) — not that `podman compose up
|
|
# --build` actually finished and Puma is answering. Polls the health
|
|
# endpoint (cube-host itself, same machine the container's port is
|
|
# published on) instead of trusting that — checking for this
|
|
# deploy's own revision, not just any 200, so a stuck old container
|
|
# that never got torn down can't pass as a successful deploy of the
|
|
# new one. 5 minutes covers even a from-scratch rebuild (Gemfile
|
|
# change, ~3.5 minutes measured) with room to spare; a cached
|
|
# rebuild is normally up within a minute.
|
|
- name: Wait for it to come up
|
|
run: |
|
|
expected=$(git rev-parse --short HEAD)
|
|
for i in $(seq 1 30); do
|
|
if curl -sf http://localhost:4567/api/health | grep -q "\"revision\":\"$expected\""; then
|
|
echo "Healthy at $expected after $(( (i - 1) * 10 ))s"
|
|
exit 0
|
|
fi
|
|
sleep 10
|
|
done
|
|
echo "::error::maptool did not report revision $expected within 5 minutes"
|
|
exit 1
|