The config that deploys itself
Home Assistant pulls its configuration from git every five minutes. No deploy job reaches it.
A pull, not a push
The hub’s container carries a forked deploy integration that watches the
main branch, fetches it every five minutes, and merges it into its own
configuration directory. There is no deploy job and no login seam: nothing in CI
reaches the running instance.
After a pull an event fires and a git-tracked automation applies it — a
reload, or a full restart when the core configuration or an integration’s code
changed. A merge to main can restart the house.
What CI checks
Configuration validation runs Home Assistant’s own check inside the official image, pinned by tag and digest to the version production runs, and it blocks the merge request. Home Assistant logs a schema-invalid item, disables just that item and carries on, so only a syntax error or a core-block failure exits non-zero. The captured log is therefore searched for the failure strings as well. A separate job scans the full git history for secrets.
Every integration’s config entry and every credential live in the hub’s own storage, deliberately untracked.
The third-party integrations installed on the hub update themselves in a weekly window on Sunday morning. The window is narrow on purpose. It skips firmware of every kind, because an over-the-air update would overwrite the lamp firmware written here, wipe the credentials off the sensor nodes and push a development build onto the radio coordinator. It skips an update that reboots the NAS. And it skips the handful of components carrying local patches, because installing over one of those clobbers the patch.
On 2026-08-24 the vendor’s own update entity was pressed; it flashed the stock factory image, which carries no credentials for anything to reach the hardware with, and all four Apollo multi-sensor nodes left the network at once. That entity and the vendor’s firmware button are now hidden on all eight vendor nodes — hiding the entity alone leaves the identical flash one press away, because the button calls it. The nodes have since gone further than hiding: the firmware’s own web updater is disabled outright and the fallback access point each node raises when it cannot find the network now carries a password, so the same flash is no longer reachable over the air at all.
What is missing, and what is planned
- The off-box backup has been decrypted and booted in isolation; a from-scratch rebuild onto an empty box has not. The bootstrap restores the untracked state — from the off-box backup, decrypted with the emergency key held for it — onto an empty configuration directory, and the sync takes over from there.
- Planned: one pass over the physical controls — inventory every button, dial and pad, then decide the mapping per room alongside retiring the lighting behaviours still running inside a vendor bridge, so two systems never drive the same lamps.