# Sesja 2026-06-08 — onboarding LUSTRO (RPi4 / Magic Mirror / KEN)

## Cel

Budowa reużywalnego narzędzia onboardingu nodów `scripts/onboard/` (bash idempotentny,
NIE Ansible — świadoma decyzja), napędzanego deklaratywnym manifestem
`hosts/<node>/node.yaml`. Pierwszy realny node: LUSTRO.

## Node LUSTRO (fakty z preflight)

- RPi4, aarch64, Debian bookworm, hostname pimirror2, sieć KEN 192.168.31.x
- RAM 4 GB (MM zjada ~1.7 Gi — ten sam profil co VPS z OOM 2026-06-01 → `mem_limit` obowiązkowy)
- dysk 58 G / 48% (luz)
- docker 29.5.3 już zainstalowany (krok `20-install-docker` zbędny dla tego node'a)
- user `pi`: uid=1000, passwordless sudo (potwierdzone `sudo -n true`=0), grupy docker+ollama
- Magic Mirror = systemd unit `magicmirror.service` (Electron jako pi) — **NIETKNIĘTY** przez całą sesję
- swap = 200 M plik `/var/swap` na SD → do migracji na zram (wear karty)
- Tailscale: zainstalowany w tej sesji, Running, IP 100.99.85.73

## Decyzje

- **user = istniejący `pi`** (NIE tworzymy `oskar` — `pi` już zajmuje uid 1000, jest
  właścicielem MM, ma docker+sudo; node-agent docker `1000:1000` pasuje out-of-box).
  Świadome odstępstwo od konwencji "oskar wszędzie".
- runtime node-agent = docker
- `first_contact` = LAN IP `pi@192.168.31.19` (mDNS `.local` okazał się zawodny —
  transient resolve fail); po `tailscale up` kontakt przejmuje mesh (`pi@lustro`)
- Tailscale auth = login interaktywny (URL), bez authkey
- swap target = zram

## Stan: 00-access ZAMKNIĘTY

Idempotentny, przeszedł na ostro + re-run czysty. Lustro w mesh, kanał SATURN→lustro
przez Tailscale działa bezhasłowo. Verify czysty (arch=aarch64).

## Bugi narzędzia naprawione w tej sesji

1. **dry-run był płytki** (tylko orchestrator) → `run()` helper + propagacja `DRY_RUN=1`
   do steps (`lib/common.sh`, `onboard.sh`, `remote.sh`, `00-access.sh`)
2. **`yaml_get` fallback** (bez `yq`):
   - inline-comment stripping — `[[:space:]]+#.*$` po wartości
   - PRE-EXISTING greedy-colon bug — `.*:` ucinał ostatni dwukropek, gubił prefix
     w `systemd:magicmirror.service`; fix: `^[[:space:]]*[^:]*:[[:space:]]*`
3. **`00-access` verify** — ssh known-hosts warning wpadał do parsowanego `arch`
   (`WARN "Unexpected arch 'Warning:Permanently…'"`); fix: `-o LogLevel=ERROR`
   + czysty stdout (bez `2>&1`)

## Branch / commity

`feat/node-onboarding` (6 commitów):

| Hash | Opis |
|------|------|
| `adb8407` | scaffold — onboard.sh, lib/, steps/00-preflight, hosts/lustro/node.yaml draft |
| `9012a36` | 00-access.sh + node.yaml ssh_user/first_contact/hardware |
| `931fd46` | dry-run propagacja — run() helper, DRY_RUN=0/1 |
| `eed0ad0` | yaml_get fix — inline-comment + greedy-colon |
| `1bed855` | first_contact: IP zamiast mDNS .local |
| `471ba09` | verify fix — LogLevel=ERROR, czysty stdout |

## OTWARTE — do następnej sesji (kolejność)

1. **WORKTREE HYGIENE** (pierwsza rzecz): cała sesja jechała w MAIN checkout wbrew
   zasadzie "main = deploy-only". Decyzja nierozstrzygnięta:
   - (A) rename `feat/` → `task/node-onboarding` + worktree + main→master
     (pełna zgodność z `agent.sh`; merge=FF)
   - (B) zostać `feat/` + ręczny `git merge --ff-only`

   `agent.sh new` tworzy `task/<name>` od `master` i NIE bierze istniejącego brancha.
   `git worktree list` jeszcze nieodczytany (potrzebny wzorzec ścieżki).

2. **base step**: migracja swap 200 M-plik → zram; `/opt/homelab` + `chown pi`
   (uid 1000 już pasuje); event dir `/opt/homelab/events/lustro/`
3. **node-agent step**: docker override, user 1000:1000 (pi=1000), `mem_limit: 256m`
4. **register step**: observer/supervisor inventory + redis sub + UI panel agents.okit.pl
5. **verify step (50)**: smoke end-to-end (event dotarł do control plane, widać w UI,
   realny alert path Telegram)
6. **mm-watch**: health check `systemctl is-active magicmirror.service`
7. **drobiazgi**: baner URL w 00-access ma defekt wyrównania; `locale pl_PL`
   niewygenerowane na lustrze (niegroźne)

## Learnings

(odzwierciedlone też w `scripts/onboard/README.md`)

- mDNS `.local` zawodny do automatyzacji → `first_contact` przez IP lub tailscale, nie `.local`
- istniejący node z userem uid=1000: użyj go zamiast tworzyć `oskar` (kolizja uid)
- swap na SD = wear → zram
- dry-run MUSI propagować do step-skryptów (`run()` wrapper), inaczej bezużyteczny
- yaml fallback bez `yq` musi strippować inline komentarze i nie być greedy na `:`

## Update — worktree hygiene
- feat/node-onboarding → task/node-onboarding. Main checkout (~/homelab-codex-ws) wrócił na master (deploy-only). Praca onboardingu w ~/homelab-codex-ws-node-onboarding.
- Origin: task/ pushnięty+tracking, feat/ usunięty.
- DROBIAZG: worktree utworzony ręcznie (git worktree add) → agent.sh list pokazuje "(no marker)"/parent=?. Działa; przy finałowym `agent.sh merge node-onboarding` zweryfikować, czy brak markera nie przeszkadza — inaczej dorobić marker (wzór: ha-piha) lub ręczny `git merge --ff-only`.
- NASTĘPNE: base step (zram, /opt/homelab, event dir /opt/homelab/events/lustro/) — z worktree node-onboarding.
- Osobny przyszły projekt: parent-layout refaktor (bare + worktree pod jednym katalogiem) — wymaga przepisania agent.sh + zabezpieczenia dirty ha-piha.

## Tech-debt złapany w sesji
- OBSERVER STALENESS: martwy node (chelsty-infra) świeci NOMINAL w agents.okit.pl — observer/supervisor trzyma ostatni znany stan i nie degraduje przy braku heartbeatu (eventy: tylko VPS raportuje świeżo, chelsty milczy a status NOMINAL). FIX (zdalny, software): heartbeat TTL → po przekroczeniu oznacz `stale`/`down`. Ważne: false-NOMINAL podważa zaufanie do statusu wszystkich nodów. Przenieść do głównego tech-debt backlogu, jeśli istnieje osobny.
