Loading...
E.G.

Home Assistant: Packages statt Include-Dateien

28. Oktober 2026
Teil 2 von 2Serie: Umzug eines Hauses auf eine neue Home-Assistant-InstanzAlle Teile
  1. 01Home-Assistant-Umzug: alte und neue Instanz parallel
  2. 02Home Assistant: Packages statt Include-Dateien
Inhaltsverzeichnis

Die Home-Assistant-Config, von der ich gerade wegziehe, ist nach Typ aufgeteilt, so wie es die meisten Anleitungen zeigen: input_boolean.yaml, input_datetime.yaml, templates.yaml und eine Datei pro Kategorie unter automations/. Zusammen sind das rund 4.800 Zeilen YAML, davon rund 2.900 Zeilen Automationen in 13 Dateien. Für die neue Instanz, die mit 2026.8.2 in Docker auf der Maschine läuft, auf der ich das Setup neu aufgebaut habe, habe ich diese Aufteilung aufgegeben und alles in Packages gelegt, eine Datei pro Bereich. Der Preis dafür ist, dass sich Package-Automationen nur im YAML bearbeiten lassen. Die Dateien liefere ich mit einem Deploy-Skript aus, das die Konfiguration prüft und bei einer fehlgeschlagenen Prüfung die vorherigen Dateien wiederherstellt.

Wo die Aufteilung nach Typ nicht mehr geholfen hat

Ich habe früher darüber geschrieben, wie ich die automations.yaml in Kategorie-Dateien aufgeteilt habe, und für das damalige Problem hat das funktioniert. Ich konnte die Heizungsautomationen öffnen, ohne an allem anderen vorbeizuscrollen. Aus den acht Dateien von damals sind inzwischen 13 geworden. Diese Aufteilung hat immer nur die Automationen erfasst. Die Helfer blieben in input_boolean.yaml, die Zeitfenster in input_datetime.yaml und die Anzeige-Sensoren in templates.yaml, sodass ein Bereich des Hauses über fünf bis sechs Dateien verstreut war.

Relevant wurde das mit dem Umzug, weil ich die Logik schrittweise von der alten auf die neue Instanz übertrage. Ich will einen Bereich komplett übernehmen, testen und umschalten. Bei der alten Aufteilung muss ich die Helfer, Templates und Automationen eines Bereichs erst aus fünf bis sechs Dateien zusammensuchen, bevor ich ihn umziehen kann.

Ein Package pro Bereich

Ein Package bündelt alles, was ein Bereich braucht, in einer Datei: Input-Helfer, Template-Sensoren, Automationen, Skripte und bei Bedarf Integrationen wie modbus oder rest. Eine Zeile in der configuration.yaml bindet das ganze Verzeichnis ein, und der Dateiname wird zum Package-Namen. Das Beispiel unten ist gekürzt und verwendet Platzhalternamen, meine echten Packages sind aber genauso aufgebaut.

# configuration.yaml
homeassistant:
  packages: !include_dir_named packages   # Dateiname = Package-Name

# packages/beispielbereich.yaml (gekürzt, Platzhalternamen)
input_boolean:
  beispielbereich_aktiv:
    name: Beispielbereich aktiv

template:
  - sensor:
      - name: "Beispielbereich Status"
        state: "{{ 'on' if is_state('input_boolean.beispielbereich_aktiv', 'on') else 'off' }}"

automation:
  - id: beispielbereich_abend
    alias: "Beispielbereich: Abendroutine"
    initial_state: false          # Schattenbetrieb, solange die alte Instanz das noch ausführt
    triggers:
      - trigger: time
        at: "19:00:00"
    actions:
      - action: light.turn_on
        target:
          entity_id: light.beispiel

Als Erstes habe ich die Demo-Daten umgestellt, die die neue Instanz bis dahin hatte, 21 Template-Entitäten. Vorher waren sie über ein einzelnes !include eingebunden, jetzt stehen sie wie alles andere in einem Package, in packages/demo.yaml. Danach bekommt jeder Bereich der alten Instanz bei der Übernahme seine eigene Datei.

Der Preis: kein UI-Editor für Package-Automationen

Automationen aus einem Package lassen sich nicht im UI-Editor bearbeiten. Sie erscheinen in der Oberfläche, und ich kann ihre Traces lesen, speichern geht aber nur im YAML. Das passt zu meinem Ablauf, weil ich Automationen im Repository ändere und von dort ausrolle. Wer Automationen hauptsächlich zusammenklickt, sollte das vor dem Umstieg abwägen.

Schattenbetrieb, solange zwei Instanzen laufen

