modules/tmproot: Make persistence optional

This commit is contained in:
Jack O'Sullivan 2022-02-13 18:36:57 +00:00
parent 7dec8bb56b
commit e306da7ce6
3 changed files with 108 additions and 85 deletions

View File

@ -1,7 +1,7 @@
{ lib, extendModules, modulesPath, baseModules, options, config, ... }:
let
inherit (lib) recursiveUpdate mkOption;
inherit (lib.my) mkBoolOpt';
inherit (lib) recursiveUpdate mkOption mkDefault;
inherit (lib.my) mkBoolOpt' dummyOption;
cfg = config.my.build;
@ -17,19 +17,31 @@ let
};
in
{
options.my = with lib.types; {
boot.isDevVM = mkBoolOpt' false "Whether the system is a development VM.";
build = options.system.build;
asDevVM = mkOption {
inherit (asDevVM) type;
default = { };
visible = "shallow";
description = "Configuration as a development VM";
options = with lib.types; {
my = {
boot.isDevVM = mkBoolOpt' false "Whether the system is a development VM.";
build = options.system.build;
asDevVM = mkOption {
inherit (asDevVM) type;
default = { };
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 = {
# 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"; };
config = {
virtualisation = {
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"; };
};
};
}

View File

@ -1,9 +1,10 @@
{ lib, pkgs, config, ... }:
let
inherit (lib) concatStringsSep concatMap concatMapStringsSep mkIf mkDefault mkMerge mkVMOverride;
inherit (lib.my) mkOpt' mkBoolOpt' mkVMOverride' dummyOption;
inherit (lib) optionalString concatStringsSep concatMap concatMapStringsSep mkIf mkDefault mkMerge mkVMOverride;
inherit (lib.my) mkOpt' mkBoolOpt' mkVMOverride';
cfg = config.my.tmproot;
enablePersistence = cfg.persistDir != null;
showUnsaved =
''
@ -56,27 +57,18 @@ in
options = with lib.types; {
my.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";
unsaved = {
showMotd = mkBoolOpt' true "Whether to show unsaved files with `dynamic-motd`.";
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 [
{
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
assertion = !config.users.mutableUsers;
@ -112,48 +104,6 @@ in
(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
''
tmprootUnsaved() {
@ -163,8 +113,10 @@ in
echo
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 '\tAdd these files to `environment.persistence."${cfg.persistDir}"` to keep them!'
echo -e '\tOtherwise, they can be ignored by adding to `my.tmproot.unsaved.ignore`.'
${optionalString enablePersistence ''
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
}
@ -172,15 +124,8 @@ in
'';
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 {
my.tmproot.unsaved.ignore = [ "/etc/resolv.conf" ];
})
@ -192,12 +137,77 @@ in
fileSystems = mkVMOverride {
"/" = 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;
};
};
})
]))
]);
}

View File

@ -1,13 +1,14 @@
{ lib, pkgsFlakes, inputs, modules }:
let
inherit (builtins) attrValues mapAttrs;
inherit (lib) mkDefault;
inherit (lib) optionals mkDefault;
mkSystem =
name: {
system,
nixpkgs ? "unstable",
config,
docCustom ? true,
}:
let
pkgsFlake = pkgsFlakes.${nixpkgs};
@ -31,8 +32,8 @@ let
inputs.impermanence.nixosModule
inputs.agenix.nixosModules.age
inputs.home-manager.nixosModule
] ++ modules;
modules = [
] ++ (optionals docCustom modules);
modules = (optionals (!docCustom) modules) ++ [
{
_module.args = { inherit system inputs; };
system.name = name;