79 lines
2.9 KiB
Bash
79 lines
2.9 KiB
Bash
#!/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."
|