#!/usr/bin/env bash # ===================================================================== # BLOBBO — Deploy to partly.party/blobbo/ # ===================================================================== # Usage: bash deploy.sh (from the BLOBBO repo root) # # 1. npm run build (vite, base=/blobbo/) # 2. rsync dist/ to VPS staging # 3. OVERLAY into forum-nginx web root (keep old chunks; prune >7d orphans) # 4. verify live index AND that its entry chunk serves as JavaScript # # Follows the GAMES/ deploy doctrine (see cratewars/deploy.sh template). # Requirements: ssh humanjing@100.71.119.27 (tailscale), container forum-nginx. # ===================================================================== set -euo pipefail VPS="humanjing@100.71.119.27" CONTAINER="forum-nginx" REMOTE_WEB_ROOT="/usr/share/nginx/html/blobbo" STAGING="/tmp/blobbo_deploy" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" echo "[1/4] Building ..." cd "$SCRIPT_DIR" npm run build # Guard: scripts/stage-farm-assets.sh copies ~43MB of raw farm GLBs into # public/ for local Workshop testing, and vite copies public/ straight into # dist/. Shipping those means every player downloads 43MB of background props. # Custom models belong in the player's browser (IndexedDB) or in a decimated # committed pack — never as an accident of local testing. DIST_KB="$(du -sk dist | cut -f1)" if [ "$DIST_KB" -gt 10240 ]; then echo " ✗ dist/ is ${DIST_KB}KB (>10MB). Staged farm assets leaked into the build?" echo " Run: rm -rf public/assets/meshes && npm run build" exit 1 fi echo " dist/ is ${DIST_KB}KB — ok" echo "[2/4] Syncing dist/ to VPS staging ..." ssh "$VPS" "rm -rf '$STAGING' && mkdir -p '$STAGING'" rsync -az --exclude='.DS_Store' "$SCRIPT_DIR/dist/" "$VPS:$STAGING/" echo "[3/4] Overlaying container content (no wipe) ..." # OVERLAY, don't 'rm -rf': assets are content-hash-named, so a browser (or CDN) # holding a PREVIOUS index.html must still find the chunks it references, or the # page dies with "expected a JS module, got text/html" (the missing chunk falls # back to index.html). Copying over the top keeps old chunks alive as a grace # window. Vite rewrites every current chunk each build, so their mtime is fresh; # prune only files orphaned for >7 days so /assets can't grow without bound. # ponytail: 7-day window is plenty for a browser cache; widen if a CDN caches index longer. ssh "$VPS" "docker exec $CONTAINER sh -c 'mkdir -p ${REMOTE_WEB_ROOT:?}' \ && docker cp $STAGING/. $CONTAINER:$REMOTE_WEB_ROOT/ \ && docker exec $CONTAINER sh -c 'find ${REMOTE_WEB_ROOT}/assets -type f -mtime +7 -delete 2>/dev/null || true'" echo "[4/4] Verifying live index + entry chunk ..." BUST="$(date +%s)" CODE_LIVE="$(curl -s -o /dev/null -w '%{http_code}' "https://partly.party/blobbo/?v=$BUST")" echo " https://partly.party/blobbo/ -> HTTP $CODE_LIVE" [ "$CODE_LIVE" = "200" ] || { echo " ✗ live index check failed"; exit 1; } # The index-only check can't catch a broken chunk graph: a missing chunk is # served as the HTML fallback (200 text/html), which is exactly the bug that # bit us. Fetch the entry chunk the LIVE index references and confirm it comes # back as JavaScript — the check that would actually have caught it. MAIN="$(curl -s "https://partly.party/blobbo/?v=$BUST" | grep -oE '/blobbo/assets/main-[^"]+\.js' | head -1)" [ -n "$MAIN" ] || { echo " ✗ no main chunk found in live index"; exit 1; } CT="$(curl -s -o /dev/null -w '%{content_type}' "https://partly.party${MAIN}?v=$BUST")" case "$CT" in application/javascript*|text/javascript*) echo " entry chunk ${MAIN##*/} -> ${CT} ✓" ;; *) echo " ✗ entry chunk ${MAIN##*/} served as '${CT}', not JS — broken deploy"; exit 1 ;; esac echo " ✓ deployed."