diff --git a/cookbooks/zwave-js-ui/README.md b/cookbooks/zwave-js-ui/README.md index d6fb03e..2cc9904 100644 --- a/cookbooks/zwave-js-ui/README.md +++ b/cookbooks/zwave-js-ui/README.md @@ -198,8 +198,40 @@ a restart loop fixes. artifact. What the external file buys is not secrecy: it is that the authoritative copy lives outside the application, is restorable from a vault, and cannot be edited from the web interface. +- **Only `external-settings.json` is mounted into the container, never the configuration + directory.** `:Z` relabels what it mounts to `container_file_t`, recursively and on disk, and + `init.sh` lives in that directory. Mounting the directory therefore leaves a script systemd + can no longer execute: `zwave-js-ui-init.service` fails with `203/EXEC` at every boot *after + the first start of the container*, and the container's `Requires=` on it keeps the container + down too. A machine that survived its installation stops surviving a reboot, which is the + worst shape a fault can take. The `mosquitto` cookbook mounts its whole directory and is fine + because it keeps no script there; this one does. - **No Traefik and no TLS.** See "What this cookbook does NOT do". +## Clearing the store + +The seed is a **first-boot** mechanism: `init.sh` writes `store/settings.json` only when it is +absent, because Z-Wave JS UI owns that file afterwards. Two consequences for an operator who +wipes the store, or who deletes `settings.json` to force the settings of +`external-settings.json` back in — after rotating the security keys, for instance, since +Z-Wave JS UI keeps its own copy of the old ones: + +- **Delete the file with the container stopped, then restart the init service explicitly**, in + this order. `systemctl restart zwave-js-ui.target` is not enough: the init service is + `RemainAfterExit=yes` and already active, so it does not run again, and Z-Wave JS UI then + writes a `settings.json` that holds the external settings and **not** the seeded ones. +- The symptom of getting it wrong is a container that is `unhealthy` forever with a driver that + is perfectly happy: no `mqtt` section means no MQTT client, and `/health` answers 500. And, + more quietly, `gateway.authEnabled` is gone with it, so the web interface stops asking for a + password. + +```sh +systemctl stop zwave-js-ui.service +rm /var/lib/virtiofs/data/zwave-js-ui/settings.json +systemctl restart zwave-js-ui-init.service # seeds it again +systemctl start zwave-js-ui.service +``` + ## Usage In a separate terminal, follow the logs. diff --git a/cookbooks/zwave-js-ui/zwave-js-ui.container b/cookbooks/zwave-js-ui/zwave-js-ui.container index 6c28993..38e5e26 100644 --- a/cookbooks/zwave-js-ui/zwave-js-ui.container +++ b/cookbooks/zwave-js-ui/zwave-js-ui.container @@ -59,9 +59,19 @@ Environment=STORE_DIR=/var/lib/virtiofs/data/zwave-js-ui # SESSION_SECRET and the initial administrator credentials (site secrets). EnvironmentFile=/etc/quadlets/zwave-js-ui/zwave-js-ui.env -# Configuration (read-only), at the same path inside and outside the container so -# that ZWAVE_EXTERNAL_SETTINGS names one path only. -Volume=/etc/quadlets/zwave-js-ui:/etc/quadlets/zwave-js-ui:ro,Z +# Configuration (read-only): the ONE file the container reads, at the same path +# inside and outside so that ZWAVE_EXTERNAL_SETTINGS names one path only. +# +# Never mount the whole /etc/quadlets/zwave-js-ui directory here. ":Z" relabels +# what it mounts to container_file_t, recursively and on disk, and init.sh lives +# in that directory: systemd can no longer execute it, so +# zwave-js-ui-init.service fails 203/EXEC at every boot after the first start of +# this container, and the Requires= above then keeps the container down with it. +# A machine that survived its install stops surviving a reboot. +# +# ConditionPathExists on this file above is also what keeps podman from creating +# a DIRECTORY at this path when the operator has not provided the file. +Volume=/etc/quadlets/zwave-js-ui/external-settings.json:/etc/quadlets/zwave-js-ui/external-settings.json:ro,Z # Precious data: the store holds the node names, the associations, the NVM # backups of the controller and the users of the web interface. It lives on the