Two build fixes surfaced while shipping the set-password rename:
1. erpnext/Dockerfile cloned frappe/payments unpinned; its default branch now
requires Python >=3.14 while frappe/erpnext:v15 ships 3.11, so the image
build failed with 'Package payments requires a different Python'. Pin the
clone to --branch version-15.
2. deploy.sh built the erpnext image without first staging the custom Frappe
apps into the build context (erpnext/build.sh). That meant a baked-code
change could silently ship stale code. Stage apps when erpnext is built.
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.