Host on AWS Lightsail as boards.watersmedia.org
Deploy Waters Boards to AWS / deploy (push) Canceled after 0s
Deploy Waters Boards to AWS / deploy (push) Canceled after 0s
Replace the planned Synology deploy with one Lightsail box: Caddy in front of the app for automatic HTTPS (deploy/aws/docker-compose.yml, Caddyfile), a deploy script that rsyncs server/ and rebuilds in place without touching data/ or .env, and a Gitea workflow that runs the same script from the nas runner over SSH. README documents the box, DNS, .env, deploys and restore. Allow both church Google Workspace domains to sign in. Drop the NAS-only compose file. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# Caddy in front of Waters Boards.
|
||||
#
|
||||
# Caddy gets and renews the Let's Encrypt certificate by itself. That only works while
|
||||
# the DNS A record for boards.watersmedia.org points at this box and ports 80 and 443
|
||||
# are open in the Lightsail firewall.
|
||||
|
||||
{
|
||||
# Let's Encrypt sends certificate expiry notices here.
|
||||
email michael@waterschurch.org
|
||||
}
|
||||
|
||||
boards.watersmedia.org {
|
||||
encode zstd gzip
|
||||
|
||||
reverse_proxy boards:3100 {
|
||||
# Required: send every write straight through. Without it Caddy can buffer the
|
||||
# server-sent events stream at /api/events and live updates stop arriving.
|
||||
flush_interval -1
|
||||
}
|
||||
}
|
||||
Executable
+78
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deploy Waters Boards to the AWS Lightsail box.
|
||||
#
|
||||
# Usage, from the repo root:
|
||||
# deploy/aws/deploy.sh
|
||||
#
|
||||
# Target host comes from BOARDS_HOST (default: the "boards-aws" SSH alias).
|
||||
# Never writes or deletes data/ or .env on the box. Uses ssh BatchMode, so it never prompts.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HOST="${BOARDS_HOST:-boards-aws}"
|
||||
REMOTE_DIR=/opt/waters-boards
|
||||
SSH_OPTS=(-o BatchMode=yes)
|
||||
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
step() { printf '\n==> %s\n' "$*"; }
|
||||
remote() { ssh "${SSH_OPTS[@]}" "$HOST" "$@"; }
|
||||
|
||||
step "1/7 Syntax-check the server code"
|
||||
node --check server/server.js
|
||||
for f in server/src/*.js; do
|
||||
node --check "$f"
|
||||
done
|
||||
echo "syntax OK"
|
||||
|
||||
step "2/7 Check $HOST:$REMOTE_DIR/.env exists"
|
||||
if ! remote "test -f $REMOTE_DIR/.env"; then
|
||||
echo "ABORT: $REMOTE_DIR/.env is missing on $HOST (or $HOST is unreachable)." >&2
|
||||
echo "Create it once by hand from server/.env.example. This script never creates or overwrites it." >&2
|
||||
echo "See README.md > Deploy to AWS (Lightsail)." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo ".env found"
|
||||
|
||||
step "3/7 Copy server/ to $HOST:$REMOTE_DIR/server/ (no data/, .env or node_modules/, no --delete)"
|
||||
rsync -a -e "ssh ${SSH_OPTS[*]}" \
|
||||
--exclude 'data/' --exclude '.env' --exclude 'node_modules/' \
|
||||
server/ "$HOST:$REMOTE_DIR/server/"
|
||||
|
||||
step "4/7 Copy docker-compose.yml and Caddyfile to $HOST:$REMOTE_DIR/"
|
||||
# --inplace: the Caddyfile is bind-mounted as a single file. A normal rsync replaces it with a
|
||||
# new file, and the running container would keep reading the old one.
|
||||
rsync -a --inplace -e "ssh ${SSH_OPTS[*]}" \
|
||||
deploy/aws/docker-compose.yml deploy/aws/Caddyfile "$HOST:$REMOTE_DIR/"
|
||||
|
||||
step "5/7 Rebuild and restart in place (no 'down' first: a failed build leaves the old container serving)"
|
||||
remote "cd $REMOTE_DIR && docker compose up -d --build --remove-orphans"
|
||||
# Caddy doesn't watch its config. Reload so a changed Caddyfile takes effect without a restart.
|
||||
remote "cd $REMOTE_DIR && docker compose exec -T caddy caddy reload --config /etc/caddy/Caddyfile" \
|
||||
|| echo "WARNING: caddy reload failed; check: docker logs waters-caddy" >&2
|
||||
|
||||
step "6/7 Wait for the waters-boards container to report healthy (up to 20 x 3 s)"
|
||||
healthy=0
|
||||
for i in $(seq 1 20); do
|
||||
status=$(remote "docker inspect --format '{{.State.Health.Status}}' waters-boards" 2>/dev/null || echo starting)
|
||||
echo " try $i/20: $status"
|
||||
if [ "$status" = "healthy" ]; then
|
||||
healthy=1
|
||||
break
|
||||
fi
|
||||
sleep 3
|
||||
done
|
||||
if [ "$healthy" -ne 1 ]; then
|
||||
echo "ABORT: waters-boards did not report healthy in time." >&2
|
||||
remote "docker ps" || true
|
||||
remote "docker logs --tail 30 waters-boards" || true
|
||||
exit 1
|
||||
fi
|
||||
echo "container healthy"
|
||||
|
||||
step "7/7 Check the public URL (informational only; DNS or the certificate may not be ready yet)"
|
||||
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 https://boards.watersmedia.org/healthz || true)
|
||||
echo "https://boards.watersmedia.org/healthz -> HTTP ${code:-000}"
|
||||
|
||||
echo
|
||||
echo "Deploy finished."
|
||||
@@ -0,0 +1,45 @@
|
||||
# Waters Boards on the AWS Lightsail box.
|
||||
#
|
||||
# This file is copied to /opt/waters-boards/ on the box (deploy/aws/deploy.sh does it), and
|
||||
# sits there next to:
|
||||
# .env secrets, created by hand once, never overwritten by a deploy
|
||||
# data/ database, uploads and nightly backups, never touched by a deploy
|
||||
# Caddyfile the reverse-proxy config from this folder
|
||||
# server/ the app source, rsynced from ../../server
|
||||
# "build: ./server" and "./data" below are relative to that folder, not to this repo.
|
||||
|
||||
services:
|
||||
boards:
|
||||
build: ./server
|
||||
image: waters-boards:latest
|
||||
container_name: waters-boards
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- ./.env
|
||||
volumes:
|
||||
# Database (boards.db), uploaded files and nightly backups.
|
||||
- ./data:/app/data
|
||||
# Reachable only from Caddy on the compose network. No "ports": the app is never public.
|
||||
expose:
|
||||
- "3100"
|
||||
|
||||
caddy:
|
||||
image: caddy:2.10-alpine
|
||||
container_name: waters-caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
# Certificates and Caddy's state. Keep these volumes, or Caddy has to re-issue the
|
||||
# certificate on every restart and Let's Encrypt will rate-limit it.
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
depends_on:
|
||||
- boards
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
Reference in New Issue
Block a user