Skip to content

Nothing patches the host OS after install — self-update covers FTW's own containers only #770

Description

@HuggeK

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions