nixos/stage-1-systemd: ZFS support

This commit is contained in:
Will Fancher 2022-04-11 07:35:01 -04:00
parent b521c51c83
commit 44a6882f55

View file

@ -58,6 +58,13 @@ let
# latter case it makes one last attempt at importing, allowing the system to # latter case it makes one last attempt at importing, allowing the system to
# (eventually) boot even with a degraded pool. # (eventually) boot even with a degraded pool.
importLib = {zpoolCmd, awkCmd, cfgZfs}: '' 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() { poolReady() {
pool="$1" 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\" } }")" state="$("${zpoolCmd}" import 2>/dev/null | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")"
@ -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 { zedConf = generators.toKeyValue {
mkKeyValue = generators.mkKeyValueDefault { mkKeyValue = generators.mkKeyValueDefault {
mkValueString = v: mkValueString = v:
@ -428,14 +512,6 @@ in
''; '';
postDeviceCommands = concatStringsSep "\n" (['' postDeviceCommands = concatStringsSep "\n" ([''
ZFS_FORCE="${optionalString cfgZfs.forceImportRoot "-f"}" 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 { ''] ++ [(importLib {
# See comments at importLib definition. # See comments at importLib definition.
zpoolCmd = "zpool"; zpoolCmd = "zpool";
@ -464,6 +540,21 @@ in
zfs load-key ${fs} zfs load-key ${fs}
'') cfgZfs.requestEncryptionCredentials} '') cfgZfs.requestEncryptionCredentials}
'') rootPools)); '') 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" '' systemd.shutdownRamfs.contents."/etc/systemd/system-shutdown/zpool".source = pkgs.writeShellScript "zpool-sync-shutdown" ''
@ -521,79 +612,11 @@ in
systemd.packages = [ cfgZfs.package ]; systemd.packages = [ cfgZfs.package ];
systemd.services = let systemd.services = let
getPoolFilesystems = pool: createImportService' = pool: createImportService {
filter (x: x.fsType == "zfs" && (fsToPool x) == pool) config.system.build.fileSystems; inherit pool;
systemd = config.systemd.package;
getPoolMounts = pool: force = cfgZfs.forceImportAll;
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
'';
};
# This forces a sync of any ZFS pools prior to poweroff, even if they're set # This forces a sync of any ZFS pools prior to poweroff, even if they're set
# to sync=disabled. # to sync=disabled.
@ -619,7 +642,7 @@ in
wantedBy = [ "zfs.target" ]; wantedBy = [ "zfs.target" ];
}; };
in listToAttrs (map createImportService dataPools ++ in listToAttrs (map createImportService' dataPools ++
map createSyncService allPools ++ map createSyncService allPools ++
map createZfsService [ "zfs-mount" "zfs-share" "zfs-zed" ]); map createZfsService [ "zfs-mount" "zfs-share" "zfs-zed" ]);