diff --git a/cookbooks/home-assistant/README.md b/cookbooks/home-assistant/README.md index 1b10351..e6d30be 100644 --- a/cookbooks/home-assistant/README.md +++ b/cookbooks/home-assistant/README.md @@ -40,9 +40,12 @@ templates). | `/etc/quadlets/home-assistant/configuration.yaml` | Bootstrap Home Assistant configuration (copied to `/config`). | | `/etc/quadlets/home-assistant/secrets.yaml` | Bootstrap secrets, incl. the recorder database URL + password. | -Nothing starts until `/etc/quadlets/home-assistant/configuration.yaml` exists (the -`home-assistant.target`, `home-assistant.service` and `home-assistant-init.service` units -all guard on it with `ConditionPathExists`). +`home-assistant-init.service` only bootstraps `/config` when +`/etc/quadlets/home-assistant/configuration.yaml` is present; `home-assistant.service` only +starts once `/config/configuration.yaml` exists (created by the init unit on first boot, or +already present if Home Assistant was configured before). So on a fresh system nothing runs +until the operator provides the bootstrap files, and once Home Assistant owns `/config` the +files under `/etc/quadlets/home-assistant/` are no longer required. Ready-to-use examples are provided under `config/examples/`. They are installed to `/etc/quadlets/home-assistant/` during development and testing only, and are **not** part diff --git a/cookbooks/home-assistant/config/init.sh b/cookbooks/home-assistant/config/init.sh new file mode 100755 index 0000000..32346ac --- /dev/null +++ b/cookbooks/home-assistant/config/init.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# +# Bootstrap Home Assistant's /config on first start. +# +# Home Assistant owns /config once it has started: after first boot the live +# copies win and nothing here is touched again. This script: +# - copies the operator's bootstrap configuration.yaml / secrets.yaml from +# /etc/quadlets/home-assistant into /config only if they are absent; +# - creates the empty include targets configuration.yaml references +# (automations.yaml / scenes.yaml / scripts.yaml) so Home Assistant does not +# fall back to recovery mode on an otherwise-empty /config. +# +set -eu + +SRC=/etc/quadlets/home-assistant +DST=/var/lib/virtiofs/data/home-assistant +HA_UID=10034 +HA_GID=10000 + +install_if_absent() { + if [ -f "$SRC/$1" ] && [ ! -e "$DST/$1" ]; then + echo "Bootstrapping $DST/$1 from $SRC/$1" + install -m "$2" -o "$HA_UID" -g "$HA_GID" "$SRC/$1" "$DST/$1" + else + echo "Keeping existing $DST/$1 (Home Assistant owns it) or no bootstrap provided" + fi +} + +stub_if_absent() { + if [ ! -e "$DST/$1" ]; then + echo "Creating empty $DST/$1" + echo "$2" > "$DST/$1" + chown "$HA_UID:$HA_GID" "$DST/$1" + fi +} + +install_if_absent configuration.yaml 0644 +install_if_absent secrets.yaml 0600 +stub_if_absent automations.yaml "[]" +stub_if_absent scenes.yaml "[]" +stub_if_absent scripts.yaml "{}" diff --git a/cookbooks/home-assistant/home-assistant-init.service b/cookbooks/home-assistant/home-assistant-init.service index 72f20f1..e1bd506 100644 --- a/cookbooks/home-assistant/home-assistant-init.service +++ b/cookbooks/home-assistant/home-assistant-init.service @@ -15,36 +15,10 @@ Before=home-assistant.service Type=oneshot RemainAfterExit=yes -# Bootstrap /config ONLY where a file is absent. Home Assistant owns /config once -# it has started: after first boot the live copies win and nothing here is touched -# again. install_if_absent copies the operator's bootstrap files; stub_if_absent -# creates the empty include targets configuration.yaml references -# (automations.yaml / scenes.yaml / scripts.yaml) so Home Assistant does not fall -# back to recovery mode on an otherwise-empty /config. -ExecStart=/bin/sh -c '\ - set -eu; \ - src=/etc/quadlets/home-assistant; \ - dst=/var/lib/virtiofs/data/home-assistant; \ - install_if_absent() { \ - if [ -f "$src/$1" ] && [ ! -e "$dst/$1" ]; then \ - echo "Bootstrapping $dst/$1 from $src/$1"; \ - install -m "$2" -o 10034 -g 10000 "$src/$1" "$dst/$1"; \ - else \ - echo "Keeping existing $dst/$1 (Home Assistant owns it) or no bootstrap provided"; \ - fi; \ - }; \ - stub_if_absent() { \ - if [ ! -e "$dst/$1" ]; then \ - echo "Creating empty $dst/$1"; \ - echo "$2" > "$dst/$1"; \ - chown 10034:10000 "$dst/$1"; \ - fi; \ - }; \ - install_if_absent configuration.yaml 0644; \ - install_if_absent secrets.yaml 0600; \ - stub_if_absent automations.yaml "[]"; \ - stub_if_absent scenes.yaml "[]"; \ - stub_if_absent scripts.yaml "{}"' +# Bootstrap /config from the operator's templates (see init.sh for details). The +# script only ever creates files that are absent, so Home Assistant's live copies +# are never overwritten. +ExecStart=/etc/quadlets/home-assistant/init.sh [Install] WantedBy=home-assistant.target diff --git a/cookbooks/home-assistant/home-assistant.container b/cookbooks/home-assistant/home-assistant.container index af01ab2..5876434 100644 --- a/cookbooks/home-assistant/home-assistant.container +++ b/cookbooks/home-assistant/home-assistant.container @@ -4,8 +4,11 @@ Documentation=https://www.home-assistant.io/ After=network.target var-lib-virtiofs-data.mount home-assistant-init.service Requires=var-lib-virtiofs-data.mount home-assistant-init.service -# Only start if Home Assistant has been configured -ConditionPathExists=/etc/quadlets/home-assistant/configuration.yaml +# Only start once Home Assistant has been bootstrapped: the live configuration +# lives in /config (populated by home-assistant-init.service on first boot, or +# already present if Home Assistant was configured before). Past that point the +# bootstrap files in /etc/quadlets are no longer needed. +ConditionPathExists=/var/lib/virtiofs/data/home-assistant/configuration.yaml # Start/stop this unit when the target is started/stopped PartOf=home-assistant.target diff --git a/cookbooks/home-assistant/home-assistant.target b/cookbooks/home-assistant/home-assistant.target index 98d5c43..1c91576 100644 --- a/cookbooks/home-assistant/home-assistant.target +++ b/cookbooks/home-assistant/home-assistant.target @@ -6,8 +6,6 @@ After=postgresql.target home-assistant-init.service home-assistant.service # Allow isolation - can stop/start this target independently AllowIsolate=yes -# Only start if Home Assistant has been configured -ConditionPathExists=/etc/quadlets/home-assistant/configuration.yaml [Install] WantedBy=multi-user.target