Skip to content

Commit

Permalink
nixos/stage-1-systemd: ZFS support
Browse files Browse the repository at this point in the history
  • Loading branch information
ElvishJerricco committed May 3, 2022
1 parent b521c51 commit 44a6882
Showing 1 changed file with 105 additions and 82 deletions.
187 changes: 105 additions & 82 deletions nixos/modules/tasks/filesystems/zfs.nix
Expand Up @@ -58,6 +58,13 @@ let
# latter case it makes one last attempt at importing, allowing the system to
# (eventually) boot even with a degraded pool.
importLib = {zpoolCmd, awkCmd, cfgZfs}: ''
for o in $(cat /proc/cmdline); do
case $o in
zfs_force|zfs_force=1)
ZFS_FORCE="-f"
;;
esac
done
poolReady() {
pool="$1"
state="$("${zpoolCmd}" import 2>/dev/null | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")"
Expand All @@ -78,6 +85,83 @@ let
}
'';

getPoolFilesystems = pool:
filter (x: x.fsType == "zfs" && (fsToPool x) == pool) config.system.build.fileSystems;

getPoolMounts = prefix: pool:
let
# Remove the "/" suffix because even though most mountpoints
# won't have it, the "/" mountpoint will, and we can't have the
# trailing slash in "/sysroot/" in stage 1.
mountPoint = fs: escapeSystemdPath (prefix + (lib.removeSuffix "/" fs.mountPoint));
in
map (x: "${mountPoint x}.mount") (getPoolFilesystems pool);

createImportService = { pool, systemd, force, prefix ? "" }:
nameValuePair "zfs-import-${pool}" {
description = "Import ZFS pool \"${pool}\"";
# we need systemd-udev-settle until https://github.com/zfsonlinux/zfs/pull/4943 is merged
requires = [ "systemd-udev-settle.service" ];
after = [
"systemd-udev-settle.service"
"systemd-modules-load.service"
"systemd-ask-password-console.service"
];
wantedBy = (getPoolMounts prefix pool) ++ [ "local-fs.target" ];
before = (getPoolMounts prefix pool) ++ [ "local-fs.target" ];
unitConfig = {
DefaultDependencies = "no";
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
environment.ZFS_FORCE = optionalString force "-f";
script = (importLib {
# See comments at importLib definition.
zpoolCmd = "${cfgZfs.package}/sbin/zpool";
awkCmd = "${pkgs.gawk}/bin/awk";
inherit cfgZfs;
}) + ''
poolImported "${pool}" && exit
echo -n "importing ZFS pool \"${pool}\"..."
# Loop across the import until it succeeds, because the devices needed may not be discovered yet.
for trial in `seq 1 60`; do
poolReady "${pool}" && poolImport "${pool}" && break
sleep 1
done
poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool.
if poolImported "${pool}"; then
${optionalString (if isBool cfgZfs.requestEncryptionCredentials
then cfgZfs.requestEncryptionCredentials
else cfgZfs.requestEncryptionCredentials != []) ''
${cfgZfs.package}/sbin/zfs list -rHo name,keylocation ${pool} | while IFS=$'\t' read ds kl; do
{
${optionalString (!isBool cfgZfs.requestEncryptionCredentials) ''
if ! echo '${concatStringsSep "\n" cfgZfs.requestEncryptionCredentials}' | grep -qFx "$ds"; then
continue
fi
''}
case "$kl" in
none )
;;
prompt )
${systemd}/bin/systemd-ask-password "Enter key for $ds:" | ${cfgZfs.package}/sbin/zfs load-key "$ds"
;;
* )
${cfgZfs.package}/sbin/zfs load-key "$ds"
;;
esac
} < /dev/null # To protect while read ds kl in case anything reads stdin
done
''}
echo "Successfully imported ${pool}"
else
exit 1
fi
'';
};

zedConf = generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault {
mkValueString = v:
Expand Down Expand Up @@ -428,14 +512,6 @@ in
'';
postDeviceCommands = concatStringsSep "\n" ([''
ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}"
for o in $(cat /proc/cmdline); do
case $o in
zfs_force|zfs_force=1)
ZFS_FORCE="-f"
;;
esac
done
''] ++ [(importLib {
# See comments at importLib definition.
zpoolCmd = "zpool";
Expand Down Expand Up @@ -464,6 +540,21 @@ in
zfs load-key ${fs}
'') cfgZfs.requestEncryptionCredentials}
'') rootPools));

