TrueNAS 26 support: one sync implementation, two wrappers
CI / python 3.13 (push) Successful in 15s
CI / shell (shellcheck + syntax) (push) Successful in 8s
CI / python 3.11 (push) Successful in 14s
CI / python 3.12 (push) Successful in 16s
TrueNAS compatibility / compat (push) Successful in 9s

26 rewrites cloud_backup from async to synchronous AND deletes
get_dataset_recursive(), which SNAPSHOT_BLOCK called out of the host module's
namespace. Either is a broken backup found at restore time.

The nested module is now one synchronous implementation talking to middlewared via
call_sync, behind two thin wrappers. apply.sh reads which flavour the installed
middleware declares and injects the matching one: <= 25.10 reaches it through
'await middleware.run_in_thread(...)', 26 is already in a worker thread and calls
it directly. The snapshot/bind-mount/failure logic exists once -- an async twin
would mean every future fix had to land twice.

A middleware whose three wrapped functions disagree about asyncness is refused,
not guessed at. get_dataset_recursive is vendored, removing the dependency on both
versions rather than asserting it.

master stays BROKEN on purpose: iX are still renaming middleware->context,
cloud_backup->entry and adding a required credentials param there. Chasing a
branch that moves daily is how you ship a patch nobody tested.
This commit is contained in:
2026-07-13 18:18:28 +00:00
parent cf2c6a8a02
commit 498b2690e1
8 changed files with 475 additions and 179 deletions
+24 -5
View File
@@ -57,11 +57,30 @@ worse than no alert, because one day it carries a security fix.
appeared somewhere in the signature, so it happily passed a call that could never
work.
- **TrueNAS 26 rewrites the entire `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 —
a broken backup, discovered at restore time. On TrueNAS 26 the nested module now
stays off rather than applying and breaking.
### Added
- **TrueNAS 26 support.** 26 rewrites the entire `cloud_backup` path from async to
**synchronous**, and separately **deletes `get_dataset_recursive()`** — which one
of the injected blocks called out of the host module's namespace. Either one is a
broken backup found at restore time: an `async def` wrapper hands `sync.py` a
coroutine where it unpacks a tuple, and the vanished helper is a straight
`NameError`.
The nested module is now **one synchronous implementation** (talking to middlewared
through `call_sync`) behind **two thin wrappers**. `apply.sh` reads which flavour
the installed middleware declares and injects the matching one: TrueNAS ≤ 25.10
reaches it via `await middleware.run_in_thread(...)`, and TrueNAS 26 — already in a
worker thread — calls it directly. The logic that owns the snapshots, the bind
mounts and the failure modes exists **once**; an async twin would mean every future
fix had to land twice, and the one that got missed would be the one that eats a
backup.
A middleware whose three wrapped functions **disagree** about async-ness is refused
outright rather than guessed at. And `get_dataset_recursive` is now carried as our
own copy — removing the dependency on both versions instead of asserting it.
Both breaks were found by the daily compatibility check **while 26 was still in
beta**, which is the entire point of it.
- **An incompatible TrueNAS no longer sets the permanent kill switch.** `apply.sh`
reused a "nothing left to do" exit that touches `disabled`, which suppresses