modules/tmproot: Make persistence optional
This commit is contained in:
		@@ -1,7 +1,7 @@
 | 
				
			|||||||
{ lib, extendModules, modulesPath, baseModules, options, config, ... }:
 | 
					{ lib, extendModules, modulesPath, baseModules, options, config, ... }:
 | 
				
			||||||
let
 | 
					let
 | 
				
			||||||
  inherit (lib) recursiveUpdate mkOption;
 | 
					  inherit (lib) recursiveUpdate mkOption mkDefault;
 | 
				
			||||||
  inherit (lib.my) mkBoolOpt';
 | 
					  inherit (lib.my) mkBoolOpt' dummyOption;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  cfg = config.my.build;
 | 
					  cfg = config.my.build;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,19 +17,31 @@ let
 | 
				
			|||||||
  };
 | 
					  };
 | 
				
			||||||
in
 | 
					in
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  options.my = with lib.types; {
 | 
					  options = with lib.types; {
 | 
				
			||||||
    boot.isDevVM = mkBoolOpt' false "Whether the system is a development VM.";
 | 
					    my = {
 | 
				
			||||||
    build = options.system.build;
 | 
					      boot.isDevVM = mkBoolOpt' false "Whether the system is a development VM.";
 | 
				
			||||||
    asDevVM = mkOption {
 | 
					      build = options.system.build;
 | 
				
			||||||
      inherit (asDevVM) type;
 | 
					      asDevVM = mkOption {
 | 
				
			||||||
      default = { };
 | 
					        inherit (asDevVM) type;
 | 
				
			||||||
      visible = "shallow";
 | 
					        default = { };
 | 
				
			||||||
      description = "Configuration as a development VM";
 | 
					        visible = "shallow";
 | 
				
			||||||
 | 
					        description = "Configuration as a development VM";
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Forward declare options that won't exist until the VM module is actually imported
 | 
				
			||||||
 | 
					    virtualisation = {
 | 
				
			||||||
 | 
					      diskImage = dummyOption;
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  config.my.build = {
 | 
					  config = {
 | 
				
			||||||
    # The meta.mainProgram should probably be set upstream but oh well...
 | 
					    virtualisation = {
 | 
				
			||||||
    devVM = recursiveUpdate config.my.asDevVM.system.build.vm { meta.mainProgram = "run-${config.system.name}-vm"; };
 | 
					      diskImage = mkDefault "./.vms/${config.system.name}.qcow2";
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    my.build = {
 | 
				
			||||||
 | 
					      # The meta.mainProgram should probably be set upstream but oh well...
 | 
				
			||||||
 | 
					      devVM = recursiveUpdate config.my.asDevVM.system.build.vm { meta.mainProgram = "run-${config.system.name}-vm"; };
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
{ lib, pkgs, config, ... }:
 | 
					{ lib, pkgs, config, ... }:
 | 
				
			||||||
let
 | 
					let
 | 
				
			||||||
  inherit (lib) concatStringsSep concatMap concatMapStringsSep mkIf mkDefault mkMerge mkVMOverride;
 | 
					  inherit (lib) optionalString concatStringsSep concatMap concatMapStringsSep mkIf mkDefault mkMerge mkVMOverride;
 | 
				
			||||||
  inherit (lib.my) mkOpt' mkBoolOpt' mkVMOverride' dummyOption;
 | 
					  inherit (lib.my) mkOpt' mkBoolOpt' mkVMOverride';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  cfg = config.my.tmproot;
 | 
					  cfg = config.my.tmproot;
 | 
				
			||||||
 | 
					  enablePersistence = cfg.persistDir != null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  showUnsaved =
 | 
					  showUnsaved =
 | 
				
			||||||
    ''
 | 
					    ''
 | 
				
			||||||
@@ -56,27 +57,18 @@ in
 | 
				
			|||||||
  options = with lib.types; {
 | 
					  options = with lib.types; {
 | 
				
			||||||
    my.tmproot = {
 | 
					    my.tmproot = {
 | 
				
			||||||
      enable = mkBoolOpt' true "Whether to enable tmproot.";
 | 
					      enable = mkBoolOpt' true "Whether to enable tmproot.";
 | 
				
			||||||
      persistDir = mkOpt' str "/persist" "Path where persisted files are stored.";
 | 
					      persistDir = mkOpt' (nullOr str) "/persist" "Path where persisted files are stored.";
 | 
				
			||||||
      size = mkOpt' str "2G" "Size of tmpfs root";
 | 
					      size = mkOpt' str "2G" "Size of tmpfs root";
 | 
				
			||||||
      unsaved = {
 | 
					      unsaved = {
 | 
				
			||||||
        showMotd = mkBoolOpt' true "Whether to show unsaved files with `dynamic-motd`.";
 | 
					        showMotd = mkBoolOpt' true "Whether to show unsaved files with `dynamic-motd`.";
 | 
				
			||||||
        ignore = mkOpt' (listOf str) [ ] "Path prefixes to ignore if unsaved.";
 | 
					        ignore = mkOpt' (listOf str) [ ] "Path prefixes to ignore if unsaved.";
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Forward declare options that won't exist until the VM module is actually imported
 | 
					 | 
				
			||||||
    virtualisation = {
 | 
					 | 
				
			||||||
      diskImage = dummyOption;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  config = mkIf cfg.enable (mkMerge [
 | 
					  config = mkIf cfg.enable (mkMerge [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      assertions = [
 | 
					      assertions = [
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
          assertion = config.fileSystems ? "${cfg.persistDir}";
 | 
					 | 
				
			||||||
          message = "The 'fileSystems' option does not specify your persistence file system (${cfg.persistDir}).";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          # I mean you probably _could_, but if you're doing tmproot... come on
 | 
					          # I mean you probably _could_, but if you're doing tmproot... come on
 | 
				
			||||||
          assertion = !config.users.mutableUsers;
 | 
					          assertion = !config.users.mutableUsers;
 | 
				
			||||||
@@ -112,48 +104,6 @@ in
 | 
				
			|||||||
        (pkgs.writeScriptBin "tmproot-unsaved" showUnsaved)
 | 
					        (pkgs.writeScriptBin "tmproot-unsaved" showUnsaved)
 | 
				
			||||||
      ];
 | 
					      ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      # Catch non-existent source directories that are needed for boot (see `pathsNeededForBoot` in
 | 
					 | 
				
			||||||
      # nixos/lib/util.nix). We do this by monkey-patching the `waitDevice` function that would otherwise hang.
 | 
					 | 
				
			||||||
      boot.initrd.postDeviceCommands =
 | 
					 | 
				
			||||||
        ''
 | 
					 | 
				
			||||||
          ensurePersistSource() {
 | 
					 | 
				
			||||||
            [ -e "/mnt-root$1" ] && return
 | 
					 | 
				
			||||||
            echo "Persistent source directory $1 does not exist, creating..."
 | 
					 | 
				
			||||||
            install -dm "$2" "/mnt-root$1" || fail
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          _waitDevice() {
 | 
					 | 
				
			||||||
            local device="$1"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            ${concatMapStringsSep " || \\\n  " (d:
 | 
					 | 
				
			||||||
              let
 | 
					 | 
				
			||||||
                sourceDir = "${d.persistentStoragePath}${d.directory}";
 | 
					 | 
				
			||||||
              in
 | 
					 | 
				
			||||||
                ''([ "$device" = "/mnt-root${sourceDir}" ] && ensurePersistSource "${sourceDir}" "${d.mode}")'')
 | 
					 | 
				
			||||||
              config.environment.persistence."${cfg.persistDir}".directories}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            waitDevice "$@"
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          type waitDevice > /dev/null || (echo "waitDevice is missing!"; fail)
 | 
					 | 
				
			||||||
          alias waitDevice=_waitDevice
 | 
					 | 
				
			||||||
        '';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      environment.persistence."${cfg.persistDir}" = {
 | 
					 | 
				
			||||||
        hideMounts = mkDefault true;
 | 
					 | 
				
			||||||
        directories = [
 | 
					 | 
				
			||||||
          "/var/log"
 | 
					 | 
				
			||||||
          # In theory we'd include only the files needed individually (i.e. the {U,G}ID map files that track deleted
 | 
					 | 
				
			||||||
          # users and groups), but `update-users-groups.pl` actually deletes the original files for "atomic update".
 | 
					 | 
				
			||||||
          # Also the script runs before impermanence does.
 | 
					 | 
				
			||||||
          "/var/lib/nixos"
 | 
					 | 
				
			||||||
          "/var/lib/systemd"
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
        files = [
 | 
					 | 
				
			||||||
          "/etc/machine-id"
 | 
					 | 
				
			||||||
        ];
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      my.dynamic-motd.script = mkIf cfg.unsaved.showMotd
 | 
					      my.dynamic-motd.script = mkIf cfg.unsaved.showMotd
 | 
				
			||||||
        ''
 | 
					        ''
 | 
				
			||||||
          tmprootUnsaved() {
 | 
					          tmprootUnsaved() {
 | 
				
			||||||
@@ -163,8 +113,10 @@ in
 | 
				
			|||||||
            echo
 | 
					            echo
 | 
				
			||||||
            echo -e "\t\e[31;1;4mWarning:\e[0m $count file(s) on / will be lost on shutdown!"
 | 
					            echo -e "\t\e[31;1;4mWarning:\e[0m $count file(s) on / will be lost on shutdown!"
 | 
				
			||||||
            echo -e '\tTo see them, run `tmproot-unsaved` as root.'
 | 
					            echo -e '\tTo see them, run `tmproot-unsaved` as root.'
 | 
				
			||||||
            echo -e '\tAdd these files to `environment.persistence."${cfg.persistDir}"` to keep them!'
 | 
					            ${optionalString enablePersistence ''
 | 
				
			||||||
            echo -e '\tOtherwise, they can be ignored by adding to `my.tmproot.unsaved.ignore`.'
 | 
					              echo -e '\tAdd these files to `environment.persistence."${cfg.persistDir}"` to keep them!'
 | 
				
			||||||
 | 
					            ''}
 | 
				
			||||||
 | 
					            echo -e "\tIf they don't need to be kept, add them to \`my.tmproot.unsaved.ignore\`."
 | 
				
			||||||
            echo
 | 
					            echo
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -172,15 +124,8 @@ in
 | 
				
			|||||||
        '';
 | 
					        '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      fileSystems."/" = rootDef;
 | 
					      fileSystems."/" = rootDef;
 | 
				
			||||||
 | 
					 | 
				
			||||||
      virtualisation = {
 | 
					 | 
				
			||||||
        diskImage = "./.vms/${config.system.name}-persist.qcow2";
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    (mkIf config.services.openssh.enable {
 | 
					
 | 
				
			||||||
      environment.persistence."${cfg.persistDir}".files =
 | 
					 | 
				
			||||||
        concatMap (k: [ k.path "${k.path}.pub" ]) config.services.openssh.hostKeys;
 | 
					 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    (mkIf config.networking.resolvconf.enable {
 | 
					    (mkIf config.networking.resolvconf.enable {
 | 
				
			||||||
      my.tmproot.unsaved.ignore = [ "/etc/resolv.conf" ];
 | 
					      my.tmproot.unsaved.ignore = [ "/etc/resolv.conf" ];
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
@@ -192,12 +137,77 @@ in
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      fileSystems = mkVMOverride {
 | 
					      fileSystems = mkVMOverride {
 | 
				
			||||||
        "/" = mkVMOverride' rootDef;
 | 
					        "/" = mkVMOverride' rootDef;
 | 
				
			||||||
        # Hijack the "root" device for persistence in the VM
 | 
					 | 
				
			||||||
        "${cfg.persistDir}" = {
 | 
					 | 
				
			||||||
          device = config.virtualisation.bootDevice;
 | 
					 | 
				
			||||||
          neededForBoot = true;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (mkIf enablePersistence (mkMerge [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        assertions = [
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            assertion = config.fileSystems ? "${cfg.persistDir}";
 | 
				
			||||||
 | 
					            message = "The 'fileSystems' option does not specify your persistence file system (${cfg.persistDir}).";
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Catch non-existent source directories that are needed for boot (see `pathsNeededForBoot` in
 | 
				
			||||||
 | 
					        # nixos/lib/util.nix). We do this by monkey-patching the `waitDevice` function that would otherwise hang.
 | 
				
			||||||
 | 
					        boot.initrd.postDeviceCommands =
 | 
				
			||||||
 | 
					          ''
 | 
				
			||||||
 | 
					            ensurePersistSource() {
 | 
				
			||||||
 | 
					              [ -e "/mnt-root$1" ] && return
 | 
				
			||||||
 | 
					              echo "Persistent source directory $1 does not exist, creating..."
 | 
				
			||||||
 | 
					              install -dm "$2" "/mnt-root$1" || fail
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _waitDevice() {
 | 
				
			||||||
 | 
					              local device="$1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              ${concatMapStringsSep " || \\\n  " (d:
 | 
				
			||||||
 | 
					                let
 | 
				
			||||||
 | 
					                  sourceDir = "${d.persistentStoragePath}${d.directory}";
 | 
				
			||||||
 | 
					                in
 | 
				
			||||||
 | 
					                  ''([ "$device" = "/mnt-root${sourceDir}" ] && ensurePersistSource "${sourceDir}" "${d.mode}")'')
 | 
				
			||||||
 | 
					                config.environment.persistence."${cfg.persistDir}".directories}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              waitDevice "$@"
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            type waitDevice > /dev/null || (echo "waitDevice is missing!"; fail)
 | 
				
			||||||
 | 
					            alias waitDevice=_waitDevice
 | 
				
			||||||
 | 
					          '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        environment.persistence."${cfg.persistDir}" = {
 | 
				
			||||||
 | 
					          hideMounts = mkDefault true;
 | 
				
			||||||
 | 
					          directories = [
 | 
				
			||||||
 | 
					            "/var/log"
 | 
				
			||||||
 | 
					            # In theory we'd include only the files needed individually (i.e. the {U,G}ID map files that track deleted
 | 
				
			||||||
 | 
					            # users and groups), but `update-users-groups.pl` actually deletes the original files for "atomic update".
 | 
				
			||||||
 | 
					            # Also the script runs before impermanence does.
 | 
				
			||||||
 | 
					            "/var/lib/nixos"
 | 
				
			||||||
 | 
					            "/var/lib/systemd"
 | 
				
			||||||
 | 
					          ];
 | 
				
			||||||
 | 
					          files = [
 | 
				
			||||||
 | 
					            "/etc/machine-id"
 | 
				
			||||||
 | 
					          ];
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        virtualisation = {
 | 
				
			||||||
 | 
					          diskImage = "./.vms/${config.system.name}-persist.qcow2";
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      (mkIf config.services.openssh.enable {
 | 
				
			||||||
 | 
					        environment.persistence."${cfg.persistDir}".files =
 | 
				
			||||||
 | 
					          concatMap (k: [ k.path "${k.path}.pub" ]) config.services.openssh.hostKeys;
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      (mkIf config.my.boot.isDevVM {
 | 
				
			||||||
 | 
					        fileSystems = mkVMOverride {
 | 
				
			||||||
 | 
					          # Hijack the "root" device for persistence in the VM
 | 
				
			||||||
 | 
					          "${cfg.persistDir}" = {
 | 
				
			||||||
 | 
					            device = config.virtualisation.bootDevice;
 | 
				
			||||||
 | 
					            neededForBoot = true;
 | 
				
			||||||
 | 
					          };
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    ]))
 | 
				
			||||||
  ]);
 | 
					  ]);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,14 @@
 | 
				
			|||||||
{ lib, pkgsFlakes, inputs, modules }:
 | 
					{ lib, pkgsFlakes, inputs, modules }:
 | 
				
			||||||
let
 | 
					let
 | 
				
			||||||
  inherit (builtins) attrValues mapAttrs;
 | 
					  inherit (builtins) attrValues mapAttrs;
 | 
				
			||||||
  inherit (lib) mkDefault;
 | 
					  inherit (lib) optionals mkDefault;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  mkSystem =
 | 
					  mkSystem =
 | 
				
			||||||
    name: {
 | 
					    name: {
 | 
				
			||||||
      system,
 | 
					      system,
 | 
				
			||||||
      nixpkgs ? "unstable",
 | 
					      nixpkgs ? "unstable",
 | 
				
			||||||
      config,
 | 
					      config,
 | 
				
			||||||
 | 
					      docCustom ? true,
 | 
				
			||||||
    }:
 | 
					    }:
 | 
				
			||||||
    let
 | 
					    let
 | 
				
			||||||
      pkgsFlake = pkgsFlakes.${nixpkgs};
 | 
					      pkgsFlake = pkgsFlakes.${nixpkgs};
 | 
				
			||||||
@@ -31,8 +32,8 @@ let
 | 
				
			|||||||
          inputs.impermanence.nixosModule
 | 
					          inputs.impermanence.nixosModule
 | 
				
			||||||
          inputs.agenix.nixosModules.age
 | 
					          inputs.agenix.nixosModules.age
 | 
				
			||||||
          inputs.home-manager.nixosModule
 | 
					          inputs.home-manager.nixosModule
 | 
				
			||||||
        ] ++ modules;
 | 
					        ] ++ (optionals docCustom modules);
 | 
				
			||||||
      modules = [
 | 
					      modules = (optionals (!docCustom) modules) ++ [
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
          _module.args = { inherit system inputs; };
 | 
					          _module.args = { inherit system inputs; };
 | 
				
			||||||
          system.name = name;
 | 
					          system.name = name;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user