Files
Project-SDE-WP-Suite/scripts/db-backup.sh
n.siegfried 39b48055ff Productionize WP Suite: auth, security hardening, sync, dashboard, PWA, email
Brings the Work Package Suite from a browser-local prototype to a
multi-tenant, SQL-backed deployment hardened for customer IP.

Auth & access control
- Local username/password login (bcrypt + JWT in an HttpOnly cookie),
  admin-managed users, per-project membership, and project-scoped API access.
- Admin console: change user roles, view the audit trail, manage settings.

Security hardening
- CSP / HSTS / X-Frame-Options / nosniff headers in nginx; Secure cookie via
  X-Forwarded-Proto; CSRF Origin check; attribute-safe output escaping.
- Login lockout, token_version session revocation, stronger password policy,
  fail-closed secret loading, encrypted (AES-256) database backups.

Persistence & schema
- SOPs and Work Packages are now DB-backed and shared across users, written
  through a durable client sync outbox that queues offline edits.
- Alembic migrations applied automatically on container start.

New capabilities
- Phase 2 dashboard (progress, gating, pagination, archive).
- Phase 3 PWA "Field View" with offline caching and auth fallback.
- WP owner assignment with OPTIONAL email notifications, OFF by default and
  toggled from the admin console. SMTP password is read only from the
  SMTP_PASSWORD env var (never stored); emails carry a WP number + deep link,
  never customer IP.

Also: IBM Carbon restyle, Help section, and DEPLOYMENT.md brought up to date.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 17:51:15 -07:00

59 lines
2.3 KiB
Bash

#!/bin/sh
# One database backup: pg_dump -> gzip [-> openssl AES-256] -> timestamped file in
# $BACKUP_DIR, then prune to the newest $BACKUP_KEEP files.
#
# Encryption: if BACKUP_ENC_PASSPHRASE is set, the dump is encrypted at rest with
# AES-256 (openssl, PBKDF2) and written as *.sql.gz.enc. STRONGLY recommended once
# the database holds customer IP — otherwise the dump (and every offsite copy) is
# plaintext. Keep the passphrase OUT of the backups directory (and off the host if
# possible); losing it means the backups are unrecoverable.
#
# Runs inside a container that has pg_dump + openssl (see scripts/backup.Dockerfile).
set -eu
BACKUP_DIR="${BACKUP_DIR:-/backups}"
KEEP="${BACKUP_KEEP:-14}"
PGHOST="${PGHOST:-db}"
PGPORT="${PGPORT:-5432}"
DB="${POSTGRES_DB:?POSTGRES_DB is required}"
DB_USER="${POSTGRES_USER:?POSTGRES_USER is required}"
export PGPASSWORD="${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}"
ENC="${BACKUP_ENC_PASSPHRASE:-}"
mkdir -p "$BACKUP_DIR"
ts="$(date -u +%Y%m%d-%H%M%SZ)"
if [ -n "$ENC" ]; then
out="$BACKUP_DIR/wpsuite-$ts.sql.gz.enc"
else
out="$BACKUP_DIR/wpsuite-$ts.sql.gz"
echo "[db-backup] WARNING: BACKUP_ENC_PASSPHRASE not set — this dump is UNENCRYPTED. Set it to protect data at rest." >&2
fi
tmp="$out.partial"
echo "[db-backup] $(date -u) dumping ${DB}@${PGHOST} -> ${out}"
if [ -n "$ENC" ]; then
if pg_dump -h "$PGHOST" -p "$PGPORT" -U "$DB_USER" -d "$DB" --clean --if-exists \
| gzip -c \
| openssl enc -aes-256-cbc -pbkdf2 -salt -pass env:BACKUP_ENC_PASSPHRASE > "$tmp"; then
mv "$tmp" "$out"
else
echo "[db-backup] FAILED — pg_dump/encrypt error" >&2; rm -f "$tmp"; exit 1
fi
else
if pg_dump -h "$PGHOST" -p "$PGPORT" -U "$DB_USER" -d "$DB" --clean --if-exists | gzip -c > "$tmp"; then
mv "$tmp" "$out"
else
echo "[db-backup] FAILED — pg_dump error" >&2; rm -f "$tmp"; exit 1
fi
fi
echo "[db-backup] wrote $(du -h "$out" | cut -f1) ${out}"
# Retention: keep the newest $KEEP dumps (plaintext or encrypted), delete the rest.
count="$(ls -1t "$BACKUP_DIR"/wpsuite-*.sql.gz* 2>/dev/null | grep -v '\.partial$' | wc -l | tr -d ' ')"
if [ "$count" -gt "$KEEP" ]; then
ls -1t "$BACKUP_DIR"/wpsuite-*.sql.gz* 2>/dev/null | grep -v '\.partial$' | tail -n +"$((KEEP + 1))" | while IFS= read -r f; do
echo "[db-backup] pruning $f"
rm -f "$f"
done
fi