Self-update covers Core, the updater sidecar, the Optimizer and signed Lua
drivers. It covers nothing else. After a site is installed, no code path in
this repository ever patches the host operating system — not on the flashed
Raspberry Pi image, not via scripts/install.sh, not from the running
application.
That is a defensible design decision, but right now it is an undocumented one,
and on the appliance image it is almost certainly not what an owner assumes when
they see an "Update" button in Settings → System.
What is and is not kept current
| Layer |
Kept current? |
By what |
| Core + updater sidecar |
yes |
Update Center / ftw-updater |
| Optimizer image |
yes |
Update Center, independent tag stream |
| Signed Lua drivers |
yes |
Update Center, per-driver artifacts |
| Userland inside the containers |
indirectly |
each release rebuilds on alpine:3.22 / python:3.12-slim-bookworm |
| Host kernel, OpenSSH, systemd, NetworkManager, avahi, curl |
no |
nothing |
| Docker engine on the host |
no |
nothing — and on the image, not even manually (see below) |
Evidence
- No match anywhere in the tree for
unattended|apt-get upgrade|apt upgrade|dist-upgrade|needrestart.
scripts/install.sh touches apt exactly once, at install time, to add
docker-compose-plugin and uidmap (lines 122–132). Nothing recurring is
installed, no timer, no unit.
deploy/pi-gen/stage-ftw/01-ftw-setup/files/firstboot.sh runs
docker compose pull + up -d and nothing else. No apt step on first boot or
any boot after it.
- Nothing under
go/ reads /etc/os-release or shells out to a package
manager, so Core cannot even report host patch state, let alone act on it.
The Pi image ships with automatic updates absent, not disabled
deploy/pi-gen/config builds STAGE_LIST="stage0 stage1 stage2 stage-ftw" on
Raspberry Pi OS trixie, with pi-gen pinned to
ca8aeed0ae300c2a89f55ce9617d5f96a27e99e5. unattended-upgrades is in none of
the package lists of those stages, and stage-ftw does not add it. So a flashed
Pi never applies a security update unless somebody SSHes in and runs apt by hand.
Package lists checked at the pinned pi-gen commit
Verified against RPi-Distro/pi-gen@ca8aeed0:
stage0/00-configure-apt/01-packages raspberrypi-archive-keyring
stage0/01-locale/00-packages locales
stage0/02-firmware/01-packages initramfs-tools raspi-firmware linux-image-rpi-v8 …
stage1/01-sys-tweaks/00-packages raspi-config
stage1/02-net-tweaks/00-packages netbase
stage1/03-install-packages/00-packages systemd-timesyncd
stage2/01-sys-tweaks/00-packages ssh less fbset sudo … apt-listchanges … (no unattended-upgrades)
stage2/01-sys-tweaks/00-packages-nr cifs-utils rpicam-apps-lite mkvtoolnix
stage2/02-net-tweaks/00-packages wpasupplicant wireless-tools firmware-* network-manager net-tools
Plus FTW's own deploy/pi-gen/stage-ftw/00-install-packages/00-packages:
avahi-daemon ca-certificates curl jq network-manager raspberrypi-sys-mods.
apt-listchanges is present, which notifies about changes during an upgrade —
it does not perform one.
The image cannot apt-upgrade its Docker engine at all
deploy/pi-gen/stage-ftw/01-ftw-setup/00-run.sh:38 deletes
/etc/apt/sources.list.d/docker.list after installing docker-ce,
docker-ce-cli, containerd.io and docker-compose-plugin.
The reason given is sound for the build — leaving Docker's third-party source
enabled has repeatedly OOMed pi-gen's export-image step under qemu on hosted
runners. But the consequence lands on every flashed device: the shipped system
has no Docker apt source, so the engine is frozen at whatever version the image
was built with, and an operator who wants to patch it has to re-add the
keyring and repo first. A container runtime that never receives updates for the
service life of the appliance deserves to be an explicit decision rather than a
side effect of a build workaround.
Why this is worth a decision
The appliance profile makes the usual "the operator patches their own host"
answer weaker than it looks:
- The device is sold/flashed as an appliance and is expected to run unattended
for years, in homes without a sysadmin.
- SSH is enabled by default (
ENABLE_SSH=1), with FIRST_USER_NAME=ftw /
FIRST_USER_PASS=ftw as the raw-flash fallback when Imager customisation is
not used.
- The dashboard has no read-authentication boundary, so the security posture
already rests on the LAN and on the host being sound.
- It sits on the home network permanently, which is exactly the exposure profile
an unpatched OpenSSH or kernel CVE is dangerous in.
The counter-pressure is real and is why this is an issue rather than a
drive-by PR: this is an energy controller, and a reboot stops dispatch.
Anything that lands here has to treat rebooting as a safety-relevant action, not
a maintenance detail.
Possible directions
Not a proposed design — the scope call belongs to whoever owns the image.
- Document the status quo. Smallest change: state plainly in
docs/operations.md and docs/rpi-image.md that host patching is the
operator's responsibility and that self-update does not cover it. Worth doing
regardless of which other option wins.
- Enable
unattended-upgrades in stage-ftw, security pocket only, with
automatic reboot off. Contained, image-only, no change to Core. Leaves
kernel updates staged until an operator-chosen reboot.
- Decide the Docker engine question separately. Either keep the apt source
on the installed system (re-adding it at first boot, after the fragile
export-image step is done) or state explicitly that the engine is pinned for
the image's lifetime.
- Surface host patch state read-only in Update Center. Much larger: Core
deliberately has no host visibility and no Docker socket, so this crosses a
boundary the architecture currently protects. Probably wants its own issue if
anyone wants it at all.
Options 2–4 apply to the appliance image. For scripts/install.sh on an
operator's own Debian/Ubuntu host, and for the Home Assistant add-on, "the host
is yours" is the right answer — but option 1 should still say so out loud.
Open questions
- Is automatic host patching in scope for the appliance image, or is FTW
deliberately only responsible for its own components?
- If security updates auto-apply, what is the reboot policy for pending kernel
updates on a controller that is actively dispatching?
- Should host patch state be visible in the UI, or is that an SSH-only concern?
Related: #730 audits the container base image. This issue is about the host
underneath it — separate layer, separate decision.
Self-update covers Core, the updater sidecar, the Optimizer and signed Lua
drivers. It covers nothing else. After a site is installed, no code path in
this repository ever patches the host operating system — not on the flashed
Raspberry Pi image, not via
scripts/install.sh, not from the runningapplication.
That is a defensible design decision, but right now it is an undocumented one,
and on the appliance image it is almost certainly not what an owner assumes when
they see an "Update" button in Settings → System.
What is and is not kept current
ftw-updateralpine:3.22/python:3.12-slim-bookwormEvidence
unattended|apt-get upgrade|apt upgrade|dist-upgrade|needrestart.scripts/install.shtouches apt exactly once, at install time, to adddocker-compose-pluginanduidmap(lines 122–132). Nothing recurring isinstalled, no timer, no unit.
deploy/pi-gen/stage-ftw/01-ftw-setup/files/firstboot.shrunsdocker compose pull+up -dand nothing else. No apt step on first boot orany boot after it.
go/reads/etc/os-releaseor shells out to a packagemanager, so Core cannot even report host patch state, let alone act on it.
The Pi image ships with automatic updates absent, not disabled
deploy/pi-gen/configbuildsSTAGE_LIST="stage0 stage1 stage2 stage-ftw"onRaspberry Pi OS trixie, with pi-gen pinned to
ca8aeed0ae300c2a89f55ce9617d5f96a27e99e5.unattended-upgradesis in none ofthe package lists of those stages, and
stage-ftwdoes not add it. So a flashedPi never applies a security update unless somebody SSHes in and runs apt by hand.
Package lists checked at the pinned pi-gen commit
Verified against
RPi-Distro/pi-gen@ca8aeed0:Plus FTW's own
deploy/pi-gen/stage-ftw/00-install-packages/00-packages:avahi-daemon ca-certificates curl jq network-manager raspberrypi-sys-mods.apt-listchangesis present, which notifies about changes during an upgrade —it does not perform one.
The image cannot apt-upgrade its Docker engine at all
deploy/pi-gen/stage-ftw/01-ftw-setup/00-run.sh:38deletes/etc/apt/sources.list.d/docker.listafter installingdocker-ce,docker-ce-cli,containerd.ioanddocker-compose-plugin.The reason given is sound for the build — leaving Docker's third-party source
enabled has repeatedly OOMed pi-gen's
export-imagestep under qemu on hostedrunners. But the consequence lands on every flashed device: the shipped system
has no Docker apt source, so the engine is frozen at whatever version the image
was built with, and an operator who wants to patch it has to re-add the
keyring and repo first. A container runtime that never receives updates for the
service life of the appliance deserves to be an explicit decision rather than a
side effect of a build workaround.
Why this is worth a decision
The appliance profile makes the usual "the operator patches their own host"
answer weaker than it looks:
for years, in homes without a sysadmin.
ENABLE_SSH=1), withFIRST_USER_NAME=ftw/FIRST_USER_PASS=ftwas the raw-flash fallback when Imager customisation isnot used.
already rests on the LAN and on the host being sound.
an unpatched OpenSSH or kernel CVE is dangerous in.
The counter-pressure is real and is why this is an issue rather than a
drive-by PR: this is an energy controller, and a reboot stops dispatch.
Anything that lands here has to treat rebooting as a safety-relevant action, not
a maintenance detail.
Possible directions
Not a proposed design — the scope call belongs to whoever owns the image.
docs/operations.mdanddocs/rpi-image.mdthat host patching is theoperator's responsibility and that self-update does not cover it. Worth doing
regardless of which other option wins.
unattended-upgradesinstage-ftw, security pocket only, withautomatic reboot off. Contained, image-only, no change to Core. Leaves
kernel updates staged until an operator-chosen reboot.
on the installed system (re-adding it at first boot, after the fragile
export-imagestep is done) or state explicitly that the engine is pinned forthe image's lifetime.
deliberately has no host visibility and no Docker socket, so this crosses a
boundary the architecture currently protects. Probably wants its own issue if
anyone wants it at all.
Options 2–4 apply to the appliance image. For
scripts/install.shon anoperator's own Debian/Ubuntu host, and for the Home Assistant add-on, "the host
is yours" is the right answer — but option 1 should still say so out loud.
Open questions
deliberately only responsible for its own components?
updates on a controller that is actively dispatching?
Related: #730 audits the container base image. This issue is about the host
underneath it — separate layer, separate decision.