docs: TrueNAS compatibility matrix and the two-stage release process
The matrix is regenerated daily by CI rather than typed once and forgotten — a support table that quietly goes stale is a false promise to someone deciding whether to trust this with their backups.
This commit is contained in:
@@ -27,6 +27,66 @@ costs a fraction of the new Storj price.
|
||||
|
||||
---
|
||||
|
||||
## 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.4 | ok | ok | nested + providers; 252-snapshot recursive backup of /mnt/Tap, 18m |
|
||||
| 26.0.0-BETA.3 _(unreleased)_ | ok | **BROKEN** | — |
|
||||
| master _(unreleased)_ | **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.
|
||||
<!-- END COMPAT MATRIX -->
|
||||
|
||||
The table above is **regenerated daily by CI** — it is not a claim somebody typed
|
||||
once and forgot. **TrueCloud Backup does not exist before 24.10**, so earlier
|
||||
versions are absent rather than "unsupported".
|
||||
|
||||
### ⚠️ TrueNAS 26 breaks nested snapshots (not yet released)
|
||||
|
||||
TrueNAS 26 rewrites the whole `cloud_backup` path **from async to synchronous**.
|
||||
Every block the nested module injects is an `async def` wrapping an `await`ed
|
||||
original, so on 26 it would hand `sync.py` a coroutine where it unpacks a tuple.
|
||||
|
||||
You do not need to do anything. `apply.sh` checks these assumptions against the
|
||||
middleware **actually installed on your box** at every boot, and will not apply a
|
||||
module that no longer fits. On TrueNAS 26 the nested module simply stays off:
|
||||
backups keep running, without nested-dataset coverage. A broken backup is worse
|
||||
than a missing feature.
|
||||
|
||||
### How this is kept honest
|
||||
|
||||
[`tools/compat.py`](tools/compat.py) is the single written-down record of what each
|
||||
module assumes about middleware. It is checked in two places:
|
||||
|
||||
- **[CI](.github/workflows/compat.yml), daily** — against iXsystems' source at every
|
||||
release line, *including `master` and the current BETA/RC*. When an unreleased
|
||||
version breaks the patch, it files a bug report automatically. The point is to
|
||||
find out while it is still a beta, rather than after it ships to you.
|
||||
- **`apply.sh`, at every boot** — against the middleware installed on your machine.
|
||||
A module whose assumptions no longer hold is not applied.
|
||||
|
||||
Regenerate the table with:
|
||||
|
||||
```bash
|
||||
python3 tools/compat.py --matrix # human-readable
|
||||
python3 tools/compat.py --matrix --markdown # the table above
|
||||
python3 tools/compat.py --ref master # one version, with reasons
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Before you install
|
||||
|
||||
This project is unofficial and not affiliated with iXsystems. A few things worth
|
||||
@@ -35,7 +95,8 @@ knowing:
|
||||
- It targets **internal middleware APIs** with no stability contract, so a
|
||||
TrueNAS update can break it. Every patch is fail-safe: if it can't apply,
|
||||
middlewared starts normally and the reason is logged to `apply.log`. Check the
|
||||
log after an update.
|
||||
log after an update. See [TrueNAS compatibility](#truenas-compatibility) for
|
||||
which versions currently work.
|
||||
- If you file a TrueNAS bug report, **remove the patch first** and reproduce on a
|
||||
stock system.
|
||||
- **Test your restores.** True of any backup, but it matters more here — see
|
||||
@@ -61,7 +122,10 @@ that capability natively. See [Native support](#if-truenas-adds-native-support).
|
||||
| `patch/create_task.py` | Create TrueCloud tasks with S3/B2 credentials; `verify` the patch state. |
|
||||
| `patch/alert_source.py` | The TrueNAS alert for "an update is available". Installed into `middlewared/alert/source/`. |
|
||||
| `patch/wait_restart.sh` | Waits for boot to actually settle before restarting middlewared. |
|
||||
| `tools/release_notes.py` | Extracts a version's CHANGELOG section; enforces version consistency. Used by CI. |
|
||||
| `release.sh` | Cut a release. Two stages, and the second is refused without the first. See [Releasing](#releasing). |
|
||||
| `tools/compat.py` | What each module assumes about middlewared — and the checker. Run by CI daily *and* by `apply.sh` at every boot. |
|
||||
| `tools/release_notes.py` | Extracts a version's CHANGELOG section; enforces version consistency. |
|
||||
| `tools/release_gate.py` | The barrier: a stable release must have been a release candidate on the same commit. |
|
||||
|
||||
## Development
|
||||
|
||||
@@ -86,9 +150,74 @@ are worth calling out, because nothing else would catch what they catch:
|
||||
newest CHANGELOG entry. `VERSION=` had silently drifted to three different
|
||||
values across the scripts before anything checked.
|
||||
|
||||
Releases are automated: push a `vX.Y.Z` tag and the workflow runs the full suite,
|
||||
verifies the version matches, and cuts a GitHub release whose body **is** the
|
||||
matching `CHANGELOG.md` section — one source of truth for release notes.
|
||||
The project is hosted on **Gitea** (`git.onetick.ninja/flan/truenas-truecloud-patch`)
|
||||
and mirrored to GitHub. Both run the same workflows — Gitea reads
|
||||
`.github/workflows/` too — so a change is checked twice, on two independent runners.
|
||||
|
||||
---
|
||||
|
||||
## Releasing
|
||||
|
||||
**Every release interrupts every user.** An update alert fires on each installed
|
||||
box (see [Update alerts](#update-alerts)), so a release that exists only to fix the
|
||||
last release teaches people to dismiss the alert — and one day that alert will be
|
||||
carrying a security fix. This project cut twelve releases in a single day once.
|
||||
Never again, and not by good intentions: by a gate.
|
||||
|
||||
### The rule
|
||||
|
||||
> A stable `vX.Y.Z` may only be published if a `vX.Y.Z-rcN` tag points at the
|
||||
> **same commit**.
|
||||
|
||||
Release candidates are **invisible to users**: `update.sh` and the update alert both
|
||||
take the newest plain `vX.Y.Z` tag, so an `-rc` is never offered as an update. All
|
||||
the debugging therefore happens across `rc1`, `rc2`, `rc3` — at nobody's expense —
|
||||
instead of across `v0.5.0`, `v0.5.1`, `v0.5.2`, at everybody's.
|
||||
|
||||
"The candidate passed, then I pushed one more little fix" is refused **by name**.
|
||||
That is not hypothetical; it is exactly how v0.5.1 happened.
|
||||
|
||||
### Day to day
|
||||
|
||||
You don't touch the release machinery. Write your changes under `## Unreleased` in
|
||||
`CHANGELOG.md` and push to `main`. `main` is a work surface — it is allowed to be
|
||||
mid-thought. Releasing is a separate, deliberate act.
|
||||
|
||||
### Cutting a release
|
||||
|
||||
```bash
|
||||
bash release.sh 0.6.0 --check # what would ship? what is the next rc?
|
||||
|
||||
bash release.sh 0.6.0 --rc # promotes `## Unreleased` -> v0.6.0, stamps every
|
||||
# VERSION=, tags v0.6.0-rc1, pushes. Users see nothing.
|
||||
|
||||
# ... install it on a real box. Exercise it. Break it. ...
|
||||
# Found a bug? Fix it on main, then `bash release.sh 0.6.0 --rc` again -> rc2.
|
||||
|
||||
bash release.sh 0.6.0 --promote # publishes v0.6.0. REFUSED unless an rc points here.
|
||||
```
|
||||
|
||||
### The gates, and where they live
|
||||
|
||||
The logic is Python so it can be unit-tested; CI is the enforcement boundary
|
||||
because it is the only actor holding the token that publishes. `release.sh` runs the
|
||||
**same** code locally so you fail in 200 ms instead of after a push.
|
||||
|
||||
| gate | enforces | where |
|
||||
| --- | --- | --- |
|
||||
| [`release_notes.py check`](tools/release_notes.py) | every script's `VERSION=` matches the tag; the CHANGELOG section exists and is non-empty; **nothing is stranded under `## Unreleased`** | `release.sh` + CI |
|
||||
| [`release_gate.py`](tools/release_gate.py) | **an rc points at this exact commit** | `release.sh` + CI |
|
||||
| the full suite | ruff, pytest, shellcheck, `bash -n` — re-run against the *tagged* commit | CI |
|
||||
|
||||
A release's body **is** its `CHANGELOG.md` section — there is no second place to
|
||||
write release notes, and therefore no second place for them to go stale. Releases
|
||||
are published on both forges.
|
||||
|
||||
### Why the alert doesn't nag
|
||||
|
||||
A release whose CHANGELOG contains only a `### Docs` section changed no code, and
|
||||
raises **no alert**. Candidates raise no alert either. So the only thing that ever
|
||||
interrupts a user is a real, complete change — which is the entire point.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user