# Systemd in stage 1
systemd = {
packages = [cfgZfs.package];
services = listToAttrs (map (pool: createImportService {
inherit pool;
systemd = config.boot.initrd.systemd.package;
force = cfgZfs.forceImportRoot;
prefix = "/sysroot";
}) rootPools);
extraBin = {
# zpool and zfs are already in thanks to fsPackages
awk = "${pkgs.gawk}/bin/awk";
};
};
};

systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/zpool".source = pkgs.writeShellScript "zpool-sync-shutdown" ''
Expand Down Expand Up @@ -521,79 +612,11 @@ in
systemd.packages = [ cfgZfs.package ];

systemd.services = let
getPoolFilesystems = pool:
filter (x: x.fsType == "zfs" && (fsToPool x) == pool) config.system.build.fileSystems;

getPoolMounts = pool:
let
mountPoint = fs: escapeSystemdPath fs.mountPoint;
in
map (x: "${mountPoint x}.mount") (getPoolFilesystems pool);

createImportService = pool:
nameValuePair "zfs-import-${pool}" {
description = "Import ZFS pool \"${pool}\"";
# we need systemd-udev-settle until https://github.com/zfsonlinux/zfs/pull/4943 is merged
requires = [ "systemd-udev-settle.service" ];
after = [
"systemd-udev-settle.service"
"systemd-modules-load.service"
"systemd-ask-password-console.service"
];
wantedBy = (getPoolMounts pool) ++ [ "local-fs.target" ];
before = (getPoolMounts pool) ++ [ "local-fs.target" ];
unitConfig = {
DefaultDependencies = "no";
};
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
environment.ZFS_FORCE = optionalString cfgZfs.forceImportAll "-f";
script = (importLib {
# See comments at importLib definition.
zpoolCmd = "${cfgZfs.package}/sbin/zpool";
awkCmd = "${pkgs.gawk}/bin/awk";
inherit cfgZfs;
}) + ''
poolImported "${pool}" && exit
echo -n "importing ZFS pool \"${pool}\"..."
# Loop across the import until it succeeds, because the devices needed may not be discovered yet.
for trial in `seq 1 60`; do
poolReady "${pool}" && poolImport "${pool}" && break
sleep 1
done
poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool.
if poolImported "${pool}"; then
${optionalString (if isBool cfgZfs.requestEncryptionCredentials
then cfgZfs.requestEncryptionCredentials
else cfgZfs.requestEncryptionCredentials != []) ''
${cfgZfs.package}/sbin/zfs list -rHo name,keylocation ${pool} | while IFS=$'\t' read ds kl; do
{
${optionalString (!isBool cfgZfs.requestEncryptionCredentials) ''
if ! echo '${concatStringsSep "\n" cfgZfs.requestEncryptionCredentials}' | grep -qFx "$ds"; then
continue
fi
''}
case "$kl" in
none )
;;
prompt )
${config.systemd.package}/bin/systemd-ask-password "Enter key for $ds:" | ${cfgZfs.package}/sbin/zfs load-key "$ds"
;;
* )
${cfgZfs.package}/sbin/zfs load-key "$ds"
;;
esac
} < /dev/null # To protect while read ds kl in case anything reads stdin
done
''}
echo "Successfully imported ${pool}"
else
exit 1
fi
'';
};
createImportService' = pool: createImportService {
inherit pool;
systemd = config.systemd.package;
force = cfgZfs.forceImportAll;
};

# This forces a sync of any ZFS pools prior to poweroff, even if they're set
# to sync=disabled.
Expand All @@ -619,7 +642,7 @@ in
wantedBy = [ "zfs.target" ];
};

in listToAttrs (map createImportService dataPools ++
in listToAttrs (map createImportService' dataPools ++
map createSyncService allPools ++
map createZfsService [ "zfs-mount" "zfs-share" "zfs-zed" ]);

Expand Down

0 comments on commit 44a6882

Please sign in to comment.