Create module and flake

This commit is contained in:
2026-07-30 18:13:19 -04:00
commit 9987f27508
2 changed files with 106 additions and 0 deletions

8
flake.nix Normal file
View File

@@ -0,0 +1,8 @@
{
description = "NixOS module to allow ZFS dataset use in systemd-nspawn containers";
outputs = { ... }: rec {
nixosModules.zfs-containers = import ./zfs-containers.nix;
nixosModules.default = nixosModules.zfs-containers;
};
}

98
zfs-containers.nix Normal file
View File

@@ -0,0 +1,98 @@
{ config, pkgs, lib, ... }:
with lib;
let
cfg = config.containers;
concatMapLines = concatMapStringsSep "\n";
wait_cmds = concatMapLines
(dataset: ''until zfs list "${dataset}"; do echo "Waiting for dataset \"${dataset}\"..."; sleep 0.5; done'');
zoned_cmds = concatMapLines (dataset: ''zfs set zoned=on "${dataset}"'');
delegate_cmds = concatMapLines (dataset: ''zfs zone /proc/"$LEADER_PID"/ns/user "${dataset}"'');
in
{
options.containers = mkOption {
type = types.attrsOf (types.submodule (
{ config, ... }:
{
options.zfs_datasets = mkOption {
description = "ZFS datasets to mount into the container";
type = types.listOf types.str;
default = [];
};
config = lib.mkIf (length config.zfs_datasets >= 1) {
allowedDevices = [
{
node = "/dev/zfs";
modifier = "rw";
}
];
bindMounts."/dev/zfs" = {
hostPath = "/dev/zfs";
isReadOnly = false;
};
# This makes systemd-nspawn immediately notify the parent service that the container is
# ready without waiting for its services to start. ExecStartPost is run when this ready
# signal is received. Turning this off creates a deadlock where the container's
# `zfs-datasets` service needs the `ExecStartPost` script to run, but that script won't
# run until the service exits
extraFlags = [ "--notify-ready=no" ];
config = { pkgs, ... }: {
systemd.targets."zfs" = {
wantedBy = [ "multi-user.target" ];
};
systemd.services."zfs-datasets" = {
description = "Waits for container ZFS datasets to become available";
path = with pkgs; [
zfs
];
script = ''
${wait_cmds config.zfs_datasets}
echo "All ZFS datasets available"
'';
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
TimeoutStartSec = 60 * 5;
};
wantedBy = [ "zfs.target" ];
};
};
};
}
));
};
config.systemd.services = mapAttrs' (name: config:
{
name = "container@${name}";
value = lib.mkIf (length config.zfs_datasets >= 1) {
path = with pkgs; [
zfs
systemd
];
preStart = mkBefore ''
${zoned_cmds config.zfs_datasets}
echo "Enabled zoned setting on zfs datasets"
'';
postStart = mkBefore ''
LEADER_PID=$(machinectl show -P Leader ${name})
${delegate_cmds config.zfs_datasets}
echo "Delegated zfs dataset to namespace of PID $LEADER_PID"
'';
};
}) cfg;
}