diff --git a/docs/14-HERDR-REMOTE-MACHINES.md b/docs/14-HERDR-REMOTE-MACHINES.md index 62747bd..637684a 100644 --- a/docs/14-HERDR-REMOTE-MACHINES.md +++ b/docs/14-HERDR-REMOTE-MACHINES.md @@ -140,6 +140,92 @@ script are at `/boot/config/go.bak-*`. The binary restore was verified without rebooting, by deleting `/root/.local/bin/herdr-core` and running the line by hand: it came back `-rwxr-xr-x` reporting `hc 0.1.17`. +### A boot-time restore is not enough on its own + +On 2026-11-05 the `hc` binary disappeared with **no reboot**, at 25 days uptime, +so the `go` block above cannot be the only guard. +`/boot/config/herdr/herdr-selfheal.sh` restores the binary from the flash stash, +re-links both `/usr/local/bin` names and re-enables linger if it has lapsed. It +is idempotent and silent when nothing is wrong. + +The script existed from 2026-11-05 but **was not scheduled anywhere** — it had +run exactly once, by hand. Scheduled 2026-11-07, every 10 minutes: + +``` +*/10 * * * * /bin/sh /boot/config/herdr/herdr-selfheal.sh +``` + +It belongs in the `printf ... | crontab -` line in `/boot/config/go`, because +that line **replaces the whole crontab** at boot: an entry added with +`crontab -e` would be wiped at the next restart. `/bin/sh` is required because +`/boot` is vfat and carries no execute bit. The live crontab and what the `go` +file will set at boot were diffed and are identical, so there is no drift. + +Verified without a reboot: deleting `/usr/local/bin/hc` took `hc` off `PATH`, +the script relinked it, logged `relinked /usr/local/bin/hc`, and `hc --version` +reported `0.1.17` again. The core and its 8 live terminals were unaffected — +the running core is already a process and does not re-resolve the symlink. + +## File drops (`hc put`) and the `shot` helper + +`hc put ` sends a **local** file to the terminal's machine and +prints where it landed. It takes `machine/t7`, so the same command works whether +the terminal is here or on the Mac — which makes it the transport for getting a +Mac screenshot into a session running on this box: + +``` +hc put unraid/t13 shot.png +-> {"bytes":67,"path":"/root/.local/state/herdr-core/default/files/t13/shot.png"} +``` + +| Property | Value | +|----------|-------| +| Destination | `/root/.local/state/herdr-core//files//` | +| Survives a reboot | **no** — `/root/.local/state` is the RAM rootfs | +| Relocatable | no — 0.1.17 honours no `XDG_*` override for it | +| Extension | preserved, so `.png` stays `.png` | + +Right for screenshots, wrong for anything large or worth keeping. + +Inside a terminal, `$HC_TERMINAL_ID` is that terminal's own address and +`$HC_TERMINAL_SOCKET` points at `/run/user/0/herdr-core/default.sock`. So a +session can tell a client exactly where to push a file instead of the client +having to guess which terminal is in front of the user. `hc` also records the +client identity per terminal (`"identity":"ghostty"`). + +`shot` reads those drops. Real copy at `/boot/config/shell/bin/shot`, installed +to `/root/.local/bin/shot` by `/boot/config/shell/install.sh` — a real +executable rather than a shell function, because a non-interactive shell (an +agent's tool call, an `ssh` command, cron) never sources `.bashrc` but does +inherit `PATH`. + +| Command | Result | +|---------|--------| +| `shot` | the newest drop for this terminal, absolute path | +| `shot -l`, `shot -n 3` | drops newest-first | +| `shot -d` | the drop folder | +| `shot -a` | drops across every terminal | + +With no drops it prints the `hc put` command that would create one. + +### Reaching the Mac from here (not yet set up) + +`hc machine ls` on this core returns `{"machines":[]}` — the relationship is +one-directional from the Mac today. Registering the Mac here would add +`hc file read|write|edit --on mac` ("an agent's hands there") and +`hc exec --on mac`, which is how a session on this box gets at Mac files +without a mount. Two caveats: `hc file read` is **text only**, so Mac images +must travel by `hc put`; and the Mac is a laptop, so register it by its tailnet +name (`100.68.118.59`, `kaloyans-macbook-air`) and expect it to be unreachable +when asleep. + +Verified 2026-11-07: the Mac answers over Tailscale in 16 ms (direct, via +`192.168.20.101`) and port 22 is open, so macOS Remote Login is already on. It +does **not** advertise Tailscale SSH (`sshHostKeys: false`; macOS cannot act as +a Tailscale SSH server), so unlike this box there is no keyless path — it needs +this host's `unraid@xtrm` key in the Mac's `authorized_keys`. A commented `Host +mac` entry is staged in `/boot/config/ssh/root/config`. + ## Notes - The binary installs to `/root/.local/bin/herdr-core`, which is not on `PATH` diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 18f8aab..0913df8 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,6 +5,17 @@ --- ## 2026-11-07 +### Herdr: self-heal scheduled, file drops wired up +- **[FIXED]** `/boot/config/herdr/herdr-selfheal.sh` **was never scheduled.** It was written on 2026-11-05 after the `hc` binary disappeared with no reboot at 25 days uptime — its own header records that a boot-time restore in `go` "is not sufficient on its own" — yet it had run exactly once, by hand. Now `*/10 * * * * /bin/sh /boot/config/herdr/herdr-selfheal.sh`, added to the `printf ... | crontab -` line in `/boot/config/go` because that line replaces the whole crontab at boot and an entry added any other way is wiped at the next restart. Verified by deleting `/usr/local/bin/hc`, confirming it left `PATH`, and watching the script relink it; the core and its 8 live terminals were unaffected. +- **[HOST]** Confirmed the crontab comment's claim of an "hourly git-ref update of the AMPECO backend checkout" corresponds to **no job** — not in the `printf`, not in the live crontab. It has not been running. Left out rather than invented; the command is still wanted if that job matters. +- **[SERVICE]** `hc put ` established as the transport for getting a Mac screenshot into a session on this box: it takes `machine/t7`, sends a local file to the terminal's machine and prints the destination. Round trip verified end to end. Drops land in `/root/.local/state/herdr-core//files//`, which is the RAM rootfs — fine for screenshots, not for anything kept, and not relocatable (0.1.17 honours no `XDG_*` override). +- **[SERVICE]** New `shot` helper (`/boot/config/shell/bin/shot` → `/root/.local/bin/shot`, installed by the shell installer) names those drops: newest path, list, folder, or across all terminals. A real executable, not a shell function, so non-interactive shells and cron can call it. `install.sh` now installs anything in `/boot/config/shell/bin/`. +- **[NETWORK]** Verified the Mac (`kaloyans-macbook-air`, `100.68.118.59`) answers over Tailscale in 16 ms direct via `192.168.20.101`, with port 22 open, so macOS Remote Login is already enabled. It does **not** advertise Tailscale SSH (macOS cannot be a Tailscale SSH server), so unlike this box there is no keyless path — reaching the Mac from here needs the `unraid@xtrm` key in its `authorized_keys`. A commented `Host mac` entry is staged in `/boot/config/ssh/root/config`. Not yet registered: `hc machine ls` here still returns `{"machines":[]}`. +- Detail in `docs/14-HERDR-REMOTE-MACHINES.md`. + +--- +## 2026-11-07 — shell environment + ### Host: zsh as root's login shell, and four silent failures fixed - **[HOST]** Root's login shell is now `/bin/zsh` (zsh 5.9 from the Slackware 15.0 patches tree, md5 verified, staged in `/boot/extra/` so `rc.local` reinstalls it at every boot). It applies to ssh, the console and the Unraid web terminal, which reads root's shell from the passwd file (`OpenTerminal.php`: `posix_getpwuid(0)['shell']`). bash remains the fallback; both shells source one shared env and alias file so they cannot drift. - **[HOST]** **`/boot/config/passwd` cannot set root's shell.** `/etc/rc.d/rc.S.cont` reads that file but hardcodes `/bin/bash`, discarding the shell field, so editing it does nothing. The change is made by `/boot/config/shell/install.sh`, which `/boot/config/go` calls after `rc.S.cont` has run. It installs zsh from `/boot/extra` if the binary is missing, and leaves root on bash if it genuinely cannot, rather than locking root out of ssh and the web terminal.