Sobald die neue Instanz am KNX hängt, teilen sich beide Instanzen für den Rest des Umzugs den Bus. Läuft dieselbe Automation auf beiden, passiert jede Aktion doppelt, Rollos fahren also doppelt, und es gehen zwei Push-Nachrichten raus. Jede übernommene Automation kommt deshalb mit initial_state: false auf die neue Instanz, was ich Schattenbetrieb nenne. Umgeschaltet wird pro Bereich und immer in derselben Reihenfolge: Ich deaktiviere die Automationen des Bereichs zuerst auf der alten Instanz und aktiviere sie danach auf der neuen. Die Zeile initial_state: false muss dabei raus, sonst schaltet der nächste Neustart sie wieder ab. Der vorige Beitrag dieser Serie beschreibt die ersten Schritte dieses Parallelbetriebs.

Die Bereiche habe ich nach Risiko sortiert. Energie und PV kommen zuerst, weil dieser Bereich nur liest und nichts steuert, dann Helfer und Zeitsteuerung, Licht, Rollos, Heizung und danach Kameras und Benachrichtigungen. Die neue Maschine hatte bei diesem ersten Schritt noch keine Verbindung zum Haus. Ich konnte die Automationen also übertragen und die Konfiguration prüfen, aber noch nichts mit den Geräten im Haus testen.

Das Deploy-Skript

Auf der alten Instanz hieß Deploy, Dateien per scp nach /tmp zu kopieren und dann mit sudo cp in den Config-Ordner zu legen, von Hand. Die neue Instanz bekommt ein Skript im Repository mit vier Schritten. Es sichert configuration.yaml und packages/ auf der Maschine und behält die letzten 10 Stände. Es überträgt per rsync nur die Dateien, die im Git gepflegt sind, sodass Secrets, .storage und die per HACS installierten Themes und Karten unberührt bleiben. Es führt check_config im Container aus und lädt nicht neu, wenn das scheitert. Der vierte Schritt ist im vollständigen Skript optional, ein Reload ausgewählter Domains über die REST-API oder ein voller Neustart. Die gekürzte Fassung unten lädt immer die Automationen neu, hat keinen Dry-Run-Modus und setzt voraus, dass configuration.yaml und packages/ auf der Maschine schon existieren. Helfer und Templates lädt sie nicht neu. Das vollständige Skript nimmt eine Liste von Domains entgegen, zum Beispiel --reload automation,input_boolean,template, und ich gebe die Domains an, die eine Änderung betrifft. Eine Änderung an der configuration.yaml oder eine neue Integration braucht --restart.

#!/usr/bin/env bash
# Deploy-Skript, gekürzt; Host, Pfade und URL sind Platzhalter
set -euo pipefail
HOST=ha-host; REMOTE=/path/to/config; SRC=config; HA_URL=http://localhost:PORT

# 1. aktuelle Config auf der Maschine sichern, die letzten 10 Stände behalten
TS=$(date +%Y%m%d-%H%M%S)
ssh "$HOST" "cd $REMOTE && mkdir -p .deploy-backups/$TS \
  && cp -a configuration.yaml packages .deploy-backups/$TS/ \
  && ls -1dt .deploy-backups/*/ | tail -n +11 | xargs -r rm -rf"

# 2. nur übertragen, was im Git gepflegt ist; nur packages/ wird gespiegelt
ssh "$HOST" "mkdir -p $REMOTE/packages"      # rsync --dry-run kann es nicht anlegen
rsync -rlt "$SRC/configuration.yaml" "$HOST:$REMOTE/"
rsync -rlt --delete "$SRC/packages/" "$HOST:$REMOTE/packages/"

# 3. check_config im Container; neben dem Exit-Code auch die Ausgabe lesen
CHECK=$(ssh "$HOST" "docker exec homeassistant python -m homeassistant \
  --script check_config -c /config" 2>&1) || RC=$?
echo "$CHECK"
if [[ -n "${RC:-}" ]] || grep -qi "invalid config" <<<"$CHECK"; then
  # Sicherung wiederherstellen, sonst lädt der nächste Neustart die fehlerhafte Datei
  ssh "$HOST" "cd $REMOTE && rm -rf packages && cp -a .deploy-backups/$TS/. ."
  exit 1                                     # es wird nichts neu geladen
fi

# 4. optionaler Reload über die REST-API; das Token geht über stdin
printf 'Authorization: Bearer %s\n' "$(cat .ha-token)" | \
  ssh "$HOST" "curl -fsS -X POST -H @- $HA_URL/api/services/automation/reload"

Nur packages/ wird gespiegelt, ein im Repository gelöschtes Package verschwindet also auch auf der Maschine. Das Token für den Reload-Aufruf geht per stdin auf die Maschine, weil es auf der Kommandozeile für jeden Prozess auf dem Host in der Prozessliste lesbar wäre.

Was das Skript abgefangen hat

