Reverts the shape of the previous commit without reverting its finding. The pull
keeps its own unit -- it can be long, and its duration has no business inside the
container's start time -- and the container keeps After= on it, so the pull still
runs first on a boot where the network is there. What changes is the strength of
the dependency.
Measured on the radio head rather than assumed, because the obvious fix does not
work: Restart=on-failure on the .image unit makes the PULL converge and leaves
the container inactive for good. A failed dependency is never retried -- the
dependent job dies at the first failure, and the later successful restart of the
required unit does not re-trigger it. Upholds= on the pull unit does pick the
container up, at the price of a container that can no longer be stopped on its
own, and it still leaves the container down for as long as the registry is
unreachable even though the image is right there.
So: Image= names the reference, which is what keeps quadlet from generating the
Requires=, and the container declares Wants= + After= on the pull unit itself.
The pull unit gains Restart=on-failure with StartLimitIntervalSec=0, so a machine
that boots without a network ends up with the image instead of a stale one, and
PartOf=zwave-js-ui.target so stopping the target leaves nothing retrying.
Verified on the target, booted with ghcr.io pointed at 127.0.0.1: the container
starts from local storage and reports healthy while the pull unit retries, then
the pull converges on its own after two restarts once resolution is restored, and
nothing is left in systemctl --failed.
The image reference now appears in both files, on purpose. The alternative -- a
drop-in resetting the generated Requires= -- has to restate the container's other
requirements and silently drops any a later edit adds. That trap is worse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A .image unit runs `podman image pull`, which contacts the registry even when
the image is already in local storage. A machine that boots before its network
does fails that pull with "Temporary failure in name resolution", and the
Requires= quadlet generates on the .image unit takes the container down with it.
A failed dependency is never retried -- Restart=always only applies to a unit
that started at least once -- so the service stays down until someone notices.
Observed on the target: the radio head was moved to a basement, powered on, and
cabled some minutes later. It booted, NetworkManager-wait-online declared the
network online 26 s in, the pull failed on DNS, and zwave-js-ui never started
even though the image was sitting in local storage and the controller was
plugged in.
Naming the image in the container lets podman resolve it at start with its
default --pull=missing: the local image starts with no registry involved, and a
genuinely missing image fails the container, which Restart= then retries until
the network is there. Auto-update is unaffected, it reads the container's label.
This is the one deviation from the convention of this repository, and the README
says so and why.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
":Z" relabels what it mounts to container_file_t, recursively and on disk, and
init.sh lives in /etc/quadlets/zwave-js-ui. Mounting that directory into the
container therefore left a script systemd could no longer execute:
zwave-js-ui-init.service failed with 203/EXEC at every boot after the first
start of the container, and the container's Requires= on it kept the container
down too. A machine that survived its installation stopped surviving a reboot.
Found on the target machine, not in review: the first installation works,
because Ignition writes /etc with the right labels and the init service runs
before the container. The fault only appears at the second boot.
The README also gains a "Clearing the store" section. The seed is a first-boot
mechanism, so an operator who deletes settings.json -- after rotating the
security keys, for instance, since Z-Wave JS UI keeps its own copy of the old
ones -- has to restart the init service explicitly. "systemctl restart
zwave-js-ui.target" does not re-run it: it is RemainAfterExit=yes and already
active. Getting that wrong leaves a container unhealthy forever with a happy
driver, and authentication silently off.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Z-Wave JS UI exposes a Z-Wave controller twice: a web control panel on 8091 and
the Z-Wave JS Server websocket on 3000, which is what the Home Assistant
zwave_js integration connects to. MQTT stays off.
The controller is taken as /dev/zwave, a name a udev rule of the site gives it,
so no site value is baked into the cookbook. The container path is spelled out
in AddDevice: a udev SYMLINK is a symbolic link, and a device passed without one
arrives under its resolved name, which is not the name ZWAVE_PORT asks for.
The security keys, the Z-Wave JS Server settings and the log level come from an
operator-provided file through ZWAVE_EXTERNAL_SETTINGS, so Z-Wave JS UI marks
them managed externally and refuses to edit them. The two settings that file
cannot carry -- authEnabled and mqtt.disabled -- are seeded into the store once,
following the home-assistant cookbook pattern.
The health probe sends Accept: text/plain. Without it the single-page-application
fallback answers 200 for any request that accepts HTML, driver or no driver.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>