Root cause: under `set -euo pipefail`, the view functions built their body
with `body="$(zfs ... | awk ...)"`. If the pipeline exited non-zero — either
because `zfs list -t snapshot` returned non-zero (e.g. the no-snapshots case)
or because the large report was handed to `whiptail --msgbox` as one argv
string and newt choked — `set -e` aborted the entire script, dropping the
whole TUI back to the shell. The snapshot view is the most exposed since it is
both the largest body and the most likely to hit a non-zero zfs result.
Fix: route every list view through a hardened show() that runs the report
producer with `set -e` disabled (so an inner non-zero can never kill the TUI)
and renders via `whiptail --textbox <tmpfile>` (reads from a file instead of
taking the report as an argv string, and scrolls large content cleanly). Each
view_* is now a thin wrapper over a _report_* producer that prints to stdout.
Verified with a stubbed harness: failing pipeline, 5000-row list, and a
non-zero whiptail all now survive; the previous pattern crashed (exit 1).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the Python Textual app (tui.py) with zfs-snapshot.sh, a single-file
whiptail TUI matching the house style of the sibling scripts in this folder
(sudo re-exec header, msg/yesno/run_and_show helpers, action functions, a
menuconfig-style main menu).
Keeps all prior functionality:
- Snapshots: list / create / delete / rollback
- Schedules: cron auto-snapshots with auto-prune (keep last N) + snapshot-now
- Datasets: usage view
- LXC Mounts: pct bind-mountpoint add/remove (hidden when pct is absent)
- Replication: full/incremental zfs send | recv
- Scrub: start / stop / schedule per pool
Cron entries are kept in a single owner file (/etc/cron.d/pve-zfs-tui);
helper scripts are generated on demand. README rewritten to match.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>