Waters Boards: self-hosted work management to replace monday.com
Deploy Waters Boards to NAS / deploy (push) Canceled after 0s

Boards with groups, items, subitems and 18 column types; table, kanban,
calendar, timeline, chart and files views; updates with @mentions,
replies and attachments; inbox, My work, search, automations; Google
sign-in restricted to company domains; live updates over SSE; a
monday.com API importer; phone layout and installable PWA; Docker +
Gitea Actions deploy to the NAS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 15:34:47 -04:00
co-authored by Claude Opus 5.5
commit fcf3805626
44 changed files with 7443 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
# Push to main -> the NAS rebuilds Waters Boards.
#
# Same shape as the checklist app's deploy: runs on the "nas" act_runner (in Docker on the
# NAS itself), copies server/ over the live directory, then recreates the container in place.
#
# ONE-TIME NAS SETUP before the first run (see README.md › Deploy):
# 1. mkdir /volume2/docker/waters-boards and put a filled-in .env there
# 2. add "/volume2/docker/waters-boards" to container.valid_volumes in the act_runner
# config.yaml on the NAS, and restart the runner
# 3. point a reverse-proxy hostname (https) at port 3100 — Google sign-in needs https
# 4. set the repository variable DEPLOY_ENABLED=true (Settings ▸ Actions ▸ Variables).
# Until then this job is skipped, so pushes don't fail before the NAS is ready.
name: Deploy Waters Boards to NAS
on:
push:
branches: [main]
paths:
- 'server/**'
- '.gitea/workflows/deploy.yml'
workflow_dispatch:
jobs:
deploy:
if: ${{ vars.DEPLOY_ENABLED == 'true' }}
runs-on: nas
container:
image: node:22-alpine
volumes:
# Mounted at the SAME path as on the host: docker-compose.yml binds ./data relative to
# the compose file and the daemon resolves that as a host path. (The docker socket is
# already mounted by act_runner — declaring it again breaks the job.)
- /volume2/docker/waters-boards:/volume2/docker/waters-boards
steps:
- name: Install tools
run: apk add --no-cache docker-cli docker-cli-compose rsync
- name: Checkout
uses: actions/checkout@v4
- name: Check the deploy directory is set up
run: |
set -eu
DEST=/volume2/docker/waters-boards
test -f "$DEST/.env" || { echo "REFUSING: $DEST/.env is missing — see README.md › Deploy"; exit 1; }
- name: Copy server files over the live directory
run: |
set -eu
# data/ holds the live database, uploads and backups; .env holds the secrets.
# Neither is in git and neither may ever be written here. No --delete.
rsync -a \
--exclude 'data/' \
--exclude '.env' \
--exclude 'node_modules/' \
server/ /volume2/docker/waters-boards/
- name: Verify the live data and credentials survived
run: |
set -eu
test -f /volume2/docker/waters-boards/.env || { echo "ABORT: .env is gone"; exit 1; }
echo ".env intact"; ls -la /volume2/docker/waters-boards/data 2>/dev/null || echo "(no data/ yet — first deploy)"
- name: Rebuild the container in place
# No 'down' first: an interrupted deploy then leaves the old container serving.
run: docker compose -f /volume2/docker/waters-boards/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}}' waters-boards 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=waters-boards --format '{{.Names}} {{.Status}}'
docker logs --tail 30 waters-boards
exit 1