245 lines
11 KiB
Markdown
245 lines
11 KiB
Markdown
# truenas-truecloud-patch
|
|
|
|
[](https://git.arch.fyi/flan/truenas-truecloud-patch/actions)
|
|
|
|
Extends TrueNAS SCALE's **TrueCloud Backup** to:
|
|
|
|
- back up to **Backblaze B2 and any S3-compatible provider**, not just Storj;
|
|
- snapshot **datasets that have child datasets** — which is every box running Apps.
|
|
|
|
**Requires TrueNAS SCALE 24.10 or newer.** TrueCloud Backup does not exist before
|
|
that, and `install.sh` will refuse.
|
|
|
|
> Storj raised the price of their TrueNAS-integrated tier from **$5/month to
|
|
> $50/month** in 2026. TrueCloud Backup is the only native TrueNAS feature that gives
|
|
> you pre-backup ZFS snapshots, restic dedup, scheduled tasks with UI progress, and
|
|
> dataset-lock integration. Running restic by hand loses all of it. This gets the
|
|
> feature back with storage you already pay for.
|
|
|
|
---
|
|
|
|
## Install
|
|
|
|
Clone it onto a **pool** (not the boot device — that is wiped on TrueNAS upgrades),
|
|
then run `install.sh` as root:
|
|
|
|
```bash
|
|
git clone https://github.com/sudolulo/truenas-truecloud-patch.git \
|
|
/mnt/tank/truenas-truecloud-patch # replace `tank` with your pool
|
|
cd /mnt/tank/truenas-truecloud-patch
|
|
sudo bash install.sh
|
|
```
|
|
|
|
That registers a PREINIT boot hook, patches middleware in a volatile overlay, and
|
|
restarts middlewared. **It survives TrueNAS updates** — the patch is re-applied at
|
|
every boot, never written to the system dataset.
|
|
|
|
Nested-dataset snapshots are **opt-in**:
|
|
|
|
```bash
|
|
sudo bash install.sh --enable-nested-snapshots
|
|
```
|
|
|
|
Then create a TrueCloud Backup task in the UI with a B2 or S3 credential, or from
|
|
[the CLI](docs/cli.md).
|
|
|
|
**Check it worked:**
|
|
|
|
```bash
|
|
sudo python3 patch/create_task.py verify
|
|
```
|
|
|
|
If something is wrong, the reason is in `apply.log` — start at
|
|
[Recovery](docs/recovery.md).
|
|
|
|
---
|
|
|
|
## TrueNAS compatibility
|
|
|
|
<!-- BEGIN COMPAT MATRIX (generated by tools/compat.py --matrix --markdown) -->
|
|
| TrueNAS | B2/S3 providers | Nested snapshots | Hardware-verified |
|
|
| --- | --- | --- | --- |
|
|
| 24.10.2.4 | ok | ok | — |
|
|
| 25.04.2.6 | ok | ok | — |
|
|
| 25.10.6 | ok | ok | — |
|
|
| 24.10.2.5 _(unreleased)_ | ok | ok | — |
|
|
| 25.10.7 _(unreleased)_ | ok | ok | — |
|
|
| 26.0.0-BETA.3 _(unreleased)_ | ok | ok | — |
|
|
| master _(27-dev)_ | **BROKEN** | **BROKEN** | — |
|
|
|
|
| verdict | meaning |
|
|
| --- | --- |
|
|
| **ok** | Every assumption the patch makes about middleware still holds. |
|
|
| **BROKEN** | middleware changed underneath the patch. `apply.sh` **refuses to apply that module** on this version and leaves TrueNAS stock, so backups keep working — without the module's feature. |
|
|
| **native** | TrueNAS does this itself now. The module retires; it is not a failure. |
|
|
|
|
"ok" means *the patch's assumptions hold*, checked automatically against iX's
|
|
source. It does not mean a human ran a backup on it — that is the
|
|
**Hardware-verified** column, which is filled in by hand and only by doing it.
|
|
|
|
**`master` is not the next release.** iX branches each major off to its own
|
|
`release/` line and master rolls straight on to the one after — so master is
|
|
`27-dev` while 26 is still in beta. A **BROKEN** master means iX has changed
|
|
something that will reach users *a major release from now*, not in the version you
|
|
are about to install. Read the numbered rows for that.
|
|
|
|
A row like `25.10.5 _(unreleased)_` is the next maintenance release: branched by iX,
|
|
not tagged yet, and the very next thing a 25.10.4 box gets. It is checked precisely
|
|
because it is the one unshipped ref that reaches real users without warning.
|
|
<!-- END COMPAT MATRIX -->
|
|
|
|
The table is **regenerated daily by CI** against iXsystems' actual middleware source
|
|
— it is not a claim somebody typed once and forgot.
|
|
|
|
It is also **static analysis**: it proves the patch's assumptions still hold, which is
|
|
a weaker claim than "a backup ran and a restore came back". For what has actually been
|
|
run — which tasks, on which hardware, and the md5 of the file that came back — see
|
|
[docs/verification.md](docs/verification.md).
|
|
|
|
**TrueNAS 26 is supported** as of v0.7.0, and was verified on a real
|
|
**26.0.0-BETA.1** install: a 274-snapshot recursive backup of a 292-dataset pool, and a
|
|
byte-identical restore of a four-level-deep child dataset. The *Hardware-verified*
|
|
column tracks the newest beta iX has tagged (currently BETA.3), so it does not carry
|
|
that mark — a build nobody has actually run a backup on does not get credit for one.
|
|
|
|
26 rewrites `cloud_backup` from async to synchronous and deletes the private ZFS
|
|
methods this module used to call, so getting there took real work: the patch now
|
|
injects the wrapper flavour that matches the installed middleware, reads dataset and
|
|
snapshot lists **from ZFS rather than middleware** (whose queries hide TrueNAS's own
|
|
datasets — 84 of 270 on a real pool, including live app data), and owns the snapshot
|
|
sweep even when it stages nothing (26 decides `recursive` by a rule this patch does not
|
|
share, and would otherwise orphan one snapshot per zvol on every run).
|
|
|
|
**And if a future TrueNAS breaks it, you get a missing feature, not a broken backup.**
|
|
`apply.sh` re-checks the patch's assumptions at every boot and **refuses to apply a
|
|
module whose assumptions no longer hold** — TrueNAS is left stock, and the reason is
|
|
named in `apply.log`. Details: [How it works](docs/how-it-works.md#truenas-26).
|
|
|
|
---
|
|
|
|
## Updating
|
|
|
|
```bash
|
|
cd /mnt/tank/truenas-truecloud-patch
|
|
sudo bash update.sh # newest release
|
|
sudo bash update.sh --check # what would change?
|
|
sudo bash update.sh --rollback # back to the previous version
|
|
```
|
|
|
|
`update.sh` refuses to run on a dirty checkout, pins you to a release tag, and keeps
|
|
the previous revision so a rollback is one command. **There is no auto-update**: this
|
|
patches system internals as root, and a bad commit reaching your box unattended would
|
|
detonate on the next reboot — v0.0.4 shipped exactly such a bug and took 54 apps
|
|
down. The manual step *is* the safety gate.
|
|
|
|
---
|
|
|
|
## Update alerts
|
|
|
|
When a newer release exists, the patch raises a **TrueNAS alert** (the bell in the
|
|
UI) telling you so. It's on by default and checks once a day.
|
|
|
|
```bash
|
|
bash install.sh --no-update-alerts # turn it off
|
|
bash install.sh --update-alerts # turn it back on
|
|
```
|
|
|
|
**It will not nag you about a README.** A release whose CHANGELOG contains only a
|
|
`### Docs` section changed no code, and raises nothing. Anything that touched the
|
|
system raises an INFO alert; a release with a `### Security` section raises a
|
|
WARNING. The CHANGELOG's own section headings are the signal, and a security fix
|
|
anywhere in the range escalates the whole span — a docs-only release on top of a
|
|
security fix still reports as security.
|
|
|
|
**Release candidates never alert.** They are invisible to `update.sh` and to the
|
|
alert, which both take the newest plain `vX.Y.Z` tag. That is what lets debugging
|
|
happen in `-rc` tags instead of in your notification bell — see
|
|
[Releasing](docs/releasing.md).
|
|
|
|
The changelog is read from whichever forge `origin` points at, derived from the
|
|
remote rather than hard-coded. That is not cosmetic: when the changelog cannot be
|
|
read, the alert deliberately fires **anyway** rather than risk hiding a security
|
|
fix — so a wrong URL would not silence the alert, it would make it fire on *every*
|
|
release, including the documentation-only ones this section promises to suppress.
|
|
|
|
### How it works, and why it's built this way
|
|
|
|
TrueNAS **cannot raise an alert from the CLI** — `midclt` exposes only
|
|
`alert.dismiss`, `alert.list`, `alert.list_categories`, `alert.list_policies` and
|
|
`alert.restore`. Alert *creation* is internal to middlewared, and none of its ~60
|
|
one-shot alert classes is generic enough to reuse. So the only way to get a real
|
|
alert is to register an `AlertSource`, which is what `patch/alert_source.py` does.
|
|
|
|
That is also the **least invasive** thing this patch does:
|
|
|
|
| | |
|
|
|---|---|
|
|
| providers module | **modifies** stock files (appends code to `b2.py`, `restic.py`) |
|
|
| nested module | **modifies** stock files (3 middleware modules) |
|
|
| **update alert** | **adds one file. Modifies nothing.** |
|
|
|
|
It's the native mechanism — the same one every built-in TrueNAS alert uses — and
|
|
TrueNAS polls it itself, so there is no cron job and no systemd timer.
|
|
|
|
- **Fail-safe.** Every error path returns `None`. It cannot take middlewared down.
|
|
- **Read-only.** `git ls-remote` plus an HTTPS fetch of the CHANGELOG. It never
|
|
writes to `.git`, so it cannot leave root-owned objects behind the way a
|
|
`git fetch` from middlewared (which runs as root) would.
|
|
- **Removed by `uninstall.sh`.**
|
|
|
|
It only *tells* you. It never updates anything — see
|
|
[Updating](#updating).
|
|
|
|
---
|
|
|
|
## Uninstall
|
|
|
|
```bash
|
|
bash /mnt/tank/truenas-truecloud-patch/uninstall.sh
|
|
```
|
|
|
|
Replace the path with your clone location. Removes the PREINIT hook,
|
|
unmounts the overlay (restoring the original backend files immediately),
|
|
and restores the original UI bundle from backup.
|
|
|
|
---
|
|
|
|
## Documentation
|
|
|
|
| | |
|
|
|---|---|
|
|
| [Nested-dataset snapshots](docs/nested-snapshots.md) | Why stock refuses, what this does instead, and **how to verify your backups actually contain the data** |
|
|
| [How it works](docs/how-it-works.md) | What is patched, how it survives updates, the boot sequence, and what happens when TrueNAS goes native |
|
|
| [Recovery](docs/recovery.md) | middlewared won't start, blank web UI, `verify` shows FAIL |
|
|
| [CLI](docs/cli.md) | Creating tasks with `create_task.py` |
|
|
| [Development](docs/releasing.md) | Tests, CI, and the release process |
|
|
|
|
## What's in the repo
|
|
|
|
| Path | What it is |
|
|
|---|---|
|
|
| `install.sh` | Register the boot hook, patch, restart middlewared. Also `--enable/--disable-nested-snapshots`. |
|
|
| `update.sh` | Fetch and apply a newer release. `--check`, `--rollback`, `--to`, `--main`. |
|
|
| `uninstall.sh` | Remove everything. |
|
|
| `recover.sh` | Emergency: kill switch + restart against stock files. |
|
|
| `patch/apply.sh` | The PREINIT script. Runs at **every boot**. |
|
|
| `patch/truecloud_nested.py` | Nested-dataset staging: plan, mount, verify, tear down, sweep snapshots. |
|
|
| `patch/create_task.py` | Create tasks with S3/B2 credentials; `verify` the patch state. |
|
|
| `tools/compat.py` | What the patch assumes about middlewared — and the checker. Run daily by CI *and* at every boot. |
|
|
| `release.sh` | Cut a release. Two stages, and the second is refused without the first. |
|
|
|
|
## Before you install
|
|
|
|
- This is **unofficial** and not affiliated with iXsystems.
|
|
- It patches **internal middleware APIs** with no stability contract. Every patch is
|
|
fail-safe: if it cannot apply, middlewared starts normally and the reason is logged.
|
|
- **Test your restores.** True of any backup; more so here. See [Verifying it
|
|
works](docs/nested-snapshots.md#verifying-it-works).
|
|
- Filing a TrueNAS bug? **Remove the patch first** and reproduce on a stock system.
|
|
- Provided as-is, no warranty. See LICENSE.
|
|
|
|
## Support
|
|
|
|
If truenas-truecloud-patch is useful to you, consider supporting development via
|
|
[GitHub Sponsors](https://github.com/sponsors/sudolulo) or [Ko-fi](https://ko-fi.com/sudolulo).
|