docs: DEV.md env-seam registry + stub-harness guidance; scheduler migrate semantics

Session-learned hardening (sole-developer project — terse, actionable):
- §7 env-override precedent list gains USER_SYSTEMD_DIR (network-download,
  communication listeners, scheduler-lib) + the scheduler's SCHEDULE_* seams,
  plus the missing-:-guard gotcha that silently writes to the real $HOME
  under stub runs.
- New-tool test checklist gains an env-seam review step (grep for unguarded
  config writes, prove with VAR=/tmp/x).
- §7 notes stub harnesses are throwaway by design: build in
  /tmp/opencode/<tool>-test/, leave there, keep only the pattern.
- howto/schedule.md: migrate copies the rule LHS verbatim as COMMAND — the
  old tool never had disk root/loadavg shorthands; rewrite those jobs.
This commit is contained in:
Your Name
2026-08-13 02:14:51 -04:00
parent 5da148ac4e
commit 6504c69154
2 changed files with 10 additions and 4 deletions
+4 -3
View File
@@ -16,6 +16,10 @@ summary (newest last).
## Done
- **2026-08-13** — `pos share smb-server share` now guards the two common `NT_STATUS_ACCESS_DENIED` causes at share time (warnings only): `--users` entries missing from the Samba passdb (`pdbedit -L`, cut to user column, `grep -qxF` per user — pointer to `pos share smb-server adduser <user>`), and ancestors of the share path lacking `other:+x` traversal (sticky dirs like `/tmp` count as traversable via the `t` slot; fix hint `chmod o+x <dir>`). Both wired into the `share` case after `require_root_dir`; howto/share.md SMB section + troubleshooting updated. Rooted in `reports/bug-report-smb-server-access-denied.md` (committed as the spec). Verified with a stub-PATH suite (`/tmp/opencode/smb-test` — pdbedit/systemctl/smbcontrol/testparm/smbpasswd stubs, `SMB_CONF` seam): 16/16 green.
- **2026-08-13** — Docs hardening from the schedule-session review (sole-developer call: terse, session-learned). DEV.md §7 env-seam registry now lists `USER_SYSTEMD_DIR` (`bin/pos-network-download`, `bin/pos-communication-{telegram,matrix}-listener`, `lib/scheduler-lib.sh`) + the scheduler's `SCHEDULE_*` seams, and documents the missing-`:-`-guard gotcha (a `VAR="${XDG…:-…}"` without leading `VAR:-` overrides the seam — stub runs then silently write to the real `$HOME`; fix: `USER_SYSTEMD_DIR="${USER_SYSTEMD_DIR:-…}"`). New-tool test checklist gains an env-seam review step (grep for unguarded config writes + prove with `VAR=/tmp/x`). §7 notes stub harnesses are throwaway by design — build in `/tmp/opencode/<tool>-test/`, leave there, keep only the pattern. howto/schedule.md documents that `migrate` copies the rule LHS **verbatim** as `COMMAND` (old tool never had `disk root`/`loadavg` shorthands — rewrite those jobs with real commands). `make check` green.
- **2026-08-12** — `pos system event-trigger` (eventer) generalized into `pos system schedule` — the scheduler replaces the single-timer threshold monitor with **per-job systemd user timers** (`pos-schedule-<name>.{timer,service}`, `Persistent`, ExecStart `run <name>`, reconciled on `enable`/`disable` — orphan units + the legacy `pos-event-trigger` timer auto-removed). Each job is a chmod-600 file `~/.config/linux_post_install/schedule.d/<name>.env`: `INTERVAL` (`5m..59m`/`1h..23h`/`hourly`/`daily`/`weekly`/`OnCalendar=…`), `NOTIFY` policy, optional `MSG`, `RULE` (threshold only), and `COMMAND` = **literal remainder of the line** (pipes/quotes/`sudo` need no escaping). Policies: `always` (full output every run), `onchange` (diff vs last run, first run always sends), `onerror` (non-zero exit or empty output), `threshold` (old event-trigger behavior: first numeric vs `RULE`, alert on false→true + recovery, per-job firing state), `never` (silent side-effect jobs — no notify; run log + last-run record still kept). Per-run logs/state in `~/.local/share/linux_post_install/schedule/{logs,state}/`. Subcommands: `run [name|all]`, `list`, `config` (interactive add/edit/remove/enable/disable with validation), `enable [name|all]`, `disable [name|all]`, `status`, `migrate` (converts legacy `event.env` rules → `schedule.d/rule-N.env` threshold jobs, adopts the legacy timer's OnCalendar or 5m, removes the old timer). Files: `bin/pos-system-event-trigger``bin/pos-system-schedule`, `lib/eventer-lib.sh``lib/scheduler-lib.sh` (git mv; installed by install.sh), `config/event.env` + `config/event-rules.template``config/schedule.d/` starter jobs (nvme-health via `sudo -n smartctl` with the user's exact grep — sudoers NOPASSWD documented; cpu-temp + disk-root thresholds; silent log-cleanup), postinstall installs them no-clobber into an empty `schedule.d/` (legacy `event.env` users get a migrate hint instead). `bin/pos` EXAMPLES + INTERACTIVE_CMDS (`system-schedule config`) updated. Supersedes the "Tier 2: watch plugins" backlog idea. Docs: POS.md system row rewritten, howto/event-trigger.md → howto/schedule.md (job syntax, policies, NVMe recipe, migration), HOWTO.md index row + config table + scheduling bullet, AGENT_Context lib row + Common Tasks row. `make gen && make check` green; stub-harness suite (fake `systemctl`/`sudo`/`smartctl`/`sensors`/`df` + fake telegram sender logging, env seams `SCHEDULE_DIR`/`SCHEDULE_STATE_DIR`/`SCHEDULE_LOG_DIR`/`USER_SYSTEMD_DIR`/`SCHED_LEGACY_ENV`) covers all 5 policies (threshold cross/recover/no-repeat, onchange first/diff/same, onerror, always, never-silent), COMMAND literal-pipe parsing, enable/disable/status + orphan/legacy cleanup, migrate (incl. skip-existing + dry-run), and dispatch.
- **2026-08-12** — `pos network download replace <gid> <url>` + fresh-link status advisory. `status` now flags stopped errored downloads whose source is marked permanently failing in `download.retry` (`needs fresh link: <name> (<gid>) — pos network download replace … <new-url>`; one `tellStopped` RPC, id-match in jq). `replace <gid> <url>` re-queues a dead single-file HTTP/FTP download with a new URL keeping the same `dir` + file name (partial resumes via `--continue=true`), unmarks the old source (`retry_unmark`, literal `grep -vxF` — URL-safe), and reuses `retry_verify` so a dead replacement link is diagnosed + marked permanent; torrents/active/multi-file are rejected with hints; `--dir/--split/--tmux` supported. `retry_verify` hardened to `${quiet:-0}` so it works outside `cmd_retry`. Stub suite grew a `replace` section (advisory match, success + unmark + advisory-clear, dead new link marked, torrent/active/arg errors, prefix gid) — tellStopped fixtures gained `uris` (real aria2 includes them). 141/141 green; docs: POS.md row + outage paragraph, howto/network.md dead-link recipe. `make gen && make check` green.
- **2026-08-12** — `pos network download` grows outage resilience: `restart <gid>` (re-queue from history — torrents via rebuilt magnet `urn:btih:` + `&tr=` trackers, HTTP via original URIs with `dir`/`out` preserved, `--continue=true` resumes partials; options `--dir/--seed/--split/--tmux`), `retry <gid|all>` (smart retry — waits out internet outages via `NET_PROBE` seam, re-queues, `retry_verify` polls the new gid; aria2 error 3 = real problem → diagnosed + marked permanent in `~/.config/linux_post_install/download.retry` as `url:<uri>`/`bt:<infohash>`, `retry all` skips them, manual restart overrides; `--once`/`--quiet` timer mode; `--interval`/`--max-wait`), and the **retry healer** systemd user pair (`pos-aria2-retry.service` oneshot `retry all --once --quiet` + `pos-aria2-retry.timer` 2min, `Persistent`) that arms on download start (`add`/`torrent`/`metalink`/`restart`) and disables itself when nothing is left; `watch <gid>` now auto-restarts its download after an outage. Fixes from stub-suite review: `ensure_healer` was missing from the three submit paths; `RESTART_NAME` was lost across `do_restart`'s process-substitution subshell (now a `download_name()` helper); `restart` exited 1 because the `[ tmux -eq 1 ] && tmux_watch` test was the function's last statement. Verification: stub-based test harness (`/tmp/opencode/dl-test` — curl/systemctl stubs with tellStatus fixtures, `NET_PROBE` file-flip, unit enable/disable logging) 119/119 green, incl. new restart/retry/healer/watch-heal cases. Docs: POS.md download rows + outage-resilience paragraph, howto/network.md outage recipe, SYSTEMD.md per-user units section, AGENT_Context + completions regenerated. `make gen && make check` green.
- **2026-08-11** — Docs: DEV.md / AGENTS.md / AGENT_Context improved from the SMB session's lessons. DEV.md: new "Testing tools that need root / systemd / missing deps" (env-override test seams — `FLAGS_DIR`/`SMB_CONF`/`SMB_CREDS_DIR`/`UNIT_DIR` precedents — + stub-PATH fakes + PTY prompt driving via `script`); new Best Practice "Managed Config Blocks" (start/end marker idiom incl. the `inblock == 1` awk guard, validate-then-apply, hot reload); deps-guards-run-before-`--help` made explicit (previously only inferable by reading the NFS tools); "Update the docs" checklist completed (howto index/section, Common Tasks row, AGENTS.md Quick facts, AGENT_TODO Done move). AGENTS.md: clarified which filetable line-count rows are hand-maintained (non-`pos-*` files above the marker) + when to bump them; deps-guard clause added to Quick facts. AGENT_Context "Adding a New Tool" steps 67 mirror the above. `make gen && make check` green.
- **2026-08-11** — `share` category grows SMB: `pos share smb server` (`bin/pos-share-smb-server`) + `pos share smb client` (`bin/pos-share-smb-client`), completing the share trio (usb/nfs/smb). **Server:** `status`/`share`/`unshare`/`list`/`adduser`/`deluser`/`reload`/`enable`/`disable`; idempotent marker blocks in `/etc/samba/smb.conf` (`# >>> pos-managed share: <name>``# <<< end pos-managed share` — hand edits outside markers survive; `inblock==1`-guarded awk so removing one block never eats another's end marker), `testparm` validation before apply + `smbcontrol smbd reload-config` hot reload; `--read-only`/`--guest`/`--users u1,u2` flags with unrestricted-share warnings; `smbpasswd` user management (prompts, requires system user first). **Client:** `mount`/`unmount`/`list`/`persist`/`unpersist`; password prompt via `/dev/tty`, throwaway chmod-600 credentials for one-shot mounts, persistent creds at `/etc/samba/credentials/<name>` (chmod 600); `persist` writes a systemd `.mount` unit (`systemd-escape`) with `x-systemd.automount` + `_netdev` — mounts on first access, never blocks boot. Both source `lib/notify.sh` for mutations; added to `INTERACTIVE_CMDS` (prompting subcommands). Deps: `samba` + `cifs-utils` added to preinstall PACKAGES. `SMB_CONF`/`SMB_CREDS_DIR`/`UNIT_DIR` env-overridable for tests (FLAGS_DIR precedent). Docs: POS.md share rows, howto/share.md SMB sections, HOWTO index row, AGENT_Context Common Tasks, AGENTS.md categories. `make gen && make check` green; logic tested via stubbed PATH + temp config (marker idempotency, guest + user persist flows).
@@ -71,9 +75,6 @@ summary (newest last).
## Later — idea backlog
- **Tier 2: watch plugins** — `pos system watch <event>`: poll conditions and
alert on change (public IP changed, disk > 90%, backup skipped, fail2ban
spike). Reuses `notify_send` + a systemd timer per watch.
- **Tier 2: `pos health` extras** — temperature/fan/load average thresholds,
`ss -tln` port checks for known services, SMART status for disks.
- **Tier 3: backup rotation + remote target** — keep-N rotations, upload to
+6 -1
View File
@@ -176,6 +176,9 @@ chmod +x bin/your-tool
bash -n bin/your-tool
shellcheck bin/your-tool
./bin/your-tool --help
env-seam review: every path the tool writes must be `VAR="${VAR:-default}"`-guarded —
grep for config writes without a `:-` guard: `grep -nE '>\s*(\$HOME|/etc)' bin/your-tool lib/your-lib`
(each hit needs the seam; prove it with `VAR=/tmp/x bin/your-tool …` + assert the real path is untouched)
bin/pos help <full command> # confirm dispatch works
bin/pos <category> --help # confirm category listing includes the new tool (first tool in a new category)
make gen # regenerate doc tables + completion flags
@@ -188,12 +191,14 @@ make check # full self-consistency gate (syntax, exec bits, d
`make check` only proves syntax, exec bits, doc sync and dispatch — not behaviour. For tools that need `sudo`, systemd, or binaries absent from the dev box (samba, usbsrv, …), test them end-to-end with two patterns:
- **Env-overridable paths.** Anything that touches a system config location gets an env override whose default is the real path — the seam that lets the tool be exercised against temp files. Precedents: `FLAGS_DIR` (`lib/flags.sh`), `SMB_CONF` (`bin/pos-share-smb-server`, default `/etc/samba/smb.conf`), `SMB_CREDS_DIR`/`UNIT_DIR` (`bin/pos-share-smb-client`). Pick a short tool-specific name and don't advertise it in `usage()` — it's a test seam, not user-facing.
- **Env-overridable paths.** Anything that touches a system config location gets an env override whose default is the real path — the seam that lets the tool be exercised against temp files. Precedents: `FLAGS_DIR` (`lib/flags.sh`), `SMB_CONF` (`bin/pos-share-smb-server`, default `/etc/samba/smb.conf`), `SMB_CREDS_DIR`/`UNIT_DIR` (`bin/pos-share-smb-client`), `USER_SYSTEMD_DIR` (`bin/pos-network-download`, `bin/pos-communication-{telegram,matrix}-listener`, `lib/scheduler-lib.sh` — write it as `${USER_SYSTEMD_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user}`), and the scheduler's `SCHEDULE_DIR`/`SCHEDULE_STATE_DIR`/`SCHEDULE_LOG_DIR`/`SCHED_LEGACY_ENV` (`lib/scheduler-lib.sh`). Pick a short tool-specific name and don't advertise it in `usage()` — it's a test seam, not user-facing. **Gotcha (session-learned):** a `VAR="${XDG…:-…}"` without the leading `VAR:-` *overrides* the seam — the stub run then silently writes to the real `$HOME` path and every assertion passes while the bug hides. The override must be written first, then tested with `VAR=/tmp/x …` and a check that the real path is untouched.
- **Stub PATH.** Create a temp dir with fake binaries, then run the tool with `PATH="$stubs:$PATH"`: fake `sudo` → `exec "$@"`; fake `systemctl`/`smbcontrol`/`mount.cifs` → echo their args; fake `testparm` → `cat` the file back (so validation passes); fake `systemd-escape` → print a fixed name. Assert on output **and** exit codes — happy path plus each failure path (`err` sets rc=1).
- **Interactive prompts** (`read … </dev/tty`): drive them with a PTY — `printf 'answer\n' | script -qec "cmd" /dev/null` — then assert the side effect (e.g. the chmod-600 creds file lands with the right mode).
Example (session-learned): `PATH=/tmp/stubs:$PATH SMB_CONF=/tmp/smb.conf bin/pos-share-smb-server share /tmp/media …`.
Stub harnesses are **throwaway by design**: no `tests/` dir and no CI in this repo — build them outside the project (`/tmp/opencode/<tool>-test/`: `stubs/` + `run-tests.sh` with a `check "desc" "expected" "$actual"` helper and a pass/fail count), run them, then leave them in `/tmp`. Only the *pattern* above is worth keeping in the repo.
---
## Adding an Entertainment Plugin