125 lines
4.8 KiB
Bash
125 lines
4.8 KiB
Bash
#!/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"
|