#!/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/ 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}) ==="