nixos filesystems: unify early filesystems handling

A new internal config option `fileSystems.<name>.early` is added to indicate
that the filesystem needs to be loaded very early (i.e. in initrd). They are
transformed to a shell script in `system.build.earlyMountScript` with calls to
an undefined `specialMount` function, which is expected to be caller-specific.
This option is used by stage-1, stage-2 and activation script to set up and
remount those filesystems. Options for them are updated according to systemd
defaults.

+75 -59
+1 -18
nixos/modules/security/hidepid.nix
··· 20 config = mkIf config.security.hideProcessInformation { 21 users.groups.proc.gid = config.ids.gids.proc; 22 23 - systemd.services.hidepid = { 24 - wantedBy = [ "local-fs.target" ]; 25 - after = [ "systemd-remount-fs.service" ]; 26 - before = [ "local-fs-pre.target" "local-fs.target" "shutdown.target" ]; 27 - wants = [ "local-fs-pre.target" ]; 28 - 29 - serviceConfig = { 30 - Type = "oneshot"; 31 - RemainAfterExit = true; 32 - ExecStart = ''${pkgs.utillinux}/bin/mount -o remount,hidepid=2,gid=${toString config.ids.gids.proc} /proc''; 33 - ExecStop = ''${pkgs.utillinux}/bin/mount -o remount,hidepid=0,gid=0 /proc''; 34 - }; 35 - 36 - unitConfig = { 37 - DefaultDependencies = false; 38 - Conflicts = "shutdown.target"; 39 - }; 40 - }; 41 }; 42 }
··· 20 config = mkIf config.security.hideProcessInformation { 21 users.groups.proc.gid = config.ids.gids.proc; 22 23 + fileSystems."/proc".options = [ "hidepid=2" "gid=${toString config.ids.gids.proc}" ]; 24 }; 25 }
+9 -3
nixos/modules/system/activation/activation-script.nix
··· 154 155 system.activationScripts.tmpfs = 156 '' 157 - ${pkgs.utillinux}/bin/mount -o "remount,size=${config.boot.devSize}" none /dev 158 - ${pkgs.utillinux}/bin/mount -o "remount,size=${config.boot.devShmSize}" none /dev/shm 159 - ${pkgs.utillinux}/bin/mount -o "remount,size=${config.boot.runSize}" none /run 160 ''; 161 162 };
··· 154 155 system.activationScripts.tmpfs = 156 '' 157 + specialMount() { 158 + local device="$1" 159 + local mountPoint="$2" 160 + local options="$3" 161 + local fsType="$4" 162 + 163 + ${pkgs.utillinux}/bin/mount -t "$fsType" -o "remount,$options" "$device" "$mountPoint" 164 + } 165 + source ${config.system.build.earlyMountScript} 166 ''; 167 168 };
+14 -12
nixos/modules/system/boot/stage-1-init.sh
··· 59 echo "<<< NixOS Stage 1 >>>" 60 echo 61 62 - 63 - # Mount special file systems. 64 mkdir -p /etc/udev 65 touch /etc/fstab # to shut up mount 66 - touch /etc/mtab # to shut up mke2fs 67 touch /etc/udev/hwdb.bin # to shut up udev 68 touch /etc/initrd-release 69 - mkdir -p /proc 70 - mount -t proc proc /proc 71 - mkdir -p /sys 72 - mount -t sysfs sysfs /sys 73 - mount -t devtmpfs -o "size=@devSize@" devtmpfs /dev 74 - mkdir -p /run 75 - mount -t tmpfs -o "mode=0755,size=@runSize@" tmpfs /run 76 - mkdir /dev/pts 77 - mount -t devpts devpts /dev/pts 78 79 # Log the script output to /dev/kmsg or /run/log/stage-1-init.log. 80 mkdir -p /tmp
··· 59 echo "<<< NixOS Stage 1 >>>" 60 echo 61 62 + # Make several required directories. 63 mkdir -p /etc/udev 64 touch /etc/fstab # to shut up mount 65 + ln -s /proc/mounts /etc/mtab # to shut up mke2fs 66 touch /etc/udev/hwdb.bin # to shut up udev 67 touch /etc/initrd-release 68 + 69 + # Mount special file systems. 70 + specialMount() { 71 + local device="$1" 72 + local mountPoint="$2" 73 + local options="$3" 74 + local fsType="$4" 75 + 76 + mkdir -m 0755 -p "$mountPoint" 77 + mount -n -t "$fsType" -o "$options" "$device" "$mountPoint" 78 + } 79 + source @earlyMountScript@ 80 81 # Log the script output to /dev/kmsg or /run/log/stage-1-init.log. 82 mkdir -p /tmp
+3 -1
nixos/modules/system/boot/stage-1.nix
··· 190 191 inherit udevRules extraUtils modulesClosure; 192 193 - inherit (config.boot) resumeDevice devSize runSize; 194 195 inherit (config.boot.initrd) checkJournalingFS 196 preLVMCommands preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules;
··· 190 191 inherit udevRules extraUtils modulesClosure; 192 193 + inherit (config.boot) resumeDevice; 194 + 195 + inherit (config.system.build) earlyMountScript; 196 197 inherit (config.boot.initrd) checkJournalingFS 198 preLVMCommands preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules;
+10 -19
nixos/modules/system/boot/stage-2-init.sh
··· 37 # Likewise, stage 1 mounts /proc, /dev and /sys, so if we don't have a 38 # stage 1, we need to do that here. 39 if [ ! -e /proc/1 ]; then 40 - mkdir -m 0755 -p /proc 41 - mount -n -t proc proc /proc 42 - mkdir -m 0755 -p /dev 43 - mount -t devtmpfs devtmpfs /dev 44 - mkdir -m 0755 -p /sys 45 - mount -t sysfs sysfs /sys 46 fi 47 48 ··· 87 88 89 # More special file systems, initialise required directories. 90 - if ! mountpoint -q /dev/shm; then 91 - mkdir -m 0755 /dev/shm 92 - mount -t tmpfs -o "rw,nosuid,nodev,size=@devShmSize@" tmpfs /dev/shm 93 - fi 94 - mkdir -m 0755 -p /dev/pts 95 [ -e /proc/bus/usb ] && mount -t usbfs usbfs /proc/bus/usb # UML doesn't have USB by default 96 mkdir -m 01777 -p /tmp 97 mkdir -m 0755 -p /var /var/log /var/lib /var/db ··· 111 # Also get rid of temporary GC roots. 112 rm -rf /nix/var/nix/gcroots/tmp /nix/var/nix/temproots 113 114 - 115 - # Create a tmpfs on /run to hold runtime state for programs such as 116 - # udev (if stage 1 hasn't already done so). 117 - if ! mountpoint -q /run; then 118 - rm -rf /run 119 - mkdir -m 0755 -p /run 120 - mount -t tmpfs -o "mode=0755,size=@runSize@" tmpfs /run 121 - fi 122 123 # Create a ramfs on /run/keys to hold secrets that shouldn't be 124 # written to disk (generally used for NixOps, harmless elsewhere).
··· 37 # Likewise, stage 1 mounts /proc, /dev and /sys, so if we don't have a 38 # stage 1, we need to do that here. 39 if [ ! -e /proc/1 ]; then 40 + specialMount() { 41 + local device="$1" 42 + local mountPoint="$2" 43 + local options="$3" 44 + local fsType="$4" 45 + 46 + mkdir -m 0755 -p "$mountPoint" 47 + mount -n -t "$fsType" -o "$options" "$device" "$mountPoint" 48 + } 49 + source @earlyMountScript@ 50 fi 51 52 ··· 91 92 93 # More special file systems, initialise required directories. 94 [ -e /proc/bus/usb ] && mount -t usbfs usbfs /proc/bus/usb # UML doesn't have USB by default 95 mkdir -m 01777 -p /tmp 96 mkdir -m 0755 -p /var /var/log /var/lib /var/db ··· 110 # Also get rid of temporary GC roots. 111 rm -rf /nix/var/nix/gcroots/tmp /nix/var/nix/temproots 112 113 114 # Create a ramfs on /run/keys to hold secrets that shouldn't be 115 # written to disk (generally used for NixOps, harmless elsewhere).
+1 -2
nixos/modules/system/boot/stage-2.nix
··· 20 src = ./stage-2-init.sh; 21 shellDebug = "${pkgs.bashInteractive}/bin/bash"; 22 isExecutable = true; 23 - inherit (config.boot) devShmSize runSize; 24 inherit (config.nix) readOnlyStore; 25 inherit (config.networking) useHostResolvConf; 26 - ttyGid = config.ids.gids.tty; 27 path = 28 [ pkgs.coreutils 29 pkgs.utillinux
··· 20 src = ./stage-2-init.sh; 21 shellDebug = "${pkgs.bashInteractive}/bin/bash"; 22 isExecutable = true; 23 inherit (config.nix) readOnlyStore; 24 inherit (config.networking) useHostResolvConf; 25 + inherit (config.system.build) earlyMountScript; 26 path = 27 [ pkgs.coreutils 28 pkgs.utillinux
+37 -4
nixos/modules/tasks/filesystems.nix
··· 18 19 prioOption = prio: optionalString (prio != null) " pri=${toString prio}"; 20 21 fileSystemOpts = { name, config, ... }: { 22 23 options = { ··· 97 description = "Disable running fsck on this filesystem."; 98 }; 99 100 }; 101 102 config = { 103 mountPoint = mkDefault name; 104 - device = mkIf (config.fsType == "tmpfs") (mkDefault config.fsType); 105 options = mkIf config.autoResize [ "x-nixos.autoresize" ]; 106 107 # -F needed to allow bare block device without partitions ··· 110 111 }; 112 113 in 114 115 { ··· 131 "/bigdisk".label = "bigdisk"; 132 } 133 ''; 134 - type = types.loaOf types.optionSet; 135 - options = [ fileSystemOpts ]; 136 description = '' 137 The file systems to be mounted. It must include an entry for 138 the root directory (<literal>mountPoint = "/"</literal>). Each ··· 177 { assertion = ! (fileSystems' ? "cycle"); 178 message = "The ‘fileSystems’ option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}"; 179 } 180 ]; 181 182 # Export for use in other modules 183 system.build.fileSystems = fileSystems; 184 185 boot.supportedFilesystems = map (fs: fs.fsType) fileSystems; 186 ··· 211 + " " + (if skipCheck fs then "0" else 212 if fs.mountPoint == "/" then "1" else "2") 213 + "\n" 214 - ) fileSystems} 215 216 # Swap devices. 217 ${flip concatMapStrings config.swapDevices (sw: ··· 257 }; 258 259 in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems)); 260 261 }; 262
··· 18 19 prioOption = prio: optionalString (prio != null) " pri=${toString prio}"; 20 21 + specialFSTypes = [ "proc" "sysfs" "tmpfs" "devtmpfs" "devpts" ]; 22 + 23 fileSystemOpts = { name, config, ... }: { 24 25 options = { ··· 99 description = "Disable running fsck on this filesystem."; 100 }; 101 102 + early = mkOption { 103 + default = false; 104 + type = types.bool; 105 + internal = true; 106 + description = '' 107 + Mount this filesystem very early during boot. At the moment of 108 + mounting no disks are exposed, so this option is primarily for 109 + special file systems. 110 + ''; 111 + }; 112 + 113 }; 114 115 config = { 116 mountPoint = mkDefault name; 117 + device = mkIf (elem config.fsType specialFSTypes) (mkDefault config.fsType); 118 options = mkIf config.autoResize [ "x-nixos.autoresize" ]; 119 120 # -F needed to allow bare block device without partitions ··· 123 124 }; 125 126 + # Makes sequence of `specialMount device mountPoint options fsType` commands. 127 + # `systemMount` should be defined in the sourcing script. 128 + makeSpecialMounts = mounts: 129 + pkgs.writeText "mounts.sh" (concatMapStringsSep "\n" (mount: '' 130 + specialMount "${mount.device}" "${mount.mountPoint}" "${concatStringsSep "," mount.options}" "${mount.fsType}" 131 + '') mounts); 132 + 133 in 134 135 { ··· 151 "/bigdisk".label = "bigdisk"; 152 } 153 ''; 154 + type = types.loaOf (types.submodule fileSystemOpts); 155 description = '' 156 The file systems to be mounted. It must include an entry for 157 the root directory (<literal>mountPoint = "/"</literal>). Each ··· 196 { assertion = ! (fileSystems' ? "cycle"); 197 message = "The ‘fileSystems’ option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}"; 198 } 199 + { assertion = all (x: !x.early || (x.label == null && !x.autoFormat && !x.autoResize)) fileSystems; 200 + message = "Early filesystems don't support mounting by label, auto formatting and resizing"; 201 + } 202 ]; 203 204 # Export for use in other modules 205 system.build.fileSystems = fileSystems; 206 + system.build.earlyMountScript = makeSpecialMounts (filter (fs: fs.early) fileSystems); 207 208 boot.supportedFilesystems = map (fs: fs.fsType) fileSystems; 209 ··· 234 + " " + (if skipCheck fs then "0" else 235 if fs.mountPoint == "/" then "1" else "2") 236 + "\n" 237 + ) (filter (fs: !fs.early) fileSystems)} 238 239 # Swap devices. 240 ${flip concatMapStrings config.swapDevices (sw: ··· 280 }; 281 282 in listToAttrs (map formatDevice (filter (fs: fs.autoFormat) fileSystems)); 283 + 284 + # Sync mount options with systemd's src/core/mount-setup.c: mount_table. 285 + fileSystems = mapAttrs (n: fs: fs // { early = true; }) { 286 + "/proc" = { fsType = "proc"; options = [ "nosuid" "noexec" "nodev" ]; }; 287 + "/sys" = { fsType = "sysfs"; options = [ "nosuid" "noexec" "nodev" ]; }; 288 + "/run" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=755" "size=${config.boot.runSize}" ]; }; 289 + "/dev" = { fsType = "devtmpfs"; options = [ "nosuid" "strictatime" "mode=755" "size=${config.boot.devSize}" ]; }; 290 + "/dev/shm" = { fsType = "tmpfs"; options = [ "nosuid" "nodev" "strictatime" "mode=1777" "size=${config.boot.devShmSize}" ]; }; 291 + "/dev/pts" = { fsType = "devpts"; options = [ "nosuid" "noexec" "mode=620" "gid=${toString config.ids.gids.tty}" ]; }; 292 + }; 293 294 }; 295