Files
Linux_post_install/AGENT_TODO.md
T
Your Name a3c01a0394 feat: usb-automount feature — udev+systemd auto-mount of USB sticks, flag-gated like autostart
features/usb-automount.sh (installed via ./install.sh --feature, flag
usb-automount) mounts every unmounted removable block device at
/media/<label> — world-writable via -o umask=000 (fallback plain mount),
label-collision bump -2/-3, no-label -> usb-<name>. First root run
self-installs the hotplug udev rule (/etc/udev/rules.d/99-usb-automount.rules,
SYSTEMD_WANTS=usb-automount.service) + udevadm reload/trigger; an existing
rule is never overwritten. systemd/usb-automount.service (Type=oneshot,
WantedBy=multi-user.target) covers boot + hotplug + manual start, gated in
postinstall.sh's systemd loop exactly like autostart. Purpose: a plugged-in
stick is ready for pos system backup's post-verify USB copy without manual
mounting. Docs: SYSTEMD/SCRIPTS/README/AGENT_Context (tree, filetable —
postinstall.sh count corrected 152->163, it was already stale). Verified:
stub suite /tmp/opencode/usb-automount-test 47/47 green; make gen && make check.
2026-08-13 03:40:13 -04:00

35 KiB
Raw Blame History

AGENT_TODO — Worklist & Idea Backlog

Living list of what we are doing, what is next, and what we might do later. Deep history lives in git: git log --follow AGENT_TODO.md, git blame, and the individual feature commits — the Done section below is just a readable summary (newest last).

Conventions

  • Now — items actively being worked on this session (only a few).
  • Next — queued, well-scoped items.
  • Later — idea backlog. Ideas marked NOT NOW were evaluated and rejected for the stated reason; revisit only if circumstances change.
  • When a task is completed: move it from Now/Next into Done (dated one-line) in the same commit that finishes the work.

