From dce163b263caab0989552d1ff680f0844cfad0ac Mon Sep 17 00:00:00 2001 From: Nicolas MASSE Date: Sat, 26 Sep 2026 17:08:41 +0200 Subject: [PATCH] zwave-js-ui: name the image in the container, drop the .image unit 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 --- cookbooks/zwave-js-ui/README.md | 14 +++++++++++++- cookbooks/zwave-js-ui/zwave-js-ui.container | 16 +++++++++++++++- cookbooks/zwave-js-ui/zwave-js-ui.image | 12 ------------ 3 files changed, 28 insertions(+), 14 deletions(-) delete mode 100644 cookbooks/zwave-js-ui/zwave-js-ui.image diff --git a/cookbooks/zwave-js-ui/README.md b/cookbooks/zwave-js-ui/README.md index 2cc9904..763c72a 100644 --- a/cookbooks/zwave-js-ui/README.md +++ b/cookbooks/zwave-js-ui/README.md @@ -27,7 +27,8 @@ This cookbook: - Supports automatic container image updates via Podman auto-update. The image is `ghcr.io/zwave-js/zwave-js-ui:11`, a multi-architecture manifest (amd64 + -arm64, verified 2026-09-26, version 11.24.1). +arm64, verified 2026-09-26, version 11.24.1). It is named in the container unit rather than in +a `.image` unit — see the deviations below, and the reason is boot resilience, not taste. ## What this cookbook does NOT do @@ -198,6 +199,17 @@ 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. +- **No `.image` unit: the image is named in the container.** 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 — a power cut, a switch slower than the + machine, a cable plugged in after power-on — fails that pull with + `Temporary failure in name resolution`, and the `Requires=` that quadlet generates on the + `.image` unit takes the container down with it. **A failed dependency is never retried**: + `Restart=always` only ever applies to a unit that started at least once, so the service stays + down until someone notices. Naming the image in the container instead 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. - **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 diff --git a/cookbooks/zwave-js-ui/zwave-js-ui.container b/cookbooks/zwave-js-ui/zwave-js-ui.container index 38e5e26..5879a0e 100644 --- a/cookbooks/zwave-js-ui/zwave-js-ui.container +++ b/cookbooks/zwave-js-ui/zwave-js-ui.container @@ -21,7 +21,21 @@ PartOf=zwave-js-ui.target [Container] ContainerName=zwave-js-ui -Image=zwave-js-ui.image +# The image reference, and NOT a zwave-js-ui.image unit -- the one deviation from +# the convention of this repository, for a reason this cookbook has already paid +# for. A .image unit runs `podman image pull`, which contacts the registry even +# when the image is already in local storage. A radio head that boots before its +# network does -- a power cut, a switch that is slower than it is, a cable +# plugged in after the machine -- therefore fails that pull, and the generated +# `Requires=` on it takes the container down with it. A failed dependency is +# never retried: Restart=always only ever applies to a unit that started once. +# +# Named here instead, podman resolves the image at container start with its +# default --pull=missing: the local image starts with no registry involved, and a +# genuinely missing image fails the container, which Restart= below then retries +# until the network is there. Auto-update still works -- it reads the label of +# the container, not the .image unit. +Image=ghcr.io/zwave-js/zwave-js-ui:11 AutoUpdate=registry # No need for root privileges: the image has no entrypoint script, it execs node diff --git a/cookbooks/zwave-js-ui/zwave-js-ui.image b/cookbooks/zwave-js-ui/zwave-js-ui.image deleted file mode 100644 index 0e57b7c..0000000 --- a/cookbooks/zwave-js-ui/zwave-js-ui.image +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=podman pull ghcr.io/zwave-js/zwave-js-ui -Documentation=https://zwave-js.github.io/zwave-js-ui/ - -[Image] -# Multi-architecture manifest (amd64 + arm64, verified 2026-09-26 on tag 11 -> -# 11.24.1), so the cookbook installs on an aarch64 host as well as on the x86_64 -# radio head. The tag is the major version: Z-Wave JS UI ships the zwave-js -# driver and its Z-Wave JS Server, whose schema version is what Home Assistant -# negotiates. A major bump is the one that can break that handshake, so it is -# the one an operator has to opt into. -Image=ghcr.io/zwave-js/zwave-js-ui:11