Files
recurring-checklist/build-deploy.sh
T

125 lines
4.8 KiB
Bash
Raw Normal View History

#!/bin/bash
# Build a deploy tarball from server/ and push it to the production NAS.
# Excludes anything that should NEVER be overwritten on the server:
# - data/ (user accounts, check states, list content)
# - .env (admin password, Gmail credentials)
# - node_modules (rebuilt by Docker)
#
# Usage: ./build-deploy.sh
#
# Requires:
# - /tmp/sshstdin.exp (created on demand)
# - /tmp/deploy.sh (created on demand)
# - Christian's SSH password, from the login keychain (see below)
set -e
ROOT="$(cd "$(dirname "$0")" && pwd)"
TARBALL="$ROOT/checklist-deploy.tgz"
NAS_HOST="10.1.1.5"
NAS_USER="Christian"
NAS_PATH="/volume2/docker/recurring-checklist"
# The NAS password is NOT stored in this file — it would end up in the repo history,
# where rotating it means rewriting history rather than changing one keychain entry.
# It is read from the login keychain, or from NAS_PASS in the environment for a machine
# that has no keychain (CI, a Linux box).
#
# To store it, or after a password change:
# security add-generic-password -U -s checklist-nas -a Christian -w
# (omit the value and security prompts for it, so it never reaches your shell history)
NAS_PASS="${NAS_PASS:-$(security find-generic-password -s checklist-nas -a Christian -w 2>/dev/null || true)}"
if [ -z "$NAS_PASS" ]; then
echo "No NAS password available." >&2
echo "Store it once with: security add-generic-password -U -s checklist-nas -a Christian -w" >&2
echo "or export NAS_PASS before running this script." >&2
exit 1
fi
echo "==> Building tarball (excluding data/, .env, node_modules)"
tar \
--exclude='./node_modules' \
--exclude='./data' \
--exclude='./data-smoke' \
--exclude='./.DS_Store' \
--exclude='./.env' \
-czf "$TARBALL" -C "$ROOT/server" .
echo "==> Sanity check: tarball must not contain data/ or .env"
if tar -tzf "$TARBALL" | grep -E '^\./?(data|\.env)(/|$)' >/dev/null; then
echo "REFUSING to deploy — tarball contains data/ or .env"
tar -tzf "$TARBALL" | grep -E '^\./?(data|\.env)(/|$)'
exit 1
fi
echo " OK ($(ls -lh "$TARBALL" | awk '{print $5}'))"
# Re-create helper scripts if missing
if [ ! -f /tmp/sshstdin.exp ]; then
cat > /tmp/sshstdin.exp <<'EXP'
#!/usr/bin/expect -f
set timeout 300
log_user 1
set host [lindex $argv 0]
set user [lindex $argv 1]
set pass [lindex $argv 2]
set cmd [lindex $argv 3]
set localfile [lindex $argv 4]
spawn bash -c "ssh -o StrictHostKeyChecking=no -o ConnectTimeout=20 -o PreferredAuthentications=password -o PubkeyAuthentication=no -o GSSAPIAuthentication=no -o NumberOfPasswordPrompts=1 $user@$host \"$cmd\" < $localfile"
expect {
-nocase "password:" { send "$pass\r"; exp_continue }
eof
}
catch wait result
exit [lindex $result 3]
EXP
fi
cat > /tmp/deploy.sh <<'DEPLOY'
#!/bin/sh
set -e
cd /volume2/docker/recurring-checklist
echo "--- DEPLOY-SIDE SANITY CHECK ---"
if tar -tzf checklist-deploy.tgz | grep -E '^\./?(data|\.env)(/|$)' >/dev/null; then
echo "ABORT: incoming tarball contains data/ or .env — refusing to extract"
exit 1
fi
echo "--- EXTRACT (data/ and .env are protected because tarball doesn't include them) ---"
tar -xzf checklist-deploy.tgz
rm -f checklist-deploy.tgz
rm -rf PaxHeader
find . -name "._*" -delete
echo "--- VERIFY DATA UNTOUCHED ---"
ls -la data/checklists.json 2>/dev/null && echo "data/checklists.json preserved" || echo "(no checklists.json yet)"
ls -la .env 2>/dev/null && echo ".env preserved" || echo "(no .env)"
echo "--- DEPLOY: recreate in place (NO prior 'down') ---"
# Deliberately NO 'docker-compose down' first. 'down' removes the container AND the network, so a
# deploy interrupted between 'down' and 'up' leaves the whole stack torn down (caused an outage once).
# 'up -d --build --remove-orphans' rebuilds the image and recreates ONLY the app container when its
# image changed, in place — the network and the data volume persist. If interrupted, the worst case is
# the old container still running, never a fully removed stack. --remove-orphans clears stale containers.
echo "$REMOTE_PASS" | sudo -S /usr/local/bin/docker-compose -f /volume2/docker/recurring-checklist/docker-compose.yml up -d --build --remove-orphans 2>&1 | tail -14
echo "--- STATUS ---"
echo "$REMOTE_PASS" | sudo -S /usr/local/bin/docker ps --filter name=recurring --format "{{.Names}} {{.Status}}"
echo "--- HEALTHCHECK ---"
sleep 4
curl -sS -o /dev/null -w "GET /api/me => HTTP %{http_code}\n" http://127.0.0.1:3000/api/me
DEPLOY
echo "==> Uploading to $NAS_USER@$NAS_HOST:$NAS_PATH"
expect /tmp/sshstdin.exp "$NAS_HOST" "$NAS_USER" "$NAS_PASS" \
"cat > $NAS_PATH/checklist-deploy.tgz && echo UPLOAD_OK" \
"$TARBALL" 2>&1 | tail -3
echo "==> Running deploy script on NAS"
expect /tmp/sshstdin.exp "$NAS_HOST" "$NAS_USER" "$NAS_PASS" \
"REMOTE_PASS='$NAS_PASS' bash -s" \
/tmp/deploy.sh 2>&1 | tail -15
echo "==> Done"