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 20 config = mkIf config.security.hideProcessInformation { 21 21 users.groups.proc.gid = config.ids.gids.proc; 22 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 - }; 23 + fileSystems."/proc".options = [ "hidepid=2" "gid=${toString config.ids.gids.proc}" ]; 41 24 }; 42 25 }
+9 -3
nixos/modules/system/activation/activation-script.nix
··· 154 154 155 155 system.activationScripts.tmpfs = 156 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 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} 160 166 ''; 161 167 162 168 };
+14 -12
nixos/modules/system/boot/stage-1-init.sh
··· 59 59 echo "<<< NixOS Stage 1 >>>" 60 60 echo 61 61 62 - 63 - # Mount special file systems. 62 + # Make several required directories. 64 63 mkdir -p /etc/udev 65 64 touch /etc/fstab # to shut up mount 66 - touch /etc/mtab # to shut up mke2fs 65 + ln -s /proc/mounts /etc/mtab # to shut up mke2fs 67 66 touch /etc/udev/hwdb.bin # to shut up udev 68 67 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 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@ 78 80 79 81 # Log the script output to /dev/kmsg or /run/log/stage-1-init.log. 80 82 mkdir -p /tmp
+3 -1
nixos/modules/system/boot/stage-1.nix
··· 190 190 191 191 inherit udevRules extraUtils modulesClosure; 192 192 193 - inherit (config.boot) resumeDevice devSize runSize; 193 + inherit (config.boot) resumeDevice; 194 + 195 + inherit (config.system.build) earlyMountScript; 194 196 195 197 inherit (config.boot.initrd) checkJournalingFS 196 198 preLVMCommands preDeviceCommands postDeviceCommands postMountCommands preFailCommands kernelModules;
+10 -19
nixos/modules/system/boot/stage-2-init.sh
··· 37 37 # Likewise, stage 1 mounts /proc, /dev and /sys, so if we don't have a 38 38 # stage 1, we need to do that here. 39 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 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@ 46 50 fi 47 51 48 52 ··· 87 91 88 92 89 93 # 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 94 [ -e /proc/bus/usb ] && mount -t usbfs usbfs /proc/bus/usb # UML doesn't have USB by default 96 95 mkdir -m 01777 -p /tmp 97 96 mkdir -m 0755 -p /var /var/log /var/lib /var/db ··· 111 110 # Also get rid of temporary GC roots. 112 111 rm -rf /nix/var/nix/gcroots/tmp /nix/var/nix/temproots 113 112 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 113 123 114 # Create a ramfs on /run/keys to hold secrets that shouldn't be 124 115 # written to disk (generally used for NixOps, harmless elsewhere).
+1 -2
nixos/modules/system/boot/stage-2.nix
··· 20 20 src = ./stage-2-init.sh; 21 21 shellDebug = "${pkgs.bashInteractive}/bin/bash"; 22 22 isExecutable = true; 23 - inherit (config.boot) devShmSize runSize; 24 23 inherit (config.nix) readOnlyStore; 25 24 inherit (config.networking) useHostResolvConf; 26 - ttyGid = config.ids.gids.tty; 25 + inherit (config.system.build) earlyMountScript; 27 26 path = 28 27 [ pkgs.coreutils 29 28 pkgs.utillinux
+37 -4
nixos/modules/tasks/filesystems.nix
··· 18 18 19 19 prioOption = prio: optionalString (prio != null) " pri=${toString prio}"; 20 20 21 + specialFSTypes = [ "proc" "sysfs" "tmpfs" "devtmpfs" "devpts" ]; 22 + 21 23 fileSystemOpts = { name, config, ... }: { 22 24 23 25 options = { ··· 97 99 description = "Disable running fsck on this filesystem."; 98 100 }; 99 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 + 100 113 }; 101 114 102 115 config = { 103 116 mountPoint = mkDefault name; 104 - device = mkIf (config.fsType == "tmpfs") (mkDefault config.fsType); 117 + device = mkIf (elem config.fsType specialFSTypes) (mkDefault config.fsType); 105 118 options = mkIf config.autoResize [ "x-nixos.autoresize" ]; 106 119 107 120 # -F needed to allow bare block device without partitions ··· 110 123 111 124 }; 112 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 + 113 133 in 114 134 115 135 { ··· 131 151 "/bigdisk".label = "bigdisk"; 132 152 } 133 153 ''; 134 - type = types.loaOf types.optionSet; 135 - options = [ fileSystemOpts ]; 154 + type = types.loaOf (types.submodule fileSystemOpts); 136 155 description = '' 137 156 The file systems to be mounted. It must include an entry for 138 157 the root directory (<literal>mountPoint = "/"</literal>). Each ··· 177 196 { assertion = ! (fileSystems' ? "cycle"); 178 197 message = "The ‘fileSystems’ option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}"; 179 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 + } 180 202 ]; 181 203 182 204 # Export for use in other modules 183 205 system.build.fileSystems = fileSystems; 206 + system.build.earlyMountScript = makeSpecialMounts (filter (fs: fs.early) fileSystems); 184 207 185 208 boot.supportedFilesystems = map (fs: fs.fsType) fileSystems; 186 209 ··· 211 234 + " " + (if skipCheck fs then "0" else 212 235 if fs.mountPoint == "/" then "1" else "2") 213 236 + "\n" 214 - ) fileSystems} 237 + ) (filter (fs: !fs.early) fileSystems)} 215 238 216 239 # Swap devices. 217 240 ${flip concatMapStrings config.swapDevices (sw: ··· 257 280 }; 258 281 259 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 + }; 260 293 261 294 }; 262 295