feat(zfs): import ZFS pools and add the shared write group #31

Merged
alexion merged 1 commits from task-0005-storage-zfs-foundation into main 2026-07-25 17:28:03 -04:00
Owner

Summary

Adds modules.zfs, the host-level ZFS pool import, and a shared storage group.

  • A host declares its ZFS host id and the pools to import, each pool mapping a dataset-relative path to its mountpoint.
    Pools are imported as durable state (boot.zfs.extraPools), never recreated, so service data survives any rebuild or reimage.
    Declared mountpoints become fileSystems entries with fsType = "zfs", orthogonal to the import — a pool with an empty map is still imported.
  • A shared storage group with a fixed gid (10000) lives in base.nix, the substrate both the host base and the guest-base build on, so a host and every guest carry the identical number.
    That identity is the write mechanism: an identity-mapped container write lands on the pool as the same numeric group with no per-service permission juggling.
    Verified identical (10000) on the host config and the sample guest's interior.

Deviations

  • No host commits an enablement.
    The only host, neogaia, is a laptop with no ZFS pools and a CachyOS kernel whose zfs-kernel-2.4.3 build is marked broken, so a committed enablement would be both dishonest and unbuildable.
    This mirrors the networking foundation's verify-by-temporary-enablement decision.
    The enabled build was verified through extendModules against a forced ZFS-supported kernel; the full nixos-system-neogaia toplevel built, and nix flake check passes on the committed, module-inert tree.
  • pools is typed attrsOf (attrsOf path) rather than a per-pool submodule; no per-pool option beyond the mount map is foreseen, so the submodule layer would have been speculative.
  • The module is the flat modules.zfs, not modules.storage.zfs under a storage/ directory. Nothing else lives under a storage namespace and the sibling foundation modules.network is likewise flat, so the extra level would have grouped a single member. The spec and ADR 0006 were updated to match.

Review

Risk

Overall: High

  • Blast radius: Medium — the module is inert and unreferenced, but the storage group with fixed gid 10000 lands unconditionally in base.nix, so it materializes on every host the flake builds.
  • Reversibility: Medium — removing the inert module is trivial, but once any pool holds files owned by gid 10000 the number is effectively frozen (a chown migration, not a config edit).
  • Test coverage: High — no tests accompany the diff and no host enables the module, so its wiring was only ad-hoc build-checked, never boot-exercised against a real pool.
  • Sensitive domain: High — touches permissions (a shared cross-host/guest write group with a hardcoded gid) and durable data (importing/mounting persistent ZFS pools); the identity-mapping premise is security-load-bearing.
  • Size & complexity: Low — a small body of declarative options and a single mount projection, no tangled control flow.
  • Runtime criticality: Low — guarded by mkIf cfg.enable with zero enablers, so nothing runs until a host opts in.

Standards

No findings left unaddressed. The semicolon-in-comment hard violation, the multi-line-sentence gid comment, the speculative per-pool submodule, and the opaque value binding were all fixed in the diff before commit.

Spec

  • The criterion "A Host enabling modules.zfs builds" is met out-of-band rather than by a committed host — reason: neogaia cannot honestly or buildably carry a ZFS enablement (no pools, broken ZFS kernel build), so it was verified via extendModules with a ZFS-supported kernel, the same approach the networking foundation took.

— Claude

## Summary Adds `modules.zfs`, the host-level ZFS pool import, and a shared `storage` group. - A host declares its ZFS host id and the pools to import, each pool mapping a dataset-relative path to its mountpoint. Pools are imported as durable state (`boot.zfs.extraPools`), never recreated, so service data survives any rebuild or reimage. Declared mountpoints become `fileSystems` entries with `fsType = "zfs"`, orthogonal to the import — a pool with an empty map is still imported. - A shared `storage` group with a fixed gid (10000) lives in `base.nix`, the substrate both the host base and the guest-base build on, so a host and every guest carry the identical number. That identity is the write mechanism: an identity-mapped container write lands on the pool as the same numeric group with no per-service permission juggling. Verified identical (10000) on the host config and the sample guest's interior. ### Deviations - **No host commits an enablement.** The only host, `neogaia`, is a laptop with no ZFS pools and a CachyOS kernel whose `zfs-kernel-2.4.3` build is marked broken, so a committed enablement would be both dishonest and unbuildable. This mirrors the networking foundation's verify-by-temporary-enablement decision. The enabled build was verified through `extendModules` against a forced ZFS-supported kernel; the full `nixos-system-neogaia` toplevel built, and `nix flake check` passes on the committed, module-inert tree. - `pools` is typed `attrsOf (attrsOf path)` rather than a per-pool submodule; no per-pool option beyond the mount map is foreseen, so the submodule layer would have been speculative. - The module is the flat `modules.zfs`, not `modules.storage.zfs` under a `storage/` directory. Nothing else lives under a storage namespace and the sibling foundation `modules.network` is likewise flat, so the extra level would have grouped a single member. The spec and ADR 0006 were updated to match. ## Review ### Risk **Overall: High** - Blast radius: Medium — the module is inert and unreferenced, but the `storage` group with fixed gid 10000 lands unconditionally in `base.nix`, so it materializes on every host the flake builds. - Reversibility: Medium — removing the inert module is trivial, but once any pool holds files owned by gid 10000 the number is effectively frozen (a chown migration, not a config edit). - Test coverage: High — no tests accompany the diff and no host enables the module, so its wiring was only ad-hoc build-checked, never boot-exercised against a real pool. - Sensitive domain: High — touches permissions (a shared cross-host/guest write group with a hardcoded gid) and durable data (importing/mounting persistent ZFS pools); the identity-mapping premise is security-load-bearing. - Size & complexity: Low — a small body of declarative options and a single mount projection, no tangled control flow. - Runtime criticality: Low — guarded by `mkIf cfg.enable` with zero enablers, so nothing runs until a host opts in. ### Standards No findings left unaddressed. The semicolon-in-comment hard violation, the multi-line-sentence gid comment, the speculative per-pool submodule, and the opaque value binding were all fixed in the diff before commit. ### Spec - The criterion "A Host enabling `modules.zfs` builds" is met out-of-band rather than by a committed host — reason: `neogaia` cannot honestly or buildably carry a ZFS enablement (no pools, broken ZFS kernel build), so it was verified via `extendModules` with a ZFS-supported kernel, the same approach the networking foundation took. — Claude
alexion added 1 commit 2026-07-25 17:26:48 -04:00
Add `modules.zfs`, the host-level pool import: a host declares its ZFS host id
and the pools to import with their dataset mountpoints, and the module imports
those pools as durable state rather than recreating them, so a service's data
survives any rebuild or reimage.

Add a shared `storage` group with a fixed gid to the base config both the host
base and the guest-base build on, so a host and every guest carry the same
number and an identity-mapped container write lands on the pool as that group
without per-service permission juggling.

No host enables the module: the only host is a laptop with no pools and a
kernel with no ZFS build, so the enabled build was verified by ad-hoc
enablement against a ZFS-supported kernel while the committed tree stays inert.
alexion force-pushed task-0005-storage-zfs-foundation from 08393cb3f3 to 7edc1ce94b 2026-07-25 17:26:48 -04:00 Compare
alexion changed title from feat(storage): import ZFS pools and add the shared write group to feat(zfs): import ZFS pools and add the shared write group 2026-07-25 17:27:38 -04:00
alexion merged commit 7edc1ce94b into main 2026-07-25 17:28:03 -04:00
alexion deleted branch task-0005-storage-zfs-foundation 2026-07-25 17:28:03 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: alexion/dotfiles#31