Releases: basnijholt/zfs-tenant
Releases · basnijholt/zfs-tenant
Release list
v0.3.0
Features
- Configurable receiver resource limits on NixOS. Each tenant's SSH gate runs in its own user slice with defaults of 512 MiB memory, 64 tasks, and 100% CPU quota. PAM applies soft and hard process limits of 128. Configure these through
tenants.<name>.resourceLimits. (#7)
Bug fixes
- Setup rejects unexpected ZFS delegation. It audits existing tenant roots and descendants before changing them, checks newly created roots for copied grants, and verifies the final permissions. Unsafe grants are reported for an administrator to remove; setup does not silently revoke them. (#7)
- Setup's new existence checks distinguish a missing dataset from other ZFS command failures, and captured ZFS commands use a stable locale for parsing. (#7)
Maintenance and testing
- Shared tenant-root validation and a clearer setup/audit sequence make the code easier to review. (#8)
- The standalone zipapp now uses the wheel's package contents and existing entry point, keeping both distributions aligned. (#8)
- Stricter test fakes reject unexpected commands, and Nix module tests check the intended assertion failures. (#8)
- The two-node VM suite now covers two tenants sharing one receiver, including independent zone restart and reboot recovery. All 34 scenarios passed with real OpenZFS and syncoid. (#8)
- CI checks 234 unit tests and eight isolated zipapp tests on each Python version from 3.10 through 3.14. (#8)
Upgrading
- Update the NixOS flake input, upgrade the Python package, or replace the standalone
zfs-tenant.pyzwith the asset attached to this release. - NixOS requires a distinct, dedicated account for each tenant and enabled SSH PAM sessions. Adjust the new limits if your replication workload needs more resources.
- If setup rejects an existing tree, inspect the named dataset with
zfs allow, explicitly remove unsafe grants as the administrator, and rerun setup. - Follow the updated installation guidance for a patched loaded OpenZFS module, root-controlled files, and isolated Python execution. Manual installations need their own receiver resource controls; the NixOS limits do not bound all ZFS kernel memory.
Full changelog: v0.2.0...v0.3.0
v0.2.0
Breaking changes
- The
nixosModules.sendermodule is gone. Push with nixpkgs' ownservices.syncoidinstead; the README has an example restricted tosend,hold, and the VM test runs that configuration.
Bug fixes
- Setup survives reboots and rebuilds once the tenant has datasets. OpenZFS rejects even an unchanged
mountpoint=nonewrite when zoned children inherit it, so setup failed, the zone never started, and the gate refused every push. Setup now leaves an existing localmountpoint=nonealone. (#2) - Replication recovers after retention gaps. The gate now passes syncoid's
zfs receive -Fthrough, so ZFS can roll the destination back to an older common snapshot when the sender's retention deleted the newest shared one. Receives stay strictly below the tenant root and need no new delegation. (#2) - The encryption check also catches a dataset that a forced receive turned unencrypted, instead of relying only on libzfs refusing that overwrite.
Documentation
- New docs site: https://zfs-tenant.nijho.lt, generated from the README, plus a short design-goals page.
- The encryption check is described accurately: it removes new unencrypted datasets after a successful receive, but cannot undo the disclosure.
- Monitoring advice: check backup freshness per pushed dataset.
Testing
- The two-node VM test now runs real sanoid and syncoid in both directions, recovers from retention gaps, reboots both receivers, and covers filesystem and snapshot limits, aborted receives, forced receives over encrypted datasets, and kernel enforcement inside the zone. Each direction has its own SSH keys.
Upgrading
- NixOS host: update the
zfs-tenantflake input. - TrueNAS SCALE host: download the new
zfs-tenant.pyzfrom this release. - Sender using
nixosModules.sender: switch to theservices.syncoidexample in the README.
v0.1.0
First release: give a friend a quota-capped corner of your ZFS pool for raw encrypted backups, without giving them a shell or a look at your data.
Features
zfs-tenant gate: SSH forced command that accepts only thezfscommands syncoid and a restore need, scopes every dataset to the tenant root, and runszfswith an argument list it builds itself, never a shell. Refuses and removes newly received datasets that are not encrypted.zfs-tenant zone: keeps the tenant's user namespace alive and attaches the tenant root to it withzfs zone, so the kernel hides the rest of the pool from everything the gate runs. The tenant keeps its own uid there, sozfs allowstill decides what it may change; the gate fails closed when the zone is down.zfs-tenant setup: creates the tenant root idempotently with a quota, dataset and snapshot limits,zoned=on, properties that keep it unmounted, and scoped delegation (zfs allow -l/-d).zfs-tenant authorized-key: prints therestrict,from=...,command=...line for manual setups.- NixOS modules:
nixosModules.host(tenants, setup and zone units, pinned keys) andnixosModules.sender(syncoid push timers as a non-root user with onlysend,hold). zfs-tenant.pyz: single-file, standard-library-only build for hosts without pip, such as TrueNAS SCALE (attached to this release).
Requirements
- OpenZFS 2.2 or newer on the host (
zfs zone), with unprivileged user namespaces enabled. - Python 3.10 or newer.
- Senders push raw:
syncoid --no-privilege-elevation --no-sync-snap --sendoptions=w --compress=none.
Tested
A two-node NixOS VM test with OpenZFS 2.4.4 and syncoid 2.3.0 covers pushes and pruning, zone isolation, fail-closed behavior, delegation limits, plaintext refusal, interrupted receives and restores, and the quota.