What changed
Two production-only bugs caught when actually copying real plugin .zips through the v4.0.41 bootstrap on a deployed system. Both fixes match the discipline of "don't trust local Docker — verify on the production-shaped environment."
Bug 1: filename collision swallowed registration silently. If the plugin .zip filename happened to match the plugin's display name (e.g. DeACSM.zip for the DeACSM plugin), calibre-customize -a failed silently — the source got truncated and customize.py.json stayed unchanged. Fix: stage every source through tempfile.NamedTemporaryFile so the destination path can never equal the source. Operator's original .zip stays exactly as they dropped it.
Bug 2: plugin files left root-owned on systems with NETWORK_SHARE_MODE=true. The post-registration chown abc:abc was incorrectly gated on the NSM flag, but /config is always a local Docker volume (NSM is for /calibre-library and /cwa-book-ingest). Calibre-extracted plugin contents stayed root-owned and the abc service user couldn't read them at conversion time. Fix: chown /config/.config/calibre unconditionally.
To get the fix
docker pull ghcr.io/new-usemame/calibre-web-nextgen:v4.0.42
If you're already on v4.0.41 with plugins registered: a pull && up -d --force-recreate will rerun the chown step. The collision fix only matters if you drop a new .zip whose filename matches its display name.
E2E verification
Local Docker reproducer with the exact regression filename:
$ ls plugins/
DeACSM.zip # collision case
DeDRM_plugin.zip
Obok_plugin.zip
$ docker logs cwa-test | grep \"Registered Calibre plugin\"
[cwa-auto-library] Registered Calibre plugin: DeACSM (0, 0, 16)
[cwa-auto-library] Registered Calibre plugin: DeDRM (10, 0, 3)
[cwa-auto-library] Registered Calibre plugin: Obok DeDRM (10, 0, 3)
All 3 plugins land in customize.py.json, all files end up abc:abc-owned.
Anything else off?
Open an issue — the tracker stays watched.
Full changelog: v4.0.41...v4.0.42