#!/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."