Merge pull request #168269 from ElvishJerricco/systemd-stage-1-zfs

nixos/stage-1-systemd: ZFS support
main
Will Fancher 2 years ago committed by GitHub
commit b0431d9d91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 199
      nixos/modules/tasks/filesystems/zfs.nix
  2. 2
      nixos/tests/installer-systemd-stage-1.nix

@ -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=y)
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\" } }")"
@ -78,6 +85,95 @@ 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);
getKeyLocations = pool:
if isBool cfgZfs.requestEncryptionCredentials
then "${cfgZfs.package}/sbin/zfs list -rHo name,keylocation,keystatus ${pool}"
else "${cfgZfs.package}/sbin/zfs list -Ho name,keylocation,keystatus ${toString (filter (x: datasetToPool x == pool) cfgZfs.requestEncryptionCredentials)}";
createImportService = { pool, systemd, force, prefix ? "" }:
nameValuePair "zfs-import-${pool}" {
description = "Import ZFS pool \"${pool}\"";
# we need systemd-udev-settle to ensure devices are available
# In the future, hopefully someone will complete this:
# https://github.com/zfsonlinux/zfs/pull/4943
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 != []) ''
${getKeyLocations pool} | while IFS=$'\t' read ds kl ks; do
{
if [[ "$ks" != unavailable ]]; then
continue
fi
case "$kl" in
none )
;;
prompt )
tries=3
success=false
while [[ $success != true ]] && [[ $tries -gt 0 ]]; do
${systemd}/bin/systemd-ask-password "Enter key for $ds:" | ${cfgZfs.package}/sbin/zfs load-key "$ds" \
&& success=true \
|| tries=$((tries - 1))
done
[[ $success = true ]]
;;
* )
${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:
@ -428,14 +524,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";
@ -464,6 +552,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" ''
@ -521,79 +624,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.
@ -619,7 +654,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" ]);

@ -27,7 +27,7 @@
simpleUefiGrubSpecialisation
simpleUefiSystemdBoot
# swraid
# zfsroot
zfsroot
;
}

Loading…
Cancel
Save