Pak Jabit: full stack — storefront, Spree customisations, catalogue tooling

A South African men's apparel store on Spree 5.6 + Next.js 15. 160 products
live, three markets, live Malaysian courier rates, customer-owned carts.

What is here:
  storefront/  the Next.js app
  backend/     ONLY the custom Spree code. Spree itself is a prebuilt image
               that mounts no app code, so every file here needs an explicit
               volume entry in docker-compose.yml or it is silently ignored.
  catalogue/   the scripts that loaded and maintain the products
  docs/        manifest, mock registry, napkins

Real .env files are committed deliberately. This lives on the house's private
Gitea, and a clone that runs beats a clone that needs a secrets handover.
LANDMINE.md maps every key, what it opens, and what must be rotated before this
repo goes anywhere less private.

The admin API key used to be a literal in six catalogue scripts. They now read
SPREE_ADMIN_KEY from the environment and refuse to run without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-15 22:09:55 +08:00
co-authored by Claude Opus 5
commit a58a61b98c
98 changed files with 15524 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
# Pull a full Pak Jabit backend backup to the house.
# - pg_dump (custom format) of spree_production
# - ActiveStorage volume, rsync'd mirror (~208MB, only deltas after the first run)
# - .env (SECRET_KEY_BASE lives here — without it every signed image URL and
# the admin API token die on restore)
#
# Usage: backup-pak-jabit.sh # normal run
# backup-pak-jabit.sh --verify # also list the dump's table counts
set -euo pipefail
HOST=ijat@35.247.170.3
KEY=/root/.ssh/google_compute_engine
SSH=(ssh -i "$KEY" -o ConnectTimeout=15 -o BatchMode=yes)
DEST=/root/Backups/pak-jabit
KEEP=14 # dated dumps to retain
STAMP=$(date +%Y%m%d-%H%M)
STORAGE_REMOTE=/var/lib/docker/volumes/pak-jabit_storage_data/_data/
mkdir -p "$DEST"/{db,storage}
chmod 700 "$DEST"
log() { printf '%s %s\n' "$(date +%H:%M:%S)" "$*"; }
# --- 1. database -------------------------------------------------------------
log "pg_dump → db/spree-$STAMP.dump"
"${SSH[@]}" "$HOST" \
'docker exec pak-jabit-postgres-1 pg_dump -U postgres -Fc spree_production' \
> "$DEST/db/spree-$STAMP.dump.part"
mv "$DEST/db/spree-$STAMP.dump.part" "$DEST/db/spree-$STAMP.dump"
# A truncated dump is the classic silent-success backup. Refuse to keep one.
if ! pg_restore -l "$DEST/db/spree-$STAMP.dump" >/dev/null 2>&1; then
rm -f "$DEST/db/spree-$STAMP.dump"
echo "FATAL: dump failed pg_restore -l — not a usable archive, deleted." >&2
exit 1
fi
log " ok $(du -h "$DEST/db/spree-$STAMP.dump" | cut -f1)"
# --- 2. ActiveStorage --------------------------------------------------------
log "rsync storage/"
rsync -a --delete --info=stats2 \
-e "ssh -i $KEY -o BatchMode=yes" \
--rsync-path='sudo rsync' \
"$HOST:$STORAGE_REMOTE" "$DEST/storage/" | tail -n 6
# --- 3. secrets & compose ----------------------------------------------------
log "env + compose"
"${SSH[@]}" "$HOST" 'cat ~/pak-jabit/.env' > "$DEST/env-$STAMP"
"${SSH[@]}" "$HOST" 'cat ~/pak-jabit/docker-compose.yml' > "$DEST/docker-compose.yml"
chmod 600 "$DEST"/env-*
ln -sfn "env-$STAMP" "$DEST/env.latest"
# --- 3b. custom app code -----------------------------------------------------
# The Spree image is prebuilt and mounts no app code, so every customisation
# (Image Studio, the API body-limit initializer, the patched routes.rb) exists
# ONLY as bind-mounted files on this VM. The database dump does not contain them
# and a rebuilt box would come back without them.
log "custom code"
rsync -a --delete \
-e "ssh -i $KEY -o BatchMode=yes" \
--include='config/' --include='config/routes.rb' \
--include='config/initializers/***' \
--include='app/' --include='app/controllers/***' --include='app/views/***' \
--exclude='*' \
"$HOST:pak-jabit/" "$DEST/custom/"
find "$DEST/custom" -type f | sed "s|$DEST/custom/| |"
# --- 4. rotate ---------------------------------------------------------------
ls -1t "$DEST"/db/spree-*.dump 2>/dev/null | tail -n +$((KEEP + 1)) | xargs -r rm -v
ls -1t "$DEST"/env-* 2>/dev/null | tail -n +$((KEEP + 1)) | xargs -r rm -v
if [[ ${1:-} == --verify ]]; then
log "dump contents:"
pg_restore -l "$DEST/db/spree-$STAMP.dump" | grep -c 'TABLE DATA' \
| xargs printf ' %s tables with data\n'
fi
log "done → $DEST ($(du -sh "$DEST" | cut -f1) total)"