Done

  • 2026-08-13usb-automount feature, integrated exactly like autostart: features/usb-automount.sh (root-guard re-exec via sudo; first-root-run self-install of udev rule /etc/udev/rules.d/99-usb-automount.rulesACTION=="add", KERNEL=="sd[a-z]*", SUBSYSTEM=="block", ENV{ID_BUS}=="usb", TAG+="systemd", SYSTEMD_WANTS="usb-automount.service" — + udevadm control --reload + trigger --subsystem-match=block; an existing/edited rule is never overwritten; scans lsblk -J for unmounted removable partitions/raw whole-disk filesystems, mounts each at /media/<label> — vfat/exfat/ntfs world-writable via -o umask=000, fallback plain mount, label-collision bump -2/-3, no label → usb-<name>, logs ${HOME:-/root}/.usb-automount.log) + systemd/usb-automount.service (Type=oneshot, WantedBy=multi-user.target — boot + hotplug + manual systemctl start usb-automount), gated in postinstall.sh's systemd loop exactly like autostart (flag_is_set usb-automount → skip with hint). Purpose: a plugged-in stick is auto-mounted world-writable, ready for pos system backup's post-verify USB copy. Docs: SYSTEMD.md (service section + gating code block), SCRIPTS.md (feature section + systemd bullet + TOC), AGENT_Context tree + filetable rows (postinstall.sh count corrected 152→163 — it was already 6 lines stale), README index rows. Verified with a stub suite (/tmp/opencode/usb-automount-test — lsblk JSON fixtures, mount/mountpoint/udevadm/sudo stubs, MOUNT_BASE/UDEV_RULES_DIR seams, HOME isolation): 47/47 green. make gen && make check green. Gotcha learned: ${VAR:-{...}} with a { inside the parameter-expansion default mis-parses in bash (emits a stray } — printf of a multi-line value showed }}); avoid braces in :- defaults.

  • 2026-08-13pos system backup copies the finished backup to a USB stick. Detection runs after the archive verifies (so a stick plugged in while the backup ran is found; if none is mounted, one re-scan prompt before giving up — s skips, EOF from cron skips silently, rc stays 0). Single stick → y/N confirm; several → numbered pick (0 = skip). Copy lands in <usb>/backups/ (mkdir -p; chmod 600 best-effort — vfat chmod failures warn, never fail), and the transfer is proven 100% by sha256 source-vs-copy before any success is announced: mismatch → warn with both hashes + notify_send "USB copy FAILED…" + rc=1 (the ERR trap is re-armed mid-script so a USB-phase failure no longer notifies "Backup FAILED"). Detection: lsblk -J → recursive jq filter (rm==true && mounted && type part|disk, space-safe via JSON) or pinned BACKUP_USB_ROOT seam (= <root>/backups/, skips detection — also the test seam). Docs: usage() Environment, POS.md backup row, howto/system.md (USB section + env table + mismatch troubleshooting), DEV.md system.env list. Verified with a stub suite (/tmp/opencode/backup-test — sudo/gpg/lsblk/sender stubs, HOME isolation, per-test lsblk JSON fixtures, corrupting-cp + vfat-chmod override stubs): 40/40 green (skip s/EOF, seam y/n, detect single, multi pick 2/0, re-scan after replug, corrupt copy rc=1 + honest notify, vfat tolerance). make gen && make check green.

  • 2026-08-13pos 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-12pos 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-triggerbin/pos-system-schedule, lib/eventer-lib.shlib/scheduler-lib.sh (git mv; installed by install.sh), config/event.env + config/event-rules.templateconfig/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-12pos 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-12pos 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-11share 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).

  • 2026-08-11pos network checkport nmap overhaul: two-pass engine — pass 1 = fast -Pn -T4 --max-retries 1 scan of only the asked ports (was: all 65535) with per-port state + nmap service names; pass 2 (--versions, opt-in) = -sV --version-light on open ports only (generous host-timeout — version probing a silent service otherwise made nmap skip the host entirely), fallback fast banner probe for open TCP with no version info; TCP fast path ~2s for 3 ports. Unprivileged UDP now falls back to the nc engine (Debian nmap -sU requires root and quit outright); IPv6 hosts get -6; no output/filtered states set rc=1; --timeout scales nmap host-timeouts. New --versions flag in # POS_FLAGS: (completions regenerated) + usage text; port-metadata fallback retained. make gen && make check green.

  • 2026-08-11pos communication matrix sender login error reporting: captures HTTP status + Matrix errcode/error from the JSON body (temp file, not stdout) instead of a generic "wrong credentials?" message — distinguishes unreachable homeserver from rejected credentials; auto-prepends @ when --user is bare (e.g. --user alice:example.org@alice:example.org).

  • 2026-08-11 — New share category — usb and nfs moved out of pos usb / pos system into pos share: pos share usb server (was pos-usb-server), pos share nfs server + pos share nfs client (were pos-system-nfs-*). Renamed the three tools (bin/pos-share-*), updated # POS: headers/usage strings, INTERACTIVE_CMDS (usb-servershare-usb-server), bin/pos usage() EXAMPLES, and the notify-scope comment in pos-system-backup. Docs: new DOC/howto/share.md (USB + NFS consolidated; howto/usb.md deleted, NFS sections stripped from howto/system.md), POS.md ### share section (replaces ### usb, nfs rows moved out of ### system), HOWTO/README indices, AGENT_Context hand-written spots, root README, DEV.md INTERACTIVE_CMDS example, AGENTS.md categories. Category is the home for future smb. make gen && make check green; /usr/local/bin refreshed.

  • 2026-08-09 — Matrix/Synapse communication tools — pos communication matrix sender + listener, completing the second notify platform lib/notify.sh was designed for (NOTIFY_PLATFORM=telegram,matrix fan-out; the sender implements the send <value> [--markdown] contract via notify_sender_name()'s default key→tool mapping, no lib changes). Sender (bin/pos-communication-matrix-sender): send <value> [--markdown] [--room <id|alias>] PUTs m.room.message (m.text) to the client-server API v3 — room ids/aliases URL-encoded (#pos:example.org%23pos%3A…), unique per-message txn id, --markdown sends org.matrix.custom.html via a best-effort markdown→HTML converter (bold/italic/code/fences/strike/links/headers/lists, escapes HTML, never fails the send); login --user <@id> (masked password prompt → m.login.password → saves access_token+user_id); test. Config scope matrix (~/.config/linux_post_install/matrix.env, MATRIX_HOMESERVER/MATRIX_ACCESS_TOKEN/MATRIX_USER_ID/MATRIX_ROOM_ID, secret masked) registered via # POS_CONFIG:pos config matrix + tab-completion scope. Listener (bin/pos-communication-matrix-listener): systemd user daemon (pos-matrix-listener.service) long-polling /sync (30s timeout, per-sync since token, compact filter dropping presence/account_data/device noise, m.room.message only); reacts to MATRIX_USER_ID's own messages (resolved via /account/whoami if unset), MATRIX_ROOM_ID restricts to one room; / and ! both resolve; replies threaded m.in_reply_to; @quiet no-reply marker; /cmd::desc=… map descriptions; ai … bridge (pos ai gemini ask, per-room session matrix-<room>, ai /reset clears, markdown stripped); interactive editor (--status/--enable/--disable/--run), 60s command timeout, exit-code prefix, ~3800-char truncation. communication-matrix-listener added to INTERACTIVE_CMDS (stdin editor + forever-loop daemon). Docs: POS.md rows + "in detail" sections + ai bridge note, howto/communication.md rewritten Matrix sections, HOWTO.md index + config table + platform note, bin/pos usage EXAMPLES; make gen && make check green. Verified against a mock homeserver: send plain/markdown/--room/test request shape (URL-encoding, Bearer auth, JSON body), login token save, listener owner-filter + /status reply + /help + @quiet silence + non-zero exit reply + interactive editor add. — state-based threshold rule monitors (eventer). Each line of ~/.config/linux_post_install/event.env is an independent rule: ["<msg>" if ] <check-command> <op> <threshold> (op > < >= <= == !=, unit suffix ok 60c/80%). The check command is run on every pass and its first numeric output compared float-safe; operator detected as the rightmost op threshold pair so checks containing their own >/< (awk, redirection) parse fine. Alerts once on false→true plus one recovery message on true→false (no repeats while a condition holds); per-rule state in ~/.local/share/linux_post_install/eventer/state/ keyed by rule-line hash (editing a rule resets its state). Subcommands: run (timer entrypoint), config (interactive add/remove/edit with validation by test-running the check), list (rules + live values), enable [interval] (systemd user timer pos-event-trigger.timer + oneshot service; 5m…weekly or OnCalendar=…; graceful warnings when no user systemd manager, loginctl enable-linger attempt), disable, status. --dry-run honors the DEV.md dry-run convention. Alerts via lib/notify.sh (Telegram default; other platforms via NOTIFY_PLATFORM). New: bin/pos-system-event-trigger, lib/eventer-lib.sh, config/event.env template (installed no-clobber by postinstall), lib/eventer-lib.sh installed by install.sh, system-event-trigger added to INTERACTIVE_CMDS, usage EXAMPLES row. Docs: POS.md system row, HOWTO.md index row, howto/event-trigger.md; make gen && make check green; functional tests covered trigger/recovery/no-repeat, float + unit parsing, editor add/remove/edit + validation + dry-run, timer enable/disable/status (graceful), dispatcher routing.

  • 2026-08-09pos media mp3/mp4 hardened + smart format selection. Both tools: yt-dlp calls go through spawn (honor $DRY_RUN; --dry-run prints the exact command and skips dep checks), -o/--output, --no-playlist, --cookies (file existence check), clean ffmpeg/yt-dlp guards, # POS_FLAGS: for completion, full metadata (--embed-metadata --embed-chapters --embed-thumbnail --no-overwrites, mp3 also --convert-thumbnails jpg + --parse-metadata "%(artist,uploader)s:%(artist)s" so the uploader fills the artist tag). mp3 gains --by-artist (~/Music/<artist>/<title>.mp3). mp4 gains -f <id> / --best / --worst (no prompt), conflict validation, and an interactive picker that shows a curated -F table ([audio]/[video]/[combo] grouping, raw clutter dropped) on stderr — stdout carries only the chosen id (ui_pick lesson) — with id validation against the real table and empty/best default. Docs: howto/media.md rewritten (flags, metadata, by-artist, troubleshooting); make gen && make check green.

  • 2026-08-09 — Telegram ai … now answers about a message you reply to: the listener extracts reply_to_message.text (falls back to caption) from each update and passes it to handle_message; the AI bridge prefixes the prompt with [Reply context — the message you are replying to]. So replying to a /status output and asking ai check this details gives the model the actual output. Applies only to the AI bridge (mapped /commands untouched); reply context rides in the user turn so the session records what was analyzed. Docs: howto/ai.md bridge section.

  • 2026-08-09pos ai gemini sessions + Telegram-friendly replies. --session <name> gives ask/chat persistent memory (~/.local/share/linux_post_install/ai/<name>.json, capped at 40 turns, pruning keeps the first user turn as scene); new sessions subcommand (list / reset <name>). Telegram listener now keeps one session per chat (telegram-<chat_id>) with ai /reset to clear. New --system "<text>" flag injects a Gemini systemInstruction (via jq merge) sent every turn but never stored in the session file; the listener passes a Telegram-voice prompt ("reply like a friendly Telegram chat, use emojis") and strips markdown (**, *, backticks, #, links, lists, blockquotes) from replies before sendMessage, since messages go out as plain text. Docs: howto/ai.md (flags, sessions, bridge memory/formatting), make gen && make check green.

  • 2026-08-09 — Fixed pos config secret-value corruption: cfg_read_secret's cursor-advance echo went to stdout and, since the function is called via $(...), a leading \n ended up inside every secret value → the env file got AI_GEMINI_API_KEY="\n<key>", unreadable by cfg_value/load_config (menu showed (not set), pos ai gemini demanded a key). The newline now goes to the terminal (echo >&2). Defense in depth: cfg_write/write_config_key strip CR and truncate multi-line pastes (warn), cfg_value and the ai/telegram load_configs strip CR on read. Verified on a real PTY (piped tests couldn't reproduce — non-TTY stdin skips the echo path).

  • 2026-08-09ai category — pos ai gemini (ask/chat/models) via Google Gemini REST API. ask prints only the answer (pipe/script/Telegram-friendly), chat is a multi-turn REPL (q/quit/Ctrl+C, /reset, empty input re-prompts), models lists generateContent-capable ids and flags the default; --model override; default gemini-2.5-flash. Config scope ai (AI_GEMINI_API_KEY secret + AI_GEMINI_MODEL) in ~/.config/linux_post_install/ai.env, edited via pos config ai; config/ai.env template installed no-clobber by postinstall; ai-gemini added to INTERACTIVE_CMDS. Telegram listener now answers non-command messages starting with ai via pos ai gemini ask (owner chat only; error replies carry the pos config ai hint) — future intents (reminders) slot in as more case arms in handle_message. Docs: POS.md ai section + listener bridge, howto/ai.md, HOWTO/README index rows, bin/pos usage example.

  • 2026-08-09 — Entertainment plugins gold + weather now emit emoji-visualized Telegram messages. Gold: headline is USD/gram (XAU/oz ÷ 31.1034768), ounce as reference, bid/ask, cleaned timestamp (+00:00/fractional seconds stripped). Weather: per-WMO-code emoji (☀️/🌙 day-night aware for clear sky), °C + feels-like, humidity, wind with unit spacing. Both verified live; emojis are safe in the default plain send mode.

  • 2026-08-09pos tree: prints the live pos command tree (categories → commands → subcommands) by deriving the hierarchy from bin/pos-* filenames + # POS: / # POS_SUBCMDS: headers, so it always matches what the dispatcher can run. Category-less like pos-config; --depth N limit; pos help tree works. Docs: POS.md tree section, bin/pos usage example, make gen regenerated the AGENT_Context tree/dispatch/filetable + _pos_flags[tree].

  • 2026-08-09 — Telegram sender config / config set removed — redundant with pos config telegram (same # POS_CONFIG: registry, masked token display + input, chat-id validation, chmod 600); sender/listener error hints now point there. Deep-review bugfixes in the same commit: mapped /command values containing | are no longer truncated (load_map switched from a | to a \x1f delimiter — previously /up=echo hi | head silently ran echo hi ); pos entertainment send <plugin> [args…] actually forwards the extra args (every arg was shifted in the flag loop, so $@ was empty) and passes -- before the message so leading-- plugin output isn't parsed as an option; write_config_key (entertainment-lib) and cfg_write (config-ui) replaced unescaped sed -i "s|^K=.*|K=\"$v\"|" with grep-v+append so values with &/|/\ no longer mangle (also the path all telegram config now flows through); sync_systemd daemon-reloads after removing timer units; digits config validation accepts negative group/supergroup chat ids (-100…).

  • 2026-08-09 — Fixed telegram listener editor crash on remove/edit/test: ui_pick printed its menu listing to stdout, so idx="$(ui_pick)" captured the menu and the number, and MAP_CMDS[$idx] (arithmetic array subscript) blew up with "syntax error in expression". Menu decoration now goes to stderr; only the picked index is emitted on stdout. Pre-existing bug (before the ::desc work), exposed by the description column.

  • 2026-08-09 — Telegram listener pushes its mapped /commands to the bot's / menu via setMyCommands (auto after every map edit, on --enable, and at daemon start; manual --sync-commands flag). Map lines may carry a menu description: /cmd::short description=bash command (falls back to the bash command, ~40 chars). Names are validated against Telegram's lowercase [a-z0-9_] rule — invalid ones are skipped from the menu with a warning but still resolve when typed; empty map clears the menu. Fixed latent bugs found by the sync work: map_has (awk END{exit 1} overrode the match), and warn() went to stdout so it leaked into the generated JSON (now stderr).

  • 2026-08-09pos config <TAB> scope completion is now cached at gen time (_pos_config_scopes array emitted by make gen from the # POS_CONFIG: registry) instead of scanning ~40 tools per TAB — a per-keypress subshell storm that wedged interactive shells for minutes on the loaded homelab box. Two stuck -bash sessions (69%/38% CPU) killed. plugin_marker/plugin_keys hardened with || true so config_keys no longer aborts mid-scan under set -euo pipefail on mixed lib/plugin dirs (installed layout) — fixes missing plugin keys in pos config entertainment.

  • 2026-08-09pos config <scope> interactive config editor: reads the # POS_CONFIG: registry across tools into a single runtime config (~/.config/linux_post_install/*.env, one file per scope, chmod 600); secret masking with show/hide toggle, digits:/num:/url: validation, - to clear, blank keeps; *plugins marker expands plugin vars (entertainment) from entertainment-lib.sh; desc::example value-format hints shown in the editor; gen-docs now handles category-less tools (pos-config), fixed a set -e+pipefail bug that truncated the header registry.

  • 2026-08-09pos-communication-telegrampos-communication-telegram-sender: one canonical send (dropped the legacy --send flag, which duplicated the send subcommand in completion). pos communication telegram <TAB> now completes to just sender listener. lib/notify.sh maps platform telegramtelegram-sender via notify_sender_name(); entertainment-send + health --send check updated. Removed phantom subcommands from howto/communication.md (webhook/logs/broadcast/file never existed).

  • 2026-08-09 — Structure/convention audit fix: --dry-run now truly dry (spawn() honors DRY_RUN, install.sh exports it to child phases, postinstall mutations run-wrapped); gen-docs.sh no longer chmods regenerated files to 0600; make check now syntax-checks apps/entertainment/features/templates; .gitignore protects config/authorized_keys + config/rclone.conf; honest --send confirmation; docs refreshed (notify.sh in lib lists, pos-health systemd units, tsui, scripts/, INTERACTIVE_CMDS).

  • 2026-08-07pos system health --send notification-only; listener @quiet prefix (run mapped command without replying, for commands that self-notify). /status=@quiet pos system health --send = exactly one digest.

  • 2026-08-12pos network download (bin/pos-network-download) — aria2 JSON-RPC daemon + queue control. Daemon: persistent aria2c as a systemd user service (pos-aria2.service, ${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user, enable --now + linger warning on headless boxes), --rpc-listen-port=6800, generated RPC_SECRET in ~/.config/linux_post_install/download.env (chmod 600, env override), unit flags --continue=true --max-connection-per-server=16 --split=16 --seed-time=0 --dir=$HOME/Downloads. Commands (18): start/stop/status (+ bare overview = status+list), add <url…> [--dir --out --split --tmux], torrent <file|magnet…> [--dir --seed --tmux] (base64 addTorrent), metalink <file|url> [--tmux], list (active/waiting/stopped table), info/files/peers <gid>, pause|resume|remove [gid|all] (--forceforce*), purge, move <gid> <pos>, limit [gid] <speed> (--upload, 0=unlimited, 2M/512K), set <k=v…> [--gid], watch [gid] (2s live repoll; exits when that gid completes). --tmux opens a detached dl-<name> session running watch <gid> (name from --out/URL basename, sanitized, 40-char truncate, -2 on collision; closes itself on completion). Deps: aria2c/jq/curl guards before --help; aria2 added to preinstall PACKAGES. No stdin → not in INTERACTIVE_CMDS. JSON built with jq -nc --arg (never string interpolation — fixes JSON-quote bugs); # POS_SUBCMDS: (18) + # POS_FLAGS: → completions. Test seams RPC_PORT/RPC_SECRET/DOWNLOAD_DIR/USER_SYSTEMD_DIR/ACTIVE_MARKER; 76-case stub-PATH behavior suite green (unit content, secret 600, add→gid, tables, queue ops, error paths). Docs: POS.md network row+detail, howto/network.md section, HOWTO index, AGENT_Context Common Tasks row. make gen && make check green.

Now

  • (none — Tier 1 shipped: pos system health, lib/notify.sh, digest timer)

Next

  • Wire alerting into more tools as they are added (default: source lib/notify.sh, call notify_send on success/failure).

Later — idea backlog

  • 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 rclone remote after verify, --remote flag, digest reports rotation age.
  • Tier 3: pos secret vault — gpg/age-encrypted key-value store; backend for future tools that need stored tokens.
  • Tier 3: pos inventory — machine manifest (OS, packages, services, mounted disks, USB devices) exportable as markdown/JSON.
  • Tier 4: pos self update — pull repo, make gen && make check, re-run install.sh to refresh /usr/local/bin.
  • Tier 4: pos new — scaffold a new tool from templates/pos-tool.sh (category, name, POS header, exec bit, doc stubs).
  • NOT NOW: per-category bin/ subdirectories — flat bin/ + filename dispatch scales fine; revisit only if bin/ passes ~40 files.
  • NOT NOW: split lib/entertainment-lib.sh — fine under 600 lines; revisit if it grows.

Done (summary, newest last)

  • 2026-08-06: Fix entertainment timer 1h not firing — interval_to_oncalendar emitted invalid OnCalendar=*-*-* */N:00:00 (systemd rejects */N in the hour field); now *-*-* 00/N:00:00. Dropped the cron fallback entirely: scheduling is systemd user timers only (sync_cron/interval_to_cron/cron_block removed), status simplified, Nd intervals rejected with a clear error.
  • 2026-08-06: Nested pos subcommands — # POS_SUBCMDS: header annotation (telegram, docker-compose, docker-vbox) + make gen emits a _pos_subcmds completion map; nested tools (telegram listener) auto-list under their parent instead of as a flat sibling (telegram-listener) in pos <category> and tab-completion; generic tool-level completion (subcommands + flags + --help).
  • 2026-08-06: Telegram listenerpos communication telegram listener: interactive /command → bash map editor + owner-only polling daemon as a systemd user service (map in ~/.config/linux_post_install/telegram_commands.env, re-read per message; /help, unknown-command reply, 60s timeout, stdout reply).
  • 2026-08-06: NFS in pos systempos system nfs-server (status/share/ unshare/list/reload/enable/disable, idempotent /etc/exports edits, generic default with Tailscale/WireGuard/LAN examples) + pos system nfs-client (mount/unmount/list + persistent mounts as systemd .mount units ordered after network-online.target, no fstab); nfs-kernel-server + nfs-common added to preinstall PACKAGES.
  • 2026-08-06: pos HOW-TO guide set — DOC/HOWTO.md index + per-category DOC/howto/*.md (network, docker, media, system, ssh, usb, communication, entertainment) with flags, recipes, config, and troubleshooting; wired into DOC/README, root README, AGENTS.md.
  • 2026-08-06: Multi-platform alerting — lib/notify.sh routes via NOTIFY_PLATFORM (notify.env, default telegram; sender contract for Matrix/Synapse later), system.env shared config for health/backup, dynamic effective values in --help, telegram --markdown alias.
  • 2026-08-06: Tier 1 — pos system health (dashboard + --send), lib/notify.sh (wired into backup + firewall), daily digest timer via postinstall.
  • 2026-08-06: Document Map index + Entertainment section in AGENT_Context (cf36780).
  • 2026-08-06: Entertainment module — plugins (weather/joke/gold), pos entertainment config/enable/disable/send/status, auto-trigger + Telegram send.
  • 2026-08-05: pos communication telegram--parse-mode (plain/markdown/html).
  • 2026-08-05: doc/code sync gate — make gen + make check + pre-commit hook.
  • 2026-08-05: pos usb server — USB Redirector control tool (494eae2).
  • 2026-08-05: pos <category> --help auto-discovery in the dispatcher.
  • 2026-08-05: AGENTS.md with lazy-loaded DOC references.