Der erste Probelauf des vollständigen Skripts mit --dry-run brach ab. Im Trockenlauf kann rsync kein Zielverzeichnis anlegen, in das es danach hineinschauen soll, deshalb legt das Skript packages/ jetzt vorher per SSH an. Die zweite Überraschung war eine Einzeldatei, die unter anderem Namen übertragen wird: Die Ausgabe des Trockenlaufs zeigte den Quellnamen an der Stelle, an der der Zielname hätte stehen sollen. Ich habe aufgehört zu raten und übertrage diese eine Datei jetzt mit ssh und cat. Im gekürzten Beispiel fehlt diese Übertragung. Gesehen habe ich das mit rsync 2.6.9 von 2006, das macOS noch mitbringt. Eine neuere Version habe ich nicht ausprobiert.

Der wichtigere Befund kam einen Tag später. check_config meldete eine ungültige Konfiguration und beendete sich trotzdem mit Erfolg. Das Skript prüfte nur den Exit-Code und startete Home Assistant neu. Seitdem liest es die Ausgabe von check_config und bricht bei den Worten Invalid config ab, und in diesem Fall stellt es die Sicherung aus Schritt eins automatisch wieder her. Vor dieser Änderung lag die fehlerhafte Datei nach einem Abbruch weiter auf der Maschine, und der nächste Neustart hätte sie geladen. Eine Übertragung, die mittendrin scheitert, ist damit weiterhin nicht abgedeckt, weil sich das Skript vor der Wiederherstellung beendet. Die Prüfung und die Wiederherstellung habe ich mit einer absichtlich kaputten Datei getestet. Das Skript hat den Fehler erkannt, abgebrochen und die Sicherung zurückgespielt.

Noch am selben Tag wurde das gehärtete Skript im Ernstfall gebraucht. Beim Bau eines Überwachungs-Packages hatte ich einen Einrückungsfehler im YAML. Das Skript erkannte ihn, brach ab und stellte die Sicherung wieder her, bevor irgendetwas neu geladen wurde. Nach diesem Tag würde ich keine Config mehr ohne die Ausgabeprüfung und die Wiederherstellung ausliefern. Es folgt demselben Gedanken wie die Routine aus Backup, Test und Rollback, die ich für Container-Updates verwende, angewendet auf die Config-Dateien.

Recorder von Anfang an begrenzt

Die Recorder-Einstellungen kamen im selben Schritt dazu. Ich behalte 7 statt 10 Tage Verlauf, und ein Zähler ist ausgeschlossen. Der Grund ist die alte Instanz, auf der ein einziger Sensor, der Telegrammzähler der KNX-Schnittstelle, 98 % aller Zeilen der Recorder-Datenbank stellte, 8,39 von 8,56 Millionen in 7 Tagen. Er steigt mit jedem Bus-Telegramm, und jeder Zwischenstand wurde als eigene Zeile geschrieben.

recorder:
  purge_keep_days: 7
  exclude:
    entities:
      # auf der alten Instanz stellte dieser Zähler 98 % aller Recorder-Zeilen
      - sensor.knx_telegram_counter
  # kein include-Block: aufgezeichnet wird alles außer diesem Zähler

Auf der alten Instanz stand außerdem eine include-Liste neben dem exclude-Block, in der Annahme, sie wirke als Whitelist. Auf der neuen Instanz will ich alles außer diesem Zähler aufzeichnen, deshalb verwende ich nur eine exclude-Liste.

Nach der Umstellung auf Packages antwortete Home Assistant 4 s nach docker compose restart wieder mit HTTP 200. Abgefragt habe ich einmal pro Sekunde. HTTP 200 heißt nicht, dass alle Integrationen fertig geladen sind. Die Instanz hatte danach 40 Entitäten, davon 21 Demo, und im Log standen keine Fehler oder Warnungen außer dem üblichen Hinweis zu HACS als Custom Integration.

Häufige Fragen

Kann ich Automationen aus einem Package noch im UI bearbeiten?

Nein. Package-Automationen und ihre Traces sind in der Oberfläche sichtbar, ändern lassen sie sich aber nur in der YAML-Datei.

Wie vermeide ich doppelte Aktionen, wenn zwei Instanzen dieselben Automationen haben?

Ich übertrage jede Automation mit initial_state: false. Pro Bereich schalte ich dann die Automationen auf der alten Instanz aus, nehme auf der neuen initial_state: false heraus und schalte sie dort ein.

Reicht der Exit-Code von check_config für ein Deploy-Skript?

Bei mir nicht. check_config meldete eine ungültige Konfiguration und beendete sich trotzdem mit Exit-Code 0. Mein Skript durchsucht jetzt zusätzlich die Ausgabe nach Invalid config und stellt die Sicherung wieder her, wenn es den Text findet.

Fragen oder Ergänzungen?

Die Kommentare laufen über GitHub Discussions, zum Schreiben brauchst du ein GitHub-Konto.

Verwandte Artikel