nixos: Add system-wide option to set the hostid
The old boot.spl.hostid option was not working correctly due to an upstream bug. Instead, now we will create the /etc/hostid file so that all applications (including the ZFS kernel modules, ZFS user-space applications and other unrelated programs) pick-up the same system-wide host id. Note that glibc (and by extension, the `hostid` program) also respect the host id configured in /etc/hostid, if it exists. The hostid option is now mandatory when using ZFS because otherwise, ZFS will require you to force-import your ZFS pools if you want to use them, which is undesirable because it disables some of the checks that ZFS does to make sure it is safe to import a ZFS pool. The /etc/hostid file must also exist when booting the initrd, before the SPL kernel module is loaded, so that ZFS picks up the hostid correctly. The complexity in creating the /etc/hostid file is due to having to write the host ID as a 32-bit binary value, taking into account the endianness of the machine, while using only shell commands and/or simple utilities (to avoid exploding the size of the initrd).
This commit is contained in:
parent
12e77fdc3f
commit
e9affb4274
|
@ -45,6 +45,9 @@ with lib;
|
||||||
# Add support for cow filesystems and their utilities
|
# Add support for cow filesystems and their utilities
|
||||||
boot.supportedFilesystems = [ /* "zfs" */ "btrfs" ];
|
boot.supportedFilesystems = [ /* "zfs" */ "btrfs" ];
|
||||||
|
|
||||||
|
# Configure host id for ZFS to work
|
||||||
|
networking.hostId = "8425e349";
|
||||||
|
|
||||||
# Allow the user to log in as root without a password.
|
# Allow the user to log in as root without a password.
|
||||||
users.extraUsers.root.initialHashedPassword = "";
|
users.extraUsers.root.initialHashedPassword = "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,6 +122,9 @@ for o in $(cat /proc/cmdline); do
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# Set hostid before modules are loaded.
|
||||||
|
# This is needed by the spl/zfs modules.
|
||||||
|
@setHostId@
|
||||||
|
|
||||||
# Load the required kernel modules.
|
# Load the required kernel modules.
|
||||||
mkdir -p /lib
|
mkdir -p /lib
|
||||||
|
|
|
@ -188,6 +188,15 @@ let
|
||||||
fsInfo =
|
fsInfo =
|
||||||
let f = fs: [ fs.mountPoint (if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}") fs.fsType fs.options ];
|
let f = fs: [ fs.mountPoint (if fs.device != null then fs.device else "/dev/disk/by-label/${fs.label}") fs.fsType fs.options ];
|
||||||
in pkgs.writeText "initrd-fsinfo" (concatStringsSep "\n" (concatMap f fileSystems));
|
in pkgs.writeText "initrd-fsinfo" (concatStringsSep "\n" (concatMap f fileSystems));
|
||||||
|
|
||||||
|
setHostId = optionalString (config.networking.hostId != null) ''
|
||||||
|
hi="${config.networking.hostId}"
|
||||||
|
${if pkgs.stdenv.isBigEndian then ''
|
||||||
|
echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > /etc/hostid
|
||||||
|
'' else ''
|
||||||
|
echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > /etc/hostid
|
||||||
|
''}
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -51,19 +51,6 @@ in
|
||||||
###### interface
|
###### interface
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
boot.spl.hostid = mkOption {
|
|
||||||
default = "";
|
|
||||||
example = "0xdeadbeef";
|
|
||||||
description = ''
|
|
||||||
ZFS uses a system's hostid to determine if a storage pool (zpool) has been
|
|
||||||
imported on this system, and can thus be used again without reimporting.
|
|
||||||
Unfortunately, this hostid can change under linux from boot to boot (by
|
|
||||||
changing network adapters, for instance). Specify a unique 32 bit hostid in
|
|
||||||
hex here for zfs to prevent getting a random hostid between boots and having to
|
|
||||||
manually and forcibly reimport pools.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
boot.zfs = {
|
boot.zfs = {
|
||||||
useGit = mkOption {
|
useGit = mkOption {
|
||||||
type = types.bool;
|
type = types.bool;
|
||||||
|
@ -196,6 +183,10 @@ in
|
||||||
config = mkMerge [
|
config = mkMerge [
|
||||||
(mkIf enableZfs {
|
(mkIf enableZfs {
|
||||||
assertions = [
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = config.networking.hostId != null;
|
||||||
|
message = "ZFS requires config.networking.hostId to be set";
|
||||||
|
}
|
||||||
{
|
{
|
||||||
assertion = !cfgZfs.forceImportAll || cfgZfs.forceImportRoot;
|
assertion = !cfgZfs.forceImportAll || cfgZfs.forceImportRoot;
|
||||||
message = "If you enable boot.zfs.forceImportAll, you must also enable boot.zfs.forceImportRoot";
|
message = "If you enable boot.zfs.forceImportAll, you must also enable boot.zfs.forceImportRoot";
|
||||||
|
@ -205,9 +196,6 @@ in
|
||||||
boot = {
|
boot = {
|
||||||
kernelModules = [ "spl" "zfs" ] ;
|
kernelModules = [ "spl" "zfs" ] ;
|
||||||
extraModulePackages = [ splPkg zfsPkg ];
|
extraModulePackages = [ splPkg zfsPkg ];
|
||||||
extraModprobeConfig = mkIf (cfgSpl.hostid != "") ''
|
|
||||||
options spl spl_hostid=${cfgSpl.hostid}
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
boot.initrd = mkIf inInitrd {
|
boot.initrd = mkIf inInitrd {
|
||||||
|
|
|
@ -189,6 +189,10 @@ let
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
hexChars = stringToCharacters "0123456789abcdef";
|
||||||
|
|
||||||
|
isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -205,6 +209,20 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
networking.hostId = mkOption {
|
||||||
|
default = null;
|
||||||
|
example = "4e98920d";
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
description = ''
|
||||||
|
The 32-bit host ID of the machine, formatted as 8 hexadecimal characters.
|
||||||
|
|
||||||
|
You should try to make this ID unique among your machines. You can
|
||||||
|
generate a random 32-bit ID using the following command:
|
||||||
|
|
||||||
|
<literal>head -c4 /dev/urandom | od -A none -t x4</literal>
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
networking.enableIPv6 = mkOption {
|
networking.enableIPv6 = mkOption {
|
||||||
default = true;
|
default = true;
|
||||||
description = ''
|
description = ''
|
||||||
|
@ -513,10 +531,15 @@ in
|
||||||
config = {
|
config = {
|
||||||
|
|
||||||
assertions =
|
assertions =
|
||||||
flip map interfaces (i: {
|
(flip map interfaces (i: {
|
||||||
assertion = i.subnetMask == null;
|
assertion = i.subnetMask == null;
|
||||||
message = "The networking.interfaces.${i.name}.subnetMask option is defunct. Use prefixLength instead.";
|
message = "The networking.interfaces.${i.name}.subnetMask option is defunct. Use prefixLength instead.";
|
||||||
});
|
})) ++ [
|
||||||
|
{
|
||||||
|
assertion = cfg.hostId == null || (stringLength cfg.hostId == 8 && isHexString cfg.hostId);
|
||||||
|
message = "Invalid value given to the networking.hostId option.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
boot.kernelModules = [ ]
|
boot.kernelModules = [ ]
|
||||||
++ optional cfg.enableIPv6 "ipv6"
|
++ optional cfg.enableIPv6 "ipv6"
|
||||||
|
@ -872,14 +895,29 @@ in
|
||||||
# clear it if it's not configured in the NixOS configuration,
|
# clear it if it's not configured in the NixOS configuration,
|
||||||
# since it may have been set by dhcpcd in the meantime.
|
# since it may have been set by dhcpcd in the meantime.
|
||||||
system.activationScripts.hostname =
|
system.activationScripts.hostname =
|
||||||
optionalString (config.networking.hostName != "") ''
|
optionalString (cfg.hostName != "") ''
|
||||||
hostname "${config.networking.hostName}"
|
hostname "${cfg.hostName}"
|
||||||
'';
|
'';
|
||||||
system.activationScripts.domain =
|
system.activationScripts.domain =
|
||||||
optionalString (config.networking.domain != "") ''
|
optionalString (cfg.domain != "") ''
|
||||||
domainname "${config.networking.domain}"
|
domainname "${cfg.domain}"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
environment.etc = mkIf (cfg.hostId != null)
|
||||||
|
[
|
||||||
|
{
|
||||||
|
target = "hostid";
|
||||||
|
source = pkgs.runCommand "gen-hostid" {} ''
|
||||||
|
hi="${cfg.hostId}"
|
||||||
|
${if pkgs.stdenv.isBigEndian then ''
|
||||||
|
echo -ne "\x''${hi:0:2}\x''${hi:2:2}\x''${hi:4:2}\x''${hi:6:2}" > $out
|
||||||
|
'' else ''
|
||||||
|
echo -ne "\x''${hi:6:2}\x''${hi:4:2}\x''${hi:2:2}\x''${hi:0:2}" > $out
|
||||||
|
''}
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
services.udev.extraRules =
|
services.udev.extraRules =
|
||||||
''
|
''
|
||||||
KERNEL=="tun", TAG+="systemd"
|
KERNEL=="tun", TAG+="systemd"
|
||||||
|
|
|
@ -187,6 +187,7 @@ let
|
||||||
isArm = system == "armv5tel-linux"
|
isArm = system == "armv5tel-linux"
|
||||||
|| system == "armv6l-linux"
|
|| system == "armv6l-linux"
|
||||||
|| system == "armv7l-linux";
|
|| system == "armv7l-linux";
|
||||||
|
isBigEndian = system == "powerpc-linux";
|
||||||
|
|
||||||
# For convenience, bring in the library functions in lib/ so
|
# For convenience, bring in the library functions in lib/ so
|
||||||
# packages don't have to do that themselves.
|
# packages don't have to do that themselves.
|
||||||
|
|
Loading…
Reference in a new issue