Deploy web to NAS / deploy (push) Successful in 2m47s
act_runner already mounts /var/run/docker.sock into every job container, so declaring it again in the workflow made the daemon refuse to create the container at all: "Duplicate mount point: /var/run/docker.sock". The deploy directory still has to be declared, and still at its own host path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
91 lines
3.8 KiB
YAML
91 lines
3.8 KiB
YAML
# Push to main -> the NAS rebuilds itself.
|
|
#
|
|
# This replaces having to run ./release.sh --web by hand for a server change. It does the
|
|
# same two things that script's remote half did, in the same order and with the same
|
|
# protections: copy server/ over the live directory, then recreate the container in place.
|
|
#
|
|
# It runs on the "nas" runner (act_runner, in Docker on the NAS itself), so it does not
|
|
# care whether the laptop is awake, on the church network, or in the building.
|
|
#
|
|
# NOT covered here on purpose: the iOS and Android apps. They bundle a copy of the web UI
|
|
# at build time (see mobile/scripts/sync-web.js), so a UI change only reaches phones via a
|
|
# new store release. Server-side changes reach them immediately, because they call this
|
|
# same server live.
|
|
|
|
name: Deploy web to NAS
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- 'server/**'
|
|
- '.gitea/workflows/deploy.yml'
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: nas
|
|
container:
|
|
image: node:20-alpine
|
|
volumes:
|
|
# The docker socket is deliberately NOT listed here: act_runner already mounts it
|
|
# into every job container, and declaring it again makes the daemon reject the
|
|
# container with "Duplicate mount point: /var/run/docker.sock".
|
|
#
|
|
# Mounted at the SAME path it has on the host. docker-compose.yml binds ./data
|
|
# relative to the compose file, and the daemon resolves that as a HOST path — so if
|
|
# this were mounted anywhere else the app would come up pointed at a directory that
|
|
# does not exist, with an empty data volume.
|
|
- /volume2/docker/recurring-checklist:/volume2/docker/recurring-checklist
|
|
|
|
steps:
|
|
- name: Install tools
|
|
run: apk add --no-cache docker-cli docker-cli-compose rsync
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Copy server files over the live directory
|
|
run: |
|
|
set -eu
|
|
DEST=/volume2/docker/recurring-checklist
|
|
test -f "$DEST/docker-compose.yml" || { echo "REFUSING: $DEST is not the deploy directory"; exit 1; }
|
|
|
|
# data/ holds the live checklists, uploads and backups; .env holds the admin and
|
|
# Gmail credentials. Neither is in git, and neither may ever be written here.
|
|
# No --delete: this mirrors the old tarball deploy, which only ever added and
|
|
# overwrote. A stale file left behind is survivable; a wiped data/ is not.
|
|
rsync -a \
|
|
--exclude 'data/' \
|
|
--exclude 'data-smoke/' \
|
|
--exclude '.env' \
|
|
--exclude 'node_modules/' \
|
|
server/ "$DEST/"
|
|
|
|
- name: Verify the live data and credentials survived
|
|
run: |
|
|
set -eu
|
|
DEST=/volume2/docker/recurring-checklist
|
|
test -f "$DEST/.env" || { echo "ABORT: .env is gone"; exit 1; }
|
|
test -d "$DEST/data" || { echo "ABORT: data/ is gone"; exit 1; }
|
|
echo ".env and data/ intact"
|
|
|
|
- name: Rebuild the container in place
|
|
# No 'down' first: that removes the container AND the network, so an interrupted
|
|
# deploy would leave the whole stack torn down. 'up -d --build' recreates only the
|
|
# app container, and the worst case on failure is the old one still serving.
|
|
run: docker compose -f /volume2/docker/recurring-checklist/docker-compose.yml up -d --build --remove-orphans
|
|
|
|
- name: Healthcheck
|
|
run: |
|
|
set -eu
|
|
for i in $(seq 1 20); do
|
|
S=$(docker inspect --format '{{.State.Health.Status}}' recurring-checklist 2>/dev/null || echo starting)
|
|
[ "$S" = "healthy" ] && { echo "container healthy"; exit 0; }
|
|
sleep 3
|
|
done
|
|
echo "container did not report healthy in time:"
|
|
docker ps --filter name=recurring-checklist --format '{{.Names}} {{.Status}}'
|
|
docker logs --tail 30 recurring-checklist
|
|
exit 1
|