]> ##&name; ###2026.08.08 - The plugin bundle no longer ships `craft-corrupt`. It is a test-only corruption injector (it deliberately corrupts array data) that was bundled for the CI test array's benefit — it is now built only by the CI test workflows and never packaged into the plugin, so it can never be run on a live system by mistake. The bundle now contains just the `scrub-rs` binary. - Removed the `scrub-rs --resolve` diagnostic subcommand (a logical-address → physical-location debugging aid); the Extra options help text no longer suggests it. ###2026.08.07f - The progress table now follows the target-disk selection live: ticking or unticking a disk in the settings form shows or hides its table column immediately, as a preview (the selection itself still persists with Save, as before). Columns no longer depend on which disks have data — status.php parses the run log for every device regardless of the selection, so re-checking a disk brings its column back with the last run's results (or live progress), and hiding a disk never loses its stored progress. - The table's column set is now stable and derived from the checkbox list instead of being assembled dynamically from status blocks: a stray device in a log can no longer create a phantom column, and a new "Scrub order" row numbers the targeted disks in the order they are scrubbed. ###2026.08.07e - Robust parity reconstruction on real arrays: device capacities for all parity/other-disk reads are now queried from the kernel via BLKGETSIZE64 (the same source `blockdev --getsize64` uses), so the startup array canary and parity recovery operate reliably on real block devices. - Hardened batched recovery: the per-batch freeze window is bounded by a wall-clock guard (`--freeze-max`, default 60s), mid-run array degradation is handled gracefully, and a run aborts cleanly if a frozen filesystem cannot be thawed. - Coverage accounting: an EIO isolation budget surfaces read-error gaps instead of silently under-scrubbing, stale-checksum branches are counted once per run, and the final verdict reflects the true state of the data. - Open-path hardening: single-device filesystems are enforced with a clear error (multi-device pool members would be misread), and a missing DEV_TREE root can no longer panic the run. ###2026.08.07d - Scrubs now start almost immediately instead of stalling for minutes on large disks. The old pre-scrub pass that read the entire CSUM_TREE to count bad metadata leaves up front is gone (on a 12 TB disk that pass read ~12-100 GB of checksum metadata before the first status update); metadata failures are now counted live, as a side effect of the scrub itself, deduplicated per node so the counts stay exact. Coverage statistics and exit codes are unchanged. - Churn-proof metadata accounting: on a busy live filesystem, tree branches that the filesystem retired mid-scrub are recognised as normal churn and skipped silently — they no longer risk being reported as metadata corruption. Only genuinely corrupt metadata (checksum-valid copies gone) is reported; DUP mirror divergences continue to surface as recoverable metadata mirror mismatches, including checksum-tree nodes. - Fix: a cancelled run no longer shows a phantom "dry-run):cancelled" column — the status page and stop handler now match only the runner's own per-device markers, so the interrupted disk's counters are shown correctly after a manual stop. ###2026.08.07c - The status table now explains itself: every statistic row and every group header carries a question-mark help popup with a plain-language explanation of what the number means, whether a non-zero value is good or bad, and what (if anything) to do about it. No filesystem expertise is needed to read a scrub result. - The table now shows only the essentials: progress, scrub volume, mismatch and read-error counts, and the recovery outcome. Diagnostic rows that are normally zero (stale blocks, no-checksum blocks, skipped recoveries) and the whole metadata group stay hidden until a disk actually reports a non-zero value, so a healthy all-zero run reads clean at a glance and issue rows appear exactly when there is something to look at. - Target disk(s): disks without a btrfs filesystem are greyed out and cannot be selected, instead of only being rejected when a scrub reaches them. ###2026.08.07b - Fixed: the plugin's icons now actually render in the webGUI. The shipped `images/rotshield.png` was a corrupt 115-byte placeholder (a patched transparent 1x1 PNG whose IDAT fails to decompress), so browsers showed nothing even though the page referenced the right path. It is replaced with a real 128x128 render of the Rotshield logo, and the icon is now also shipped in `icons/` — the webGUI looks there for the settings-page title bar (and sidebar) icons. The plugin list, the Utilities panel and the settings-page title now all show the logo. ###2026.08.07a - Launch path fixed: the plugin manager's Launch button now points at `Utilities/rotshield`, matching where the page actually lives (the .page file's Menu attribute); the previous `Settings/rotshield` launch 404'd. - Community Apps submission prep: added the repository profile (`ca_profile.xml`), the CA wrapper (`plugins/rotshield.xml`, whose PluginURL matches the .plg's pluginURL exactly), an Apache-2.0 LICENSE, and the `rotshield.svg` repository icon wired into both CA files. The wrapper has no MinVer/MaxVer caps, so new unRAID releases don't require maintenance. - Repo layout: the plugin tree moved from `plugin/` to `plugins/` (the Community Apps starter convention — `plugins/` now holds the CA wrapper, the manifest, the build tree and the archive); pluginURL and the build workflow were updated to match. ###2026.08.07 - Rebrand: the plugin is now **Rotshield** (plugin id `rotshield`) under digi-deity/rotshield. The .plg manifest, page + icon, scripts, config dir (/boot/config/plugins/rotshield), rc script and slack-desc were renamed; pluginURL/bundleURL point at the new repo, and the versioned bundle keeps its rotshield-<version>-x86_64-1.txz name. The Rust tools keep their scrub-rs / craft-corrupt names and ship bundled with the plugin only. ###2026.08.05c - Fixed: final per-disk status blocks were served without their key names (state=/device=/counter=), so the progress table went empty as soon as a run ended — after a manual stop AND after natural completion. The table now keeps every disk's exact final counters for all outcomes (clean, issues found, metadata fatal, runtime error, cancelled). - Stop is now robust: it records a cancelled block with the last live counters fetched just before the kill, falling back to the Settings page's last-received payload when the fetch fails (works with STATUS_PORT=0). A stop that arrives after the run already finished is a no-op instead of overwriting the real result, garbage/mismatched status payloads are rejected, and failures to record or kill are reported. - No phantom /dev/nmd1p1 default: a fresh install has an empty target list (table shows headers only), an empty selection is saved as-is, and scrubbing is skipped — never retargeted — until at least one disk is selected. ###2026.08.05b - Plugin updates now actually replace the shipped files. The bundle filename and download URL are derived from the plugin version (rotshield-<version>-x86_64-1.txz, fetched from the release tagged v<version>), so every release downloads a fresh bundle. unRAID's plugin manager never re-downloads a file that already exists on the flash drive ("skipping: ... already exists"), which is why the previous fixed-name bundle — even with upgradepkg --reinstall — left the old php pages and binaries in place after an update. The versioned package is installed with plain upgradepkg --install-new, which replaces the previous package and drops files that no longer ship. - Stale bundles are pruned from the flash drive on install/update, so repeated updates don't accumulate .txz files under /boot/config/plugins/rotshield/install/. - Releases must be tagged v<version> matching the .plg version; the build workflow verifies this and fails the release otherwise. ###2026.08.05a - Target disks are now stored and listed in alphabetical order (natural sort, so /dev/loop2 comes before /dev/loop10). The config stays the single source of truth for the scrub order: the run and the progress table follow DEVICES verbatim, so a hand-edited config order is honored. - Manual stop now keeps the aborted disk's results: scrub.sh fetches one snapshot of the live counters right before killing the run and records a `status:` block with state=cancelled (plus a "finished: CANCELLED" marker) in the run log. The table shows the aborted disk as cancelled with its last known numbers, sticky across page reloads; completed disks of the aborted run keep their done state. - The progress table no longer stretches: fixed column widths (220px statistic column, 170px per disk), long disk names ellipsize with a tooltip, and the wrapper scrolls horizontally when many disks exceed the page width. - Idle disks with no data no longer show a "pending" chip — pending only appears while a run is active. ###2026.08.05 - The Settings page now shows a per-disk **progress table** instead of raw logs (logs are still written under …/runs/ for debugging): rows are the statistics from the status payload, grouped by hierarchy (progress, scrub volume, issues found, metadata, recovery) with color-coded values and relationship hints (e.g. Recovered "3 of 5" shows how corruption was handled), columns are the disks. The disk being scrubbed refreshes live every 5 s; finished disks keep their exact final counters. - scrub-rs now prints a final `status:` block to stdout at the end of every run (same key=value payload as the live server, no flag needed), so the exact final counters survive the process and are read back from the run log — the table stays populated even with STATUS_PORT=0. - New `recovery=1|0` field in the status payload tells the UI whether the recovered/failed/skipped counters are meaningful (array present or not); writer counters are now mirrored unconditionally so the final block is always accurate. - Fixed: recovery summary + exit codes 4/5 were unreachable since the writer handle was consumed before the exit-code decision (recovered runs reported rc=3). The recovery summary now prints and recoverable/ unrecoverable outcomes map to their documented exit codes. ###2026.08.02a - Fixed plugin updates: the bundle is now installed with `upgradepkg --install-new --reinstall`. The bundle filename is kept fixed (so the releases/latest/download URL stays stable), which made upgradepkg see the same package name on every update and skip re-extracting the bundle (`Skipping package ... already installed`) — leaving the old binaries on disk. `--reinstall` forces re-extraction so version bumps actually take effect; `--install-new` keeps fresh installs working. ###2026.08.02 - Live status endpoint: scrub-rs now serves real-time error + progress counters on a localhost-only HTTP port (STATUS_PORT, default 9101; set 0 to disable). The Settings page shows a live "Status" panel that polls it every 2 s while a scrub runs, instead of just "running / idle". - Coarse scrub progress: `progress_pct` (float, two decimals, monotonic non-decreasing) on the status endpoint — the fraction of data dev-extents scrubbed so far, known up front with no full scan. 1 TiB+ disks get ~1000+ steps (~0.1% resolution); tiny test images show chunky ~10% jumps. - New `scrub.sh status` subcommand (curls the live server) backing the panel. - Internal refactor: filesystem re-confirmation moved behind the `fs` seam (filesystem-owned `Reconfirmer`), the array canary moved to a glue module, and the recovery batch writer no longer depends on btrfs internals. No user-visible behaviour change. ###2026.08.01b - Settings form: align inputs with their pre-text labels (fixed-width label column, no more whitespace gap on wide screens); removed the redundant "Current: ..." hint behind the schedule dropdown. ###2026.08.01a - Settings page no longer overflows the viewport: the settings table is constrained to the page width and long log lines scroll inside the log block instead of widening the page. - Removed the redundant post-input hint labels (the field name + help bubble already describe each option). ###2026.08.01 - Log management simplified: past runs are rotated (newest 3 kept), selectable and viewable verbatim from the Settings page; the status JSON file, progress and current-device tracking were removed. - Running state is now derived live from a run lock (atomic mkdir), which also prevents a manual and a scheduled scrub from running simultaneously for the same disk(s). Stale locks are reclaimed automatically. - Notifications now fire in real time: "Scrub started" when each disk's scrub launches, "Scrub finished" (with severity based on the result) as soon as each disk completes, and "Scrub stopped" on manual stop. - Fixed: multi-disk DEVICES and custom CRON values in the config file were unquoted and broke when sourced by bash; they are now quoted and valid for both the PHP and bash config parsers. - Added scrub.sh running|devices subcommands; removed status|lastlog. &bundleURL; /usr/local/emhttp/plugins/rotshield/scripts/install.sh &version; /usr/local/emhttp/plugins/rotshield/scripts/install.sh &version; /usr/local/emhttp/plugins/rotshield/scripts/uninstall.sh