diff --git a/hosts/hass.windy.lan.md b/hosts/hass.windy.lan.md index 85eec00..9ac5d89 100644 --- a/hosts/hass.windy.lan.md +++ b/hosts/hass.windy.lan.md @@ -1,3 +1,4 @@ +[hosts/hass.windy.lan.md#8DF6] # hass.windy.lan — Home Assistant (HAOS) ## Role and access @@ -110,31 +111,125 @@ getent hosts hass.windy.lan ## Local patches (custom components) -**`china_southern_power_grid_stat` configurable IP family (verified 2026-08-14, W1N-105):** -`/config/custom_components/china_southern_power_grid_stat` is a file copy of -GitHub `windyboy/china_southern_power_grid_stat` `main` (`a433e8c`, W1N-105 + -P1; the fork's default branch was renamed `master` → `main` on 2026-08-14). -Earlier live trees: `de01914` (W1N-103), `eb8b174` (W1N-102). The live client -uses HA `async_get_clientsession` with a stored `ip_family` option (`auto` / -`ipv4` / `ipv6`; default `auto`, which does IPv4/IPv6 happy-eyeballs). The -network step is now part of Add Entry (and reauth), not only the options flow. -Entry `01KGCQDSZCF523A9X6SV3BZ1B9` is `ip_family: ipv4`. Backups: -`china_southern_power_grid_stat.bak-20260814-w1n102` (HACS/`requests`), -`china_southern_power_grid_stat.bak-20260814-w1n104` (`eb8b174`), and -`china_southern_power_grid_stat.bak-20260814-w1n105` (`de01914`). HACS still -tracks `CubicPill/china_southern_power_grid_stat` — a HACS update would -overwrite this fork. This directory is a file copy, not a git clone; do not -`git pull` in place. +### Manual custom-component install (this host) + +Home Assistant loads custom integrations from +`/custom_components//` (HAOS: `/config` ≡ `/homeassistant`). +A folder named after the integration domain, containing at least +`manifest.json` and `__init__.py`, is enough; Core must be restarted after +copying files. Official HA lookup order: +`/custom_components/` then built-in +`homeassistant/components/`. +See [Integration file structure](https://developers.home-assistant.io/docs/creating_integration_file_structure). + +This host **does not git-clone** custom components. The live tree is a file +copy. Do not `git pull` on HA. + +**Official plugin path** (from +[windyboy/china_southern_power_grid_stat README](https://github.com/windyboy/china_southern_power_grid_stat)): +HACS **or** [手动下载安装](https://github.com/windyboy/china_southern_power_grid_stat/releases). +This host uses the latter. Releases here have no uploaded zip assets; use +GitHub's **Source code (zip)** / zipball of the tag. + +**UI (Samba / File editor / Studio Code Server):** + +1. Download Source code (zip) from the GitHub Release. +2. Extract. Copy only the inner + `custom_components/china_southern_power_grid_stat/` tree — not the repo + root, not a nested extra folder. +3. Place it at `/config/custom_components/china_southern_power_grid_stat/`. +4. Restart Core (**Settings → System → Restart**). +5. First install only: **Settings → Devices & services → Add integration**. + +**SSH from the workstation** (verified 2026-08-14, W1N-107). Replace `v1.3.1` +with the tag being installed: + +```bash +TAG=v1.3.1 +STAGE=/tmp/csg-${TAG}-deploy +mkdir -p "$STAGE" +gh api "repos/windyboy/china_southern_power_grid_stat/zipball/${TAG}" \ + > "$STAGE/src.zip" +unzip -q "$STAGE/src.zip" -d "$STAGE" +SRC=$(find "$STAGE" -type d -path '*/custom_components/china_southern_power_grid_stat' | head -1) +# expect .../custom_components/china_southern_power_grid_stat + +ssh -o BatchMode=yes hassio@hass.windy.lan \ + 'sudo -n -i mkdir -p /homeassistant/.csg-backups && + sudo -n -i cp -a /homeassistant/custom_components/china_southern_power_grid_stat \ + /homeassistant/.csg-backups/china_southern_power_grid_stat.bak-$(date +%Y%m%d)-manual' + +rsync -a --delete \ + -e 'ssh -o BatchMode=yes' \ + "$SRC/" \ + hassio@hass.windy.lan:/homeassistant/custom_components/china_southern_power_grid_stat/ + +# --delete cannot remove Core-owned __pycache__; wipe as root, then restart +ssh -o BatchMode=yes hassio@hass.windy.lan \ + 'sudo -n -i rm -rf /homeassistant/custom_components/china_southern_power_grid_stat/__pycache__ \ + /homeassistant/custom_components/china_southern_power_grid_stat/*/__pycache__ && + sudo -n -i ha core restart' +``` + +Wait until Core is up (`ha core info` returns, typically 1–2 min; this CLI +build does not print a `state:` field). +Then: + +```bash +ssh -o BatchMode=yes hassio@hass.windy.lan \ + 'sudo -n -i cat /homeassistant/custom_components/china_southern_power_grid_stat/manifest.json' +# version must match the tag + +ssh -o BatchMode=yes hassio@hass.windy.lan \ + 'sudo -n -i ha core logs -n 2500' | grep -E 'china_southern_power_grid_stat|cannot pickle' || true +``` + +**Host constraints (do not skip):** + +- Backups **must** live in `/homeassistant/.csg-backups/`. A `*.bak-*` + directory next to the live folder is scanned as the same domain and Core + fails with `No module named '...bak-YYYYMMDD-...'`. +- Do not install this fork via HACS on this host. HACS still tracks + `CubicPill/china_southern_power_grid_stat` `v1.2.0`; a HACS update would + overwrite the live copy. +- First poll after restart can time out to CSG over IPv4; if this-month + sensors stay `unknown` while last-month filled, reload the config entry + (UI: integration → Reload, or supervisor + `POST /core/api/config/config_entries/entry//reload`). +- `runbooks/scripts/ha-maintenance.sh --restart-core --yes` can print + nothing and exit 1 in under a second **without restarting Core**. The + wrapper's ssh line discards stderr (`2>/dev/null`); with `pipefail`, + an ssh failure yields empty stdout + exit 1 before any remote command + runs. Do not treat that as a completed restart. Confirm with elapsed + time (~2 min for a real restart) and `ha core info`. Prefer + `ssh -o BatchMode=yes hassio@hass.windy.lan 'sudo -n -i ha core restart'`. + +Full command family: [runbooks/home-assistant-maintenance.md](../runbooks/home-assistant-maintenance.md). + +### `china_southern_power_grid_stat` live tree + +**v1.3.1** (`55a293fc`, verified 2026-08-14, W1N-107): manual zipball of +GitHub release +[v1.3.1](https://github.com/windyboy/china_southern_power_grid_stat/releases/tag/v1.3.1) +copied to `/config/custom_components/china_southern_power_grid_stat`. +Earlier trees: v1.3.0/`69f13c90` (W1N-106), `a433e8c` (W1N-105), `de01914` +(W1N-103), `eb8b174` (W1N-102). Backups: +`/homeassistant/.csg-backups/` (w1n102/104/105/106/107). + +v1.3.0 crashed the coordinator on first refresh +(`TypeError: cannot pickle 'mappingproxy' object` in +`copy.deepcopy(self._config)` under Python 3.14 / HA 2026.8.1). v1.3.1 +wraps those `deepcopy` calls with `dict(...)`. Post-restart 22:13 CST: +entry `loaded`, no pickle traceback. Native this-month sensors filled after +reloading entry `01KGCQDSZCF523A9X6SV3BZ1B9` (`ip_family: ipv4`). Native +cost/ladder sensors can stay `unknown` because CSG +`get_month_daily_cost_detail` returns a marketing-system SQL error; the +dashboard uses template ladder/cost entities instead. Do not change +`templates/csg_sensors.yaml` or the 电力监控 dashboard for an install. Home PPPoE IPv4 to CSG is still blackholed (`curl -4` to `218.19.148.218:443` times out). `end1` IPv6 is enabled (`ipv6.method: auto`); from HA, `curl -6 https://95598.csg.cn` returns HTTP 200 via `240e:f9:8060::1:16`. -After this deploy, Core started 16:07:15 CST; first `verify_login` timed out, -reload of the entry then succeeded (`state: loaded` at 16:08:50) and CSG -sensors updated. Some cost/ladder sensors stay `unknown` because CSG's own -`get_month_daily_cost_detail` calendar API returns a marketing-system SQL -error — transport to `95598.csg.cn` is working. Re-apply after any -HACS/`CubicPill` update. **`tianqi` weather recorder patch (verified 2026-08-13, W1N-75):** `/config/custom_components/tianqi/weather.py` has a local patch adding @@ -184,6 +279,6 @@ fails. ## Related docs -- [runbooks/home-assistant-maintenance.md](../runbooks/home-assistant-maintenance.md) — `ha` CLI maintenance runbook + [script](../runbooks/scripts/ha-maintenance.sh) +- [runbooks/home-assistant-maintenance.md](../runbooks/home-assistant-maintenance.md) — `ha` CLI maintenance runbook + [script](../runbooks/scripts/ha-maintenance.sh); custom-component zip install is §7 - [docs/lan-overview.md](../docs/lan-overview.md) — LAN map and gw port-forward -- [hosts/dns.windy.lan.md](dns.windy.lan.md) — `hass.windy.lan` / `hass.local` rewrites +- [hosts/dns.windy.lan.md](dns.windy.lan.md) — `hass.windy.lan` / `hass.local` rewrites \ No newline at end of file diff --git a/runbooks/home-assistant-maintenance.md b/runbooks/home-assistant-maintenance.md index be3c535..3ac002d 100644 --- a/runbooks/home-assistant-maintenance.md +++ b/runbooks/home-assistant-maintenance.md @@ -1,7 +1,7 @@ # Runbook: Home Assistant maintenance (hass.windy.lan) Target: [hass.windy.lan](../hosts/hass.windy.lan.md) (HAOS, `machine: green`) -Upstream: HAOS 18.1 / Core 2026.8.x / Supervisor 2026.07.5 (verified 2026-08-13) +Upstream: HAOS 18.1 / Core 2026.8.1 / Supervisor 2026.07.5 (verified 2026-08-14) This runbook covers routine Home Assistant maintenance through the **`ha` supervisor CLI**. All commands are wrapped by a single script @@ -26,6 +26,17 @@ The script runs its whole procedure in one remote login (`sudo -n -i bash -s`) so the banner appears once, then strips it with `awk` up to the `System is ready! Use browser or app to configure.` line. +**Restart wrapper (verified 2026-08-14, W1N-107):** +`./ha-maintenance.sh --restart-core --yes` exited 1 with no output in <1s +and **did not restart Core**. The wrapper pipes a remote script through +`ssh … 2>/dev/null | awk …`; with `set -uo pipefail`, an ssh failure is +silent and the pipeline returns empty/exit 1 **before any remote command +runs**. That is not a MOTD-strip artifact after a successful restart. +The working restart was +`ssh -o BatchMode=yes hassio@hass.windy.lan 'sudo -n -i ha core restart'` +(~131s, `Command completed successfully.`). Treat empty/exit 1 as +failure; confirm with elapsed time and `ha core info`. + ## Script usage ```bash @@ -33,7 +44,7 @@ cd runbooks/scripts ./ha-maintenance.sh # read-only health snapshot ./ha-maintenance.sh --check-config # validate core configuration -./ha-maintenance.sh --logs core 200 # tail core logs +./ha-maintenance.sh --logs core 2500 # tail core logs (use 2500 after a restart) ./ha-maintenance.sh --logs supervisor # tail supervisor logs (default 100) ./ha-maintenance.sh --logs host 50 # tail host journald logs ./ha-maintenance.sh --logs apps: # tail an add-on log @@ -55,7 +66,7 @@ cd runbooks/scripts - `--restore` overwrites the current installation — run only from a planned recovery, with the backup verified. -## Command reference (verified 2026-08-13) +## Command reference (verified 2026-08-14) All verified against the live host. MOTD prepends each command's output; strip with the `awk` pattern above or read the last block. @@ -65,7 +76,7 @@ with the `awk` pattern above or read the last block. | Purpose | Command | |---|---| | General overview | `ha info` | -| Core version/status | `ha core info` | +| Core version/status | `ha core info` (this CLI build has no `state:` field; success is a normal info dump) | | Core config validation | `ha core check` | | Core stats | `ha core stats` | | Supervisor status | `ha supervisor info` (incl. add-on list) | @@ -78,7 +89,7 @@ with the `awk` pattern above or read the last block. | Reload stores/versions | `ha refresh-updates` | | Job manager | `ha jobs info` | | Resolution center | `ha resolution info` | -| Core logs | `ha core logs -n 100` (`-f` follow, `-b` boot id) | +| Core logs | `ha core logs -n 100` (`-f` follow, `-b` boot id). Default 100 misses setup; use `-n 2500` after a custom-component restart. `/config/home-assistant.log` may be missing — `ha core logs` is the source of truth. | | Supervisor logs | `ha supervisor logs -n 100` | | Host journald logs | `ha host logs -n 100` | | Add-on logs | `ha apps logs -n 100` | @@ -120,14 +131,21 @@ add-on states (any `state: error`?); `resolution info` issues; disk free. Expect `Command completed successfully.` before a Core restart. +`ha core check` / a YAML reload is **not** enough after copying Python +custom-component files — restart Core. + ### 3. Inspect logs ```bash -./ha-maintenance.sh --logs core 200 +./ha-maintenance.sh --logs core 2500 # after a Core restart / custom-component copy ./ha-maintenance.sh --logs supervisor ./ha-maintenance.sh --logs apps:core_mosquitto ``` +Default `--logs core` (100 lines) is too short to catch coordinator pickle / +setup errors. `/config/home-assistant.log` may be absent while +`ha core logs` still has history. + ### 4. Apply updates (mutating) ```bash @@ -155,6 +173,104 @@ booted. After a bad OS update, `ha os boot-slot other` boots the previous slot. ./ha-maintenance.sh --backup pre-migration --yes # named backup ``` +### 7. Install or update a custom component (manual zip) + +Home Assistant loads custom integrations from +`/config/custom_components//` (on this HAOS host `/config` ≡ +`/homeassistant`). Official lookup: +`/custom_components/` then built-in +`homeassistant/components/` +([Integration file structure](https://developers.home-assistant.io/docs/creating_integration_file_structure)). +A folder named after the domain, with at least `manifest.json` and +`__init__.py`, is enough. **Restart Core** after copying — `ha core check` +and a YAML reload do not pick up new Python packages. + +This host's live trees are **file copies**, not git clones. Do not +`git pull` inside `custom_components/`. + +#### Official plugin paths (CSG) + +[windyboy/china_southern_power_grid_stat README](https://github.com/windyboy/china_southern_power_grid_stat): +[HACS](https://hacs.xyz/) **or** +[手动下载安装](https://github.com/windyboy/china_southern_power_grid_stat/releases). + +This host uses the zip path. **Do not HACS-update this integration here.** +HACS still tracks upstream `CubicPill/china_southern_power_grid_stat` +`v1.2.0` and would overwrite the fork. Releases have no uploaded zip +assets — use GitHub **Source code (zip)** / zipball of the tag. + +Worked SSH example (tag, backup, `rsync`, `__pycache__`, restart): +[hosts/hass.windy.lan.md](../hosts/hass.windy.lan.md) § Manual +custom-component install. + +#### Procedure + +1. **Backup the live tree off `custom_components/`.** HA scans every + directory under `custom_components/` whose `manifest.json` `domain` + matches. A `*.bak-*` folder next to the live tree makes Core import + the backup (`No module named '...bak-YYYYMMDD-...'`, W1N-106). CSG + backups: `/homeassistant/.csg-backups/`. +2. **Copy only the inner `custom_components//` tree**, not the + repo root and not an extra nested folder. +3. **Wipe `__pycache__` as root.** `rsync --delete` as `hassio` cannot + unlink Core-owned `.pyc` (permission denied, exit 23); stale + `cpython-314` bytecode can keep the old coordinator in memory until + restart. Then restart: + +```bash +ssh -o BatchMode=yes hassio@hass.windy.lan \ + 'sudo -n -i rm -rf /homeassistant/custom_components//__pycache__ \ + /homeassistant/custom_components//*/__pycache__ && + sudo -n -i ha core restart' +``` + +4. **Wait 1–2 min**, then `ha core info` (this CLI build has no `state:` + field; success is a normal info dump). Confirm `manifest.json` + `version` matches the tag. +5. **Read enough Core logs.** Default `ha core logs` is too short to + catch setup. Use `-n 2500` (or `--logs core 2500`) and look for + `Setting up ` plus the first coordinator errors. +6. **First poll can time out.** If last-month sensors have numbers but + this-month stay `unknown`/`unavailable`, reload the config entry + (UI: integration → Reload). Supervisor: + +```bash +# entry id from .storage/core.config_entries (CSG: 01KGCQDSZCF523A9X6SV3BZ1B9) +ssh -o BatchMode=yes hassio@hass.windy.lan \ + 'sudo -n -i python3 -c " +import os, urllib.request +req = urllib.request.Request( + \"http://supervisor/core/api/config/config_entries/entry//reload\", + method=\"POST\", + headers={\"Authorization\": \"Bearer \" + os.environ[\"SUPERVISOR_TOKEN\"]}, +) +print(urllib.request.urlopen(req, timeout=60).status) +"' +``` + +7. **Do not edit the dashboard or `templates/csg_sensors.yaml` for an + install.** Entity IDs did not change across v1.3.0/v1.3.1. Template + `| float(0)` turns native `unavailable` into fake zeros; that is a + follow-up, not part of the zip install. + +#### Verify (CSG, after v1.3.1 / W1N-107) + +| Check | Expect | +|---|---| +| `manifest.json` `version` | `1.3.1` | +| `ha core logs` after this restart | `Setting up china_southern_power_grid_stat`; **no** `cannot pickle 'mappingproxy'` | +| Config entry | `state: loaded` | +| `sensor.0800041935246530_balance` | numeric (may be `0.0`) | +| `sensor.0800041935246530_this_month_total_usage` | numeric after reload if first poll timed out | +| Native `*_total_cost` / `current_ladder` | may stay `unknown` (CSG marketing calendar SQL error); dashboard uses template `_2` / `*_ladder_cost` | + +`monetary` + `total_increasing` warnings on this-month/year cost sensors +are a remaining plugin issue, not an install failure. + +There is no long-lived `HA_TOKEN` in the agent environment. Read entity +states via Supervisor (`SUPERVISOR_TOKEN` after `sudo -n -i`) at +`http://supervisor/core/api/states/`. + ## Command families intentionally NOT scripted These exist in `ha` but are either rare, dangerous, or better done in the web @@ -186,17 +302,18 @@ host for exact syntax. The [official HAOS common-tasks docs](https://www.home-assistant.io/common-tasks/os/) reference `ha backups list` and `ha host update`. **Neither exists in the -installed CLI** (2026-08-13): backups are inspected with `ha backups info +installed CLI** (2026-08-14): backups are inspected with `ha backups info ` (a slug is required) and `host` has no `update` subcommand. Trust the server CLI (`ha --help`) over the docs. -## Known issues on hass.windy.lan (2026-08-13) +This CLI's `ha core info` also has no `state:` field (verified 2026-08-14). +Wait for a successful info dump after restart, not a `state: running` line. -All items from the original 2026-08-13 health snapshot were resolved same day -(W1N-70/71/72/73/74/75/76): OTBR and the duplicate SSH add-on uninstalled, all -resolution-center issues cleared (`ha resolution info` → empty), full backup -`pre-maintenance-20260813` (slug `411a4ba5`) taken. Remaining known issues -(documented in [hosts/hass.windy.lan.md](../hosts/hass.windy.lan.md)): +## Known issues on hass.windy.lan (2026-08-14) + +2026-08-13 snapshot items were resolved same day (W1N-70/71/72/73/74/75/76): +OTBR and the duplicate SSH add-on uninstalled, resolution-center empty, +full backup `pre-maintenance-20260813` (slug `411a4ba5`). Remaining: - **Bluetooth hci0 instability (RTL8821CS)**: `bluetooth_auto_recovery` power-reset times out every ~2 min; kernel `hci0 hardware error`. No BLE @@ -204,6 +321,19 @@ resolution-center issues cleared (`ha resolution info` → empty), full backup workaround units. - `host info` reports `disk_life_time: 10` (boot eMMC ~10% life left) — monitor on each snapshot; plan disk replacement / data-disk migration. +- **Home PPPoE IPv4 to CSG is blackholed** (`curl -4` to + `218.19.148.218:443` times out). `end1` IPv6 works (`curl -6 + https://95598.csg.cn` → HTTP 200). Entry `ip_family: ipv4` still + matches the stored option; first post-restart poll can still time out + — reload the config entry rather than reinstalling. +- **WSL HTTP proxy**: LAN `hass.windy.lan:8123` through Mihomo returns + empty `502`. Bypass proxy or add `.windy.lan` to `NO_PROXY` before + debugging UI/API from the workstation + ([hosts/hass.windy.lan.md](../hosts/hass.windy.lan.md) § HTTP proxy + gotcha). +- **No long-lived HA token in the agent environment.** Read entity + states via Supervisor (`SUPERVISOR_TOKEN` after `sudo -n -i`) at + `http://supervisor/core/api/states/...`, not a committed `HA_TOKEN`. ## Pass criteria @@ -211,4 +341,7 @@ resolution-center issues cleared (`ha resolution info` → empty), full backup - Mutating modes refuse to run without `--yes` (incl. `--restore`, `--app`) - `--check-config` returns success - Update / rollback / restore / reboot confirmed only after explicit `--yes` +- Custom-component zip install: live `manifest.json` version matches the + tag; backups not under `custom_components/`; Core restarted; logs show + `Setting up ` without import / pickle errors - Update the **Verified** line on [hosts/hass.windy.lan.md](../hosts/hass.windy.lan.md)