Skip to content

Releases: basnijholt/zfs-tenant

v0.3.0

Choose a tag to compare

@basnijholt basnijholt released this 27 Sep 00:52
08111ea

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.pyz with 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

Choose a tag to compare

@basnijholt basnijholt released this 26 Sep 19:34
d4ae18a

Breaking changes

  • The nixosModules.sender module is gone. Push with nixpkgs' own services.syncoid instead; the README has an example restricted to send,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=none write when zoned children inherit it, so setup failed, the zone never started, and the gate refused every push. Setup now leaves an existing local mountpoint=none alone. (#2)
  • Replication recovers after retention gaps. The gate now passes syncoid's zfs receive -F through, 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-tenant flake input.
  • TrueNAS SCALE host: download the new zfs-tenant.pyz from this release.
  • Sender using nixosModules.sender: switch to the services.syncoid example in the README.

v0.1.0

Choose a tag to compare

@basnijholt basnijholt released this 26 Sep 14:09

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 the zfs commands syncoid and a restore need, scopes every dataset to the tenant root, and runs zfs with 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 with zfs zone, so the kernel hides the rest of the pool from everything the gate runs. The tenant keeps its own uid there, so zfs allow still 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 the restrict,from=...,command=... line for manual setups.
  • NixOS modules: nixosModules.host (tenants, setup and zone units, pinned keys) and nixosModules.sender (syncoid push timers as a non-root user with only send,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.