feat(kb): SPLIT service+runbook — 10 serwisow -> 20 dokumentow
Wzorzec mechaniczny: sekcje deploy/verify/install/testy wycinane do
kb/runbooks/<serwis>-*.md, reszta zostaje dokumentem type: service.
Wzajemne `links` w obie strony. Tresc sekcji nietknieta — przenoszone
doslownie, dodany wylacznie naglowek H1 nowego runbooka.
kb-query, paperless-worker, planner-agent, ha-diag-agent, ollama-piha,
narty27, home-assistant, ha-mcp, job-gmail-header-backfill, job-mail-body-ingest.
Weryfikacja: dla kazdego pliku multizbior niepustych linii
(main + runbook) == oryginal z HEAD. Zero zgubionych, zero dodanych.
Recon szacowal 13 splitow service+runbook; faktycznie 2-typowych jest 10,
pozostale 5 (paperless, nextcloud, gokapi, fleet-prometheus, deploy-runner)
sa 3-typowe i ida osobno jako splity wielotypowe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 15:04:29 +02:00
|
|
|
---
|
|
|
|
|
okf: "0.1"
|
|
|
|
|
type: runbook
|
|
|
|
|
visibility: private
|
|
|
|
|
status: active
|
|
|
|
|
updated: 2026-07-22
|
|
|
|
|
links:
|
|
|
|
|
- ../services/home-assistant.md
|
|
|
|
|
---
|
2026-07-21 12:40:21 +02:00
|
|
|
|
feat(kb): SPLIT service+runbook — 10 serwisow -> 20 dokumentow
Wzorzec mechaniczny: sekcje deploy/verify/install/testy wycinane do
kb/runbooks/<serwis>-*.md, reszta zostaje dokumentem type: service.
Wzajemne `links` w obie strony. Tresc sekcji nietknieta — przenoszone
doslownie, dodany wylacznie naglowek H1 nowego runbooka.
kb-query, paperless-worker, planner-agent, ha-diag-agent, ollama-piha,
narty27, home-assistant, ha-mcp, job-gmail-header-backfill, job-mail-body-ingest.
Weryfikacja: dla kazdego pliku multizbior niepustych linii
(main + runbook) == oryginal z HEAD. Zero zgubionych, zero dodanych.
Recon szacowal 13 splitow service+runbook; faktycznie 2-typowych jest 10,
pozostale 5 (paperless, nextcloud, gokapi, fleet-prometheus, deploy-runner)
sa 3-typowe i ida osobno jako splity wielotypowe.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 15:04:29 +02:00
|
|
|
# home-assistant — import, deploy, testy
|
2026-07-21 12:40:21 +02:00
|
|
|
|
|
|
|
|
## Import
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
scripts/ha/import.sh ken
|
2026-07-22 17:27:00 +02:00
|
|
|
scripts/ha/import.sh ken-legacy
|
2026-07-21 12:40:21 +02:00
|
|
|
```
|
|
|
|
|
|
2026-07-22 17:27:00 +02:00
|
|
|
Read-only, and idempotent for both adapters — re-running against an
|
|
|
|
|
unchanged instance produces no diff.
|
2026-07-21 12:40:21 +02:00
|
|
|
|
2026-07-22 17:27:00 +02:00
|
|
|
**`docker-exec` adapter** (`ken-legacy`): pulls the whole `/config` tree
|
|
|
|
|
over `ssh ... docker exec ... tar`, filters it through `.gitignore`,
|
|
|
|
|
normalizes and splits it into `config/<instance>/`, exports curated
|
|
|
|
|
`.storage/*` into `storage-export/<instance>/`.
|
|
|
|
|
|
|
|
|
|
**`api` adapter** (`ken`, `chelsty-ha`): for instances with no
|
|
|
|
|
filesystem/SSH access (HAOS has none). Pulls automations/scripts/scenes via
|
|
|
|
|
one REST GET per object (`/api/config/<domain>/config/<id>`) and
|
|
|
|
|
dashboards/area+entity registries/`input_*` helpers via the WebSocket API
|
|
|
|
|
(`/api/websocket`) — read-only commands only, nothing that mutates the live
|
|
|
|
|
instance. Full `/config` import is out of scope for this adapter; see
|
|
|
|
|
DESIGN.md, "Deploy path: adapter per instance".
|
|
|
|
|
|
|
|
|
|
Both adapters also write a dated `/api/states` fixture snapshot to
|
|
|
|
|
`fixtures/` if a deploy token exists at
|
|
|
|
|
`~/.config/ha-deploy/<instance>.token`. For the `api` adapter, that token is
|
|
|
|
|
not optional — see "Tokens" below.
|
|
|
|
|
|
|
|
|
|
### Tokens
|
|
|
|
|
|
|
|
|
|
The `api` adapter has no filesystem fallback, so a missing or empty token
|
|
|
|
|
at `token_path` (see `instances.yaml`) is a hard error, not a soft-skip —
|
|
|
|
|
`import.sh` aborts immediately with a clear message rather than silently
|
|
|
|
|
producing an empty import. See `DESIGN.md`, "Tokens", for how the
|
|
|
|
|
`deploy_agent` account and its long-lived access token are provisioned.
|
|
|
|
|
|
|
|
|
|
The token itself never touches a subprocess argv or a log line: REST calls
|
|
|
|
|
go through `scripts/ha/lib/ha_api.py`, which reads the token file itself and
|
|
|
|
|
sets the `Authorization` header in-process via `requests` (never `curl -H`,
|
|
|
|
|
which would put the token in that process's argv, visible to any local user
|
|
|
|
|
via `ps`). `requests` (`python3-requests`) is a very common preinstalled/
|
|
|
|
|
transitive package on Debian-based nodes; if it's ever missing, `import.sh`
|
|
|
|
|
fails with Python's own `ModuleNotFoundError` — install it the same way as
|
|
|
|
|
`python3-websocket` below (`sudo apt install python3-requests` or `pip
|
|
|
|
|
install --user requests`).
|
|
|
|
|
|
|
|
|
|
### WebSocket dependency
|
|
|
|
|
|
|
|
|
|
The dashboard/registry/helper export (`scripts/ha/lib/ha_ws.py`) depends on
|
|
|
|
|
the `websocket-client` PyPI package (import name `websocket`) — not in the
|
|
|
|
|
stdlib, not installed by default. Install one of:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
sudo apt install python3-websocket # Debian/Ubuntu package name
|
|
|
|
|
# or
|
|
|
|
|
pip install --user websocket-client
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If it's missing, `import.sh ken` still imports automations/scripts/scenes
|
|
|
|
|
(REST-only, no extra dependency needed) and then reports the
|
|
|
|
|
dashboards/registries/helpers step as skipped with an actionable message
|
|
|
|
|
naming the package to install — it does not crash with a raw traceback, and
|
|
|
|
|
it does not silently produce an incomplete `storage-export/` without saying
|
|
|
|
|
so.
|
2026-07-21 12:40:21 +02:00
|
|
|
|
2026-07-22 18:21:11 +02:00
|
|
|
## Deploy
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
scripts/ha/deploy.sh ken --dry-run # plan only, read-only
|
|
|
|
|
scripts/ha/deploy.sh ken --dry-run automations/111.yaml # plan for one object
|
|
|
|
|
scripts/ha/deploy.sh ken # deploy everything in scope
|
|
|
|
|
scripts/ha/deploy.sh ken scripts/notify_email_ntfy.yaml # deploy one object
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Repo -> instance, `api` adapter only, automations/scripts/scenes only
|
|
|
|
|
(dashboards/helpers are WebSocket-only — `ha_ws.py` has no mutating
|
|
|
|
|
command, deliberately, and that write path doesn't exist yet). Refuses any
|
|
|
|
|
instance with `status != active` (see `instances.yaml`) or an adapter other
|
|
|
|
|
than `api` — no override flag.
|
|
|
|
|
|
|
|
|
|
Hard sequence, per `DESIGN.md`'s "Sync model" and "Validation gate", any
|
|
|
|
|
failure aborts before the next step:
|
|
|
|
|
|
|
|
|
|
1. **drift-check** — re-imports the instance's current state and diffs it
|
|
|
|
|
against the last commit (`HEAD`) for every object *not* being deployed
|
|
|
|
|
this run. Any difference aborts with a diff printed; drift is never
|
|
|
|
|
silently overwritten.
|
|
|
|
|
2. **validate** — local sanity (YAML parses, required keys present per
|
|
|
|
|
domain) plus a live `check_config` gate on the instance.
|
|
|
|
|
3. **write** — one `POST /api/config/<domain>/config/<id>` per object.
|
|
|
|
|
4. **verify** — GETs the same object back and compares it to what was
|
|
|
|
|
written. A mismatch is reported (which object, what differs); there is
|
|
|
|
|
no auto-rollback.
|
|
|
|
|
|
|
|
|
|
`--dry-run` runs steps 1-2 (both read-only in effect) and stops before any
|
|
|
|
|
write, printing the same plan a live run would act on.
|
|
|
|
|
|
2026-07-21 12:40:21 +02:00
|
|
|
## Tests
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
scripts/ha/tests/test_split_normalize.sh
|
2026-07-22 17:27:00 +02:00
|
|
|
scripts/ha/tests/test_normalize_tags.sh
|
|
|
|
|
scripts/ha/tests/test_import_api_offline.sh
|
2026-07-22 18:21:11 +02:00
|
|
|
scripts/ha/tests/test_deploy_api_offline.sh
|
2026-07-21 12:40:21 +02:00
|
|
|
```
|
|
|
|
|
|
2026-07-22 17:27:00 +02:00
|
|
|
All offline — no network, no HA instance required. `test_import_api_offline.sh`
|
|
|
|
|
covers the `api` adapter: normalization of real-shaped API responses (saved
|
|
|
|
|
as fixtures under `tests/fixtures/`), idempotency, stale-file cleanup, and
|
|
|
|
|
the missing-`websocket-client` error path (via `sys.modules` injection, not
|
2026-07-22 18:21:11 +02:00
|
|
|
an actual network call). `test_deploy_api_offline.sh` covers the deploy
|
|
|
|
|
write path: a clean deploy, drift-abort, local validation rejecting broken/
|
|
|
|
|
unparseable YAML, and verify detecting a post-write mismatch — using a real
|
|
|
|
|
temporary git repo for the `HEAD` comparison and a fake in-memory client for
|
|
|
|
|
`get`/`post_config`/`check_config` (no network).
|