# 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: - /var/run/docker.sock:/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