Rotshield

Rotshield

apps.detail.types.plugin from digi-deity

apps.detail.sections.overview

Btrfs scrubber for Unraid/NonRAID arrays: detects bitrot the same way btrfs scrub does, and rebuilds corrupted blocks from the array's P/Q parity instead of just reporting them. Settings-page UI with on-demand or scheduled scrubs, live per-disk progress, and repair that is always verified against the filesystem checksums before anything is written back.

Rotshield

License: Apache 2.0

A btrfs scrubber for unRAID/NonRAID arrays. It checks your data disks for bitrot the same way btrfs scrub does, and on the rare occasion it finds actual corruption, it rebuilds the bad blocks from the array's P/Q parity instead of just reporting them.

Why this exists

unRAID already supports scrubbing a btrfs disk, and that scrub will happily tell you a block is corrupt. It just won't do anything about it. A single-disk btrfs filesystem gets checksums and corruption detection from btrfs, but not self-healing: there's no second copy on the same disk to recover from. So a failed scrub leaves you with a report and two bad options: resilver the entire disk from parity to fix one bad block, or go digging through logs to figure out which file it was and pull it from a backup, if you have one.

Meanwhile the fix is sitting one layer up, and neither side is set up to reach it. btrfs sees a single disk: checksums, block layout, enough information to know exactly which bytes are wrong. It has no idea an array exists above it. unRAID sees the array: which disks make it up and how to rebuild any one of them from the others. It has no idea what's inside those disks, on purpose, since that ignorance is exactly what lets one array hold btrfs, XFS, and anything else side by side. So you end up with one side that knows what's broken and another that knows how to fix it, and nothing connecting the two.

Rotshield exists because that mismatch was annoying enough to fix: most of what it does is a plain scrub, walking the disk and checking every checksum, the same as btrfs scrub. The difference only shows up on the rare block that actually fails: instead of stopping at a report, it pulls the correct data from the surviving disks and parity, verifies it, and writes it back in place. Think of it as a first line of defense: if it can't fix something, you're in exactly the same position you'd have been in anyway, resilver or backup restore still on the table.

Why you can trust this plugin

This tool repairs your data, so it is built the way a repair tool should be: it never touches anything until it is sure, and it is designed so it can never make a bad situation worse. Everything below is open for inspection in the source code — but here it is in plain language.

The checksum is leading. A bad data block is rebuilt from the other disks, and the filesystem checksums proves the rebuilt data is correct before it is written. If the recovered block's checksum doesn't match, then we won't write.

Rigourous testing. Automated tests scrub dozens of disk images covering different array configurations, filesystem layouts and corruption scenarios, compare the results against btrfs's own tools, run scrubs while files are being actively written and deleted, and verify repairs on a full test array in a controlled environment with parity disk(s).

Every fix is verified after it's written. After a repaired block is written back, the tool reads it again and confirms the fix actually landed on the disk. The read-back deliberately invalidates the raw rdev's buffer cache first, so the verification reads the device — not a page-cache echo of the write. A read-back that disagrees is counted readback_failed (never recovered) and escalates the run: it is the clearest "this disk is lying / failing" signal there is.

One honest caveat about live mounts. The read-back proves the raw device holds the right bytes. It cannot update the mounted filesystem's file page cache: if the live mount already had a corrupt file range cached in memory before the repair, that cached copy can keep serving the old bytes to applications until memory pressure evicts it — even though the disk is now correct. When repairs are written under a live mount the tool prints a prominent advisory and the run's status: block carries repaired_while_mounted=1; the plugin escalates its notification. The safe play after any live-mount repair is to reboot or remount the disk (or drop caches) before trusting reads of the repaired files.

A repair only touches the targeted disk. The other disks and the parity disk(s) are never modified because it bypasses the array. If a disk turns out to be beyond saving, the normal unRAID path still works perfectly: pull the disk and rebuild it from parity, exactly as if this tool had never run.

It repairs data, never the filesystem's metadata. To find corruption, the tool has to read the disk's internal metadata. But it deliberately never writes to it: metadata is so sensitive that a single wrong change can be catastrophic for the whole disk, far worse than the corruption being fixed. If the bookkeeping itself turns out to be damaged, the tool refuses to guess and tells you to run the official btrfs check tool, which is designed to repair it safely.

It checks its own understanding before it starts. Before starting, the tool proves that its view of the array is correct by reconstructing a known block from parity and checking it. If something is wrong (wrong disk, wrong offset, misinterpreted array setup) it refuses to run and tells you.

It never mistakes normal activity for corruption. Files being moved, rewritten or deleted can briefly look like corruption. Before anything is written back, the tool re-checks that the problem is still really there and not just regular filesystem churn. This prevents accidental and unwarranted recoveries.

Repairs are race-free. While a block is written back, the filesystem is briefly paused so nothing else can write to that spot at the same moment. The pause is short, covers a small batch of repairs, and is always released automatically — even if the tool itself crashes.

It stays useful when the hardware fails. Corruption is rare, but when it happens it is often the first sign that a disk is starting to fail. This tool is built for that moment: one bad sector doesn't stop the scan of the rest of the disk, parts the disk can no longer read can still be rebuilt from parity, and it never keeps hammering a failing disk sector.

Memory stays modest on huge disks. Scrubbing a 20 TB disk shouldn't eat your NAS's RAM. Memory use stays bounded no matter how large the disk, so a big array never turns a scrub into an out-of-memory risk.

It cannot repair corruption baked in before it reaches the disk. If data is corrupted in memory or while travelling to your NAS — faulty RAM, a bad cable, a failing controller, or a network transfer gone wrong — the wrong bytes get written everywhere at once: the data itself, the checksum meant to protect it, and the parity. Everything then agrees, so nothing looks corrupt. This is outside any repair tool's reach; the defence is in the hardware and in how data travels — ECC memory, good cables, and verifying transfers from other machines.

Repository layout

Path What it is
scrub-rs/ The Rust crate: scrub-rs (btrfs scrub + parity recovery CLI) and craft-corrupt (test-only corruption injector — built by the CI test workflows only, never shipped with the plugin). See scrub-rs/README.md.
plugins/ The Rotshield unRAID plugin: CA wrapper (plugins/rotshield.xml), manifest (plugins/rotshield.plg), build tree (plugins/source/), and docs. See plugins/README.md.
ca_profile.xml / plugins/rotshield.xml Community Applications repository profile + plugin wrapper — what Apps shows in the store.
rotshield.svg / LICENSE Repository icon referenced by the CA files; Apache-2.0 license.
mk_array.sh / teardown_disk.sh / nmdctl Local test-array helpers: assemble a loop-device-backed NonRAID array (and tear it down) for manual experiments.
.github/workflows/ CI: Rust lint, btrfs matrix + array recovery tests, and the plugin build/release.

The Rust source and the plugin live in separate trees on purpose: the plugin bundles only the built binaries (staged into plugins/…/bin/), never the source.

Getting started

Plugin users — install the Rotshield plugin on unRAID, either from Apps (Community Applications, once published) or manually, and drive a scrub from the Settings page. See plugins/README.md for the .plg / .txz bundle, install steps, and configuration keys.

Developers — build the tools, run the test matrix, and use mk_array.sh for a local scratch array:

# Build the binaries (craft-corrupt is test-only; the plugin ships scrub-rs)
cd scrub-rs && cargo build --release
# → target/release/scrub-rs, target/release/craft-corrupt

# Run the Rust unit tests
cargo test

# Set up a local NonRAID test array (loop-backed image files)
./mk_array.sh

# Tear it down again (idempotent; keeps the image files)
./teardown_disk.sh

A typical standalone invocation (on a raw data-disk rdev, with the array running) looks like:

sudo scrub-rs /dev/loop2 --offset +64 --repair
  • --offset +<sectors> — btrfs partition start on the raw device (auto applied by the plugin from /proc/nmdstat).
  • --repair — write reconstructed blocks back (default is dry-run).
  • --freeze-mount <path> — freeze a live mountpoint while repairing.

See scrub-rs --help for the full flag list and the exit-code contract.

Testing

Two layers of automated coverage run in CI (.github/workflows/):

  • btrfs matrix + live simulation (scrub-rs-tests.yml) — generates a matrix of loop-backed btrfs images (checksum algorithms, node sizes, profiles, corruption/anomaly recipes), compares scrub-rs against btrfs check and btrfs scrub, and drives concurrent churn + live scrub.
  • Array recovery tests (scrub-rs-tests.yml) — a 6-disk asymmetric NonRAID array (P, Q + 4 data disks with distinct offsets) exercising single-disk corruption through the P, Q, and combined-PQ recovery paths.

Run the shell harness locally from scrub-rs/tests/integration/ (needs root + btrfs-progs + loop devices) — see scrub-rs/README.md.

Community Apps

This repository follows the unraid-community-apps-starter layout so it can be submitted to the Apps store:

  • ca_profile.xml — repository profile (description, icon, support link).
  • plugins/rotshield.xml — the plugin entry Apps displays; its <PluginURL> matches the .plg's pluginURL exactly.
  • rotshield.svg — the app icon shown in Apps.
  • LICENSE — Apache-2.0 (OSI-approved, required for submission).

To submit, push to main, then run Validate and Scan in the Community Apps submit flow (/submit in the unRAID webGUI).

More documentation

License

Licensed under the Apache License 2.0. scrub-rs is built from this repository's own source — no third-party binaries are bundled with the plugin. craft-corrupt (the test-only corruption injector) is likewise built from source, exclusively by the CI test workflows; it is never bundled with the plugin.

apps.marketingCta.appInstallTitle

apps.marketingCta.appInstallDescription

apps.installHelp.stepOpen apps.installHelp.stepSearchApp apps.installHelp.stepReview apps.installHelp.stepInstall

apps.detail.sections.categories

apps.downloadStats.title

0
apps.downloadStats.metrics.totalDownloads

apps.downloadStats.monthly.title

apps.downloadStats.chart.loading

apps.detail.sections.related

apps.detail.sections.details

apps.detail.details.repository
https://raw.githubusercontent.com/digi-deity/rotshield/main/plugins/rotshield.plg
apps.detail.details.lastUpdated2026-08-11
apps.detail.details.firstSeen2026-08-11