deploy: fix recurring portal CSS breakage from ERPNext asset hash drift
The portal serves Frappe assets from a host copy (/opt/erpnext-assets). Frappe emits content-hashed filenames that change on every ERPNext rebuild/migrate; the host copy was never re-synced by deploy.sh, so the manifest referenced hashes that 404'd on the host -> portal rendered with no CSS (recurring issue). - Commit extract-erpnext-assets.sh (was untracked, prod-only). It now also runs bench build to keep assets.json consistent with dist/, copies the manifest, and verifies the login bundle exists on the host before finishing. - deploy.sh: add an 'erpnext' target that rebuilds, runs bench migrate, and re-extracts assets. Plus a cheap drift guard on EVERY deploy that auto-heals by re-extracting if the portal manifest references a missing CSS bundle.
This commit is contained in:
parent
f21f3d41d9
commit
c5e6bdbe6d
2 changed files with 108 additions and 3 deletions
41
deploy.sh
41
deploy.sh
|
|
@ -1,8 +1,10 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Deploy latest code from git and rebuild containers.
|
# Deploy latest code from git and rebuild containers.
|
||||||
# Usage: ./deploy.sh (rebuilds site, api, workers)
|
# Usage: ./deploy.sh (rebuilds site, api, workers)
|
||||||
# ./deploy.sh site (rebuilds only site)
|
# ./deploy.sh site (rebuilds only site)
|
||||||
# ./deploy.sh api (rebuilds only api)
|
# ./deploy.sh api (rebuilds only api)
|
||||||
|
# ./deploy.sh erpnext (rebuild + migrate ERPNext, re-extract assets)
|
||||||
|
# ./deploy.sh api workers (rebuild a custom set)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd /opt/performancewest
|
cd /opt/performancewest
|
||||||
|
|
||||||
|
|
@ -19,11 +21,44 @@ echo ""
|
||||||
echo "=== Restarting: $SERVICES ==="
|
echo "=== Restarting: $SERVICES ==="
|
||||||
docker compose up -d $SERVICES
|
docker compose up -d $SERVICES
|
||||||
|
|
||||||
|
# ── ERPNext: migrate, then ALWAYS re-extract the host asset copy ─────────────
|
||||||
|
# Frappe emits content-hashed asset filenames; an ERPNext rebuild/migrate
|
||||||
|
# changes the hashes. If we don't re-sync the host copy that nginx serves for
|
||||||
|
# portal.performancewest.net, every asset 404s and the portal loses all CSS.
|
||||||
|
# So any time erpnext is (re)built we run bench migrate + re-extract assets.
|
||||||
|
case " $SERVICES " in
|
||||||
|
*" erpnext "*)
|
||||||
|
echo ""
|
||||||
|
echo "=== ERPNext: bench migrate ==="
|
||||||
|
docker compose exec -T erpnext bench --site performancewest.net migrate || \
|
||||||
|
docker compose exec -T erpnext bench migrate || true
|
||||||
|
echo ""
|
||||||
|
echo "=== ERPNext: re-extracting static assets for the portal ==="
|
||||||
|
sudo ./extract-erpnext-assets.sh
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Clearing nginx cache ==="
|
echo "=== Clearing nginx cache ==="
|
||||||
sudo rm -rf /var/cache/nginx/* 2>/dev/null || true
|
sudo rm -rf /var/cache/nginx/* 2>/dev/null || true
|
||||||
sudo nginx -s reload 2>/dev/null || true
|
sudo nginx -s reload 2>/dev/null || true
|
||||||
|
|
||||||
|
# ── Portal asset drift guard ────────────────────────────────────────────────
|
||||||
|
# Cheap safety net on EVERY deploy: if the portal's manifest references a CSS
|
||||||
|
# bundle that is missing from the host copy, the portal CSS is broken — detect
|
||||||
|
# it and auto-heal by re-extracting. This catches drift from any source
|
||||||
|
# (out-of-band ERPNext restarts, image pulls, etc.).
|
||||||
|
if docker inspect performancewest-erpnext-1 >/dev/null 2>&1; then
|
||||||
|
LOGIN_HASH="$(docker exec performancewest-erpnext-1 sh -c \
|
||||||
|
"grep -o 'login.bundle.[A-Z0-9]*.css' /home/frappe/frappe-bench/sites/assets/assets.json | head -1" 2>/dev/null || true)"
|
||||||
|
if [ -n "$LOGIN_HASH" ] && \
|
||||||
|
[ ! -f "/opt/erpnext-assets/assets/frappe/dist/css/${LOGIN_HASH}" ]; then
|
||||||
|
echo ""
|
||||||
|
echo "=== Portal asset drift detected (${LOGIN_HASH} missing) — re-extracting ==="
|
||||||
|
sudo ./extract-erpnext-assets.sh
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Done ==="
|
echo "=== Done ==="
|
||||||
git log --oneline -1
|
git log --oneline -1
|
||||||
|
|
|
||||||
70
extract-erpnext-assets.sh
Executable file
70
extract-erpnext-assets.sh
Executable file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# extract-erpnext-assets.sh — Sync ERPNext/Frappe static assets to the host
|
||||||
|
# directory that nginx serves for portal.performancewest.net.
|
||||||
|
#
|
||||||
|
# WHY THIS EXISTS
|
||||||
|
# The portal serves Frappe's static assets from a host copy at
|
||||||
|
# /opt/erpnext-assets/assets/ (fast, long-cache, off-loads ERPNext). Frappe
|
||||||
|
# emits content-hashed filenames (e.g. login.bundle.SSJHYWGF.css) and a
|
||||||
|
# manifest (sites/assets/assets.json). Every `bench build`/`bench migrate`
|
||||||
|
# (which runs on an ERPNext image rebuild) changes those hashes. If the host
|
||||||
|
# copy is not re-synced, the HTML references new hashes that 404 on the host
|
||||||
|
# → the portal renders with NO CSS.
|
||||||
|
#
|
||||||
|
# This script re-extracts the current assets so the host copy and the
|
||||||
|
# manifest always match. It is idempotent and safe to run any time.
|
||||||
|
#
|
||||||
|
# RUN IT after: an ERPNext image rebuild, `bench build`, or `bench migrate`.
|
||||||
|
# deploy.sh calls it automatically for the `erpnext` target.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CONTAINER="${ERPNEXT_CONTAINER:-performancewest-erpnext-1}"
|
||||||
|
DEST="${ERPNEXT_ASSETS_DIR:-/opt/erpnext-assets/assets}"
|
||||||
|
|
||||||
|
# docker may need sudo depending on the host; honor a DOCKER override.
|
||||||
|
DOCKER="${DOCKER:-sudo -n docker}"
|
||||||
|
|
||||||
|
echo "=== Extracting ERPNext assets from ${CONTAINER} -> ${DEST} ==="
|
||||||
|
|
||||||
|
if ! $DOCKER inspect "$CONTAINER" >/dev/null 2>&1; then
|
||||||
|
echo "ERROR: container ${CONTAINER} not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure the in-container manifest matches the built bundles. A bare image
|
||||||
|
# rebuild can leave assets.json stale relative to dist/, which also breaks CSS.
|
||||||
|
# `bench build` regenerates both consistently; cheap no-op if already current.
|
||||||
|
echo "--- Ensuring assets are built (bench build) ---"
|
||||||
|
$DOCKER exec "$CONTAINER" bench build >/tmp/erpnext-build.log 2>&1 || {
|
||||||
|
echo "WARN: bench build returned non-zero; see /tmp/erpnext-build.log" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
sudo rm -rf "${DEST}/frappe" "${DEST}/erpnext"
|
||||||
|
sudo mkdir -p "$DEST"
|
||||||
|
|
||||||
|
# frappe + erpnext public/ dirs (sites/assets/<app> symlinks here).
|
||||||
|
$DOCKER exec "$CONTAINER" tar cf - \
|
||||||
|
-C /home/frappe/frappe-bench/apps/frappe/frappe public \
|
||||||
|
| sudo tar xf - -C "$DEST" --transform='s|^public|frappe|'
|
||||||
|
$DOCKER exec "$CONTAINER" tar cf - \
|
||||||
|
-C /home/frappe/frappe-bench/apps/erpnext/erpnext public \
|
||||||
|
| sudo tar xf - -C "$DEST" --transform='s|^public|erpnext|'
|
||||||
|
|
||||||
|
# The manifest nginx/Frappe read for hash->file lookups.
|
||||||
|
$DOCKER exec "$CONTAINER" cat \
|
||||||
|
/home/frappe/frappe-bench/sites/assets/assets.json \
|
||||||
|
| sudo tee "${DEST}/assets.json" >/dev/null
|
||||||
|
|
||||||
|
sudo chown -R www-data:www-data /opt/erpnext-assets
|
||||||
|
sudo nginx -s reload 2>/dev/null || true
|
||||||
|
|
||||||
|
# Verify the manifest's login bundle actually exists on the host now.
|
||||||
|
LOGIN_HASH="$($DOCKER exec "$CONTAINER" sh -c \
|
||||||
|
"grep -o 'login.bundle.[A-Z0-9]*.css' /home/frappe/frappe-bench/sites/assets/assets.json | head -1" || true)"
|
||||||
|
if [ -n "$LOGIN_HASH" ] && [ ! -f "${DEST}/frappe/dist/css/${LOGIN_HASH}" ]; then
|
||||||
|
echo "ERROR: manifest references ${LOGIN_HASH} but it is missing on host." >&2
|
||||||
|
echo " Portal CSS would be broken. Aborting." >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Done. Assets at ${DEST} (login bundle: ${LOGIN_HASH:-unknown}) ==="
|
||||||
Loading…
Add table
Add a link
Reference in a new issue