Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos/stratis: initrd support for stratis root volumes #229767

Merged
merged 10 commits into from
May 25, 2023
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2305.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,8 @@ In addition to numerous new and upgraded packages, this release has the followin

- `boot.initrd.luks.device.<name>` has a new `tryEmptyPassphrase` option, this is useful for OEM's who need to install an encrypted disk with a future settable passphrase

- there is a new `boot/stratisroot.nix` module that enables booting from a volume managed by the Stratis storage management daemon. Use `fileSystems.<name>.stratis.poolUuid` to configure the pool containing the fs.

- Lisp gained a [manual section](https://nixos.org/manual/nixpkgs/stable/#lisp), documenting a new and backwards incompatible interface. The previous interface will be removed in a future release.

- The `bind` module now allows the per-zone `allow-query` setting to be configured (previously it was hard-coded to `any`; it still defaults to `any` to retain compatibility).
Expand Down
21 changes: 19 additions & 2 deletions nixos/modules/installer/tools/nixos-generate-config.pl
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ sub findStableDevPath {

my $st = stat($dev) or return $dev;

foreach my $dev2 (glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) {
foreach my $dev2 (glob("/dev/stratis/*/*"), glob("/dev/disk/by-uuid/*"), glob("/dev/mapper/*"), glob("/dev/disk/by-label/*")) {
my $st2 = stat($dev2) or next;
return $dev2 if $st->rdev == $st2->rdev;
}
Expand Down Expand Up @@ -467,14 +467,25 @@ sub in {
}
}

# is this a stratis fs?
my $stableDevPath = findStableDevPath $device;
my $stratisPool;
if ($stableDevPath =~ qr#/dev/stratis/(.*)/.*#) {
my $poolName = $1;
my ($header, @lines) = split "\n", qx/stratis pool list/;
my $uuidIndex = index $header, 'UUID';
my ($line) = grep /^$poolName /, @lines;
$stratisPool = substr $line, $uuidIndex - 32, 36;
}

# Don't emit tmpfs entry for /tmp, because it most likely comes from the
# boot.tmp.useTmpfs option in configuration.nix (managed declaratively).
next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs");

# Emit the filesystem.
$fileSystems .= <<EOF;
fileSystems.\"$mountPoint\" =
{ device = \"${\(findStableDevPath $device)}\";
{ device = \"$stableDevPath\";
fsType = \"$fsType\";
EOF

Expand All @@ -484,6 +495,12 @@ sub in {
EOF
}

if ($stratisPool) {
$fileSystems .= <<EOF;
stratis.poolUuid = "$stratisPool";
EOF
}

$fileSystems .= <<EOF;
};

Expand Down
1 change: 1 addition & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1343,6 +1343,7 @@
./system/boot/loader/raspberrypi/raspberrypi.nix
./system/boot/loader/systemd-boot/systemd-boot.nix
./system/boot/luksroot.nix
./system/boot/stratisroot.nix
./system/boot/modprobe.nix
./system/boot/networkd.nix
./system/boot/plymouth.nix
Expand Down
64 changes: 64 additions & 0 deletions nixos/modules/system/boot/stratisroot.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{ config, lib, pkgs, utils, ... }:
let
requiredStratisFilesystems = lib.attrsets.filterAttrs (_: x: utils.fsNeededForBoot x && x.stratis.poolUuid != null) config.fileSystems;
in
{
options = {};
config = lib.mkIf (requiredStratisFilesystems != {}) {
assertions = [
{
assertion = config.boot.initrd.systemd.enable;
message = "stratis root fs requires systemd stage 1";
}
];
boot.initrd = {
systemd = {
storePaths = [
"${pkgs.stratisd}/lib/udev/stratis-base32-decode"
"${pkgs.stratisd}/lib/udev/stratis-str-cmp"
"${pkgs.lvm2.bin}/bin/dmsetup"
"${pkgs.stratisd}/libexec/stratisd-min"
"${pkgs.stratisd.initrd}/bin/stratis-rootfs-setup"
];
packages = [pkgs.stratisd.initrd];
extraBin = {
thin_check = "${pkgs."thin-provisioning-tools"}/bin/thin_check";
thin_repair = "${pkgs."thin-provisioning-tools"}/bin/thin_repair";
thin_metadata_size = "${pkgs."thin-provisioning-tools"}/bin/thin_metadata_size";
stratis-min = "${pkgs.stratisd}/bin/stratis-min";
};
services =
lib.attrsets.mapAttrs' (
mountPoint: fileSystem: {
name = "stratis-setup-${fileSystem.stratis.poolUuid}";
value = {
description = "setup for Stratis root filesystem";
unitConfig.DefaultDependencies = "no";
conflicts = [ "shutdown.target" "initrd-switch-root.target" ];
onFailure = [ "emergency.target" ];
unitConfig.OnFailureJobMode = "isolate";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isolate? Seems like the upstream units all use replace-irreversibly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No particular reason, I just modeled it after this:
https://github.com/stratis-storage/stratisd/blob/7d0125be75a68d25329a6e72db4b581fadee536b/src/bin/generators/stratis_setup_generator.rs#L44
I can change it to replace-irreversibly if you want.

wants = [ "stratisd-min.service" "plymouth-start.service" ];
wantedBy = [ "initrd.target" ];
after = [ "paths.target" "plymouth-start.service" "stratisd-min.service" ];
before = [ "initrd.target" "shutdown.target" "initrd-switch-root.target" ];
environment.STRATIS_ROOTFS_UUID = fileSystem.stratis.poolUuid;
serviceConfig = {
Type = "oneshot";
ExecStart = "${pkgs.stratisd.initrd}/bin/stratis-rootfs-setup";
RemainAfterExit = "yes";
};
};
}
) requiredStratisFilesystems;
};
availableKernelModules = [ "dm-thin-pool" "dm-crypt" ] ++ [ "aes" "aes_generic" "blowfish" "twofish"
"serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
"af_alg" "algif_skcipher"
];
services.udev.packages = [
pkgs.stratisd.initrd
pkgs.lvm2
];
};
};
}
9 changes: 9 additions & 0 deletions nixos/modules/tasks/filesystems.nix
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ let
description = lib.mdDoc "Location of the mounted file system.";
};

stratis.poolUuid = lib.mkOption {
type = types.uniq (types.nullOr types.str);
description = lib.mdDoc ''
UUID of the stratis pool that the fs is located in
'';
example = "04c68063-90a5-4235-b9dd-6180098a20d9";
default = null;
};

Comment on lines +39 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how I feel about extending the fileSystems options for this. Can't multiple FSes be in the same pool?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, sure. But I don't see how that creates a problem. Due to how attrsets.mapAttrs' works, this is automatically deduplicated, so only one stage 1 service unit will be generated per pool.

I could put this information somewhere else, I just found it convenient this way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, I think this makes the most sense. Associating it with the FS gives the most useful information.

device = mkOption {
default = null;
example = "/dev/sda";
Expand Down
1 change: 1 addition & 0 deletions nixos/tests/installer-systemd-stage-1.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
simpleUefiGrub
simpleUefiGrubSpecialisation
simpleUefiSystemdBoot
stratisRoot
# swraid
zfsroot
;
Expand Down
35 changes: 35 additions & 0 deletions nixos/tests/installer.nix
Original file line number Diff line number Diff line change
Expand Up @@ -989,4 +989,39 @@ in {
)
'';
};
} // optionalAttrs systemdStage1 {
stratisRoot = makeInstallerTest "stratisRoot" {
createPartitions = ''
machine.succeed(
"sgdisk --zap-all /dev/vda",
"sgdisk --new=1:0:+100M --typecode=0:ef00 /dev/vda", # /boot
"sgdisk --new=2:0:+1G --typecode=0:8200 /dev/vda", # swap
ElvishJerricco marked this conversation as resolved.
Show resolved Hide resolved
"sgdisk --new=3:0:+5G --typecode=0:8300 /dev/vda", # /
"udevadm settle",

"mkfs.vfat /dev/vda1",
"mkswap /dev/vda2 -L swap",
"swapon -L swap",
"stratis pool create my-pool /dev/vda3",
"stratis filesystem create my-pool nixos",
"udevadm settle",

"mount /dev/stratis/my-pool/nixos /mnt",
"mkdir -p /mnt/boot",
"mount /dev/vda1 /mnt/boot"
)
'';
bootLoader = "systemd-boot";
extraInstallerConfig = { modulesPath, ...}: {
config = {
services.stratis.enable = true;
environment.systemPackages = [
pkgs.stratis-cli
pkgs.thin-provisioning-tools
pkgs.lvm2.bin
pkgs.stratisd.initrd
];
};
};
};
}
10 changes: 10 additions & 0 deletions pkgs/tools/filesystems/stratisd/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ stdenv.mkDerivation rec {
lvm2
];

outputs = ["out" "initrd"];

EXECUTABLES_PATHS = lib.makeBinPath ([
xfsprogs
thin-provisioning-tools
Expand All @@ -95,6 +97,14 @@ stdenv.mkDerivation rec {

# remove files for supporting dracut
postInstall = ''
mkdir -p "$initrd/bin"
cp "dracut/90stratis/stratis-rootfs-setup" "$initrd/bin"
mkdir -p "$initrd/lib/systemd/system"
substitute "dracut/90stratis/stratisd-min.service" "$initrd/lib/systemd/system/stratisd-min.service" \
--replace /usr "$out" \
--replace mkdir "${coreutils}/bin/mkdir"
mkdir -p "$initrd/lib/udev/rules.d"
cp udev/61-stratisd.rules "$initrd/lib/udev/rules.d"
rm -r "$out/lib/dracut"
rm -r "$out/lib/systemd/system-generators"
'';
Expand Down