Add custom impermanence module

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2024-08-08 22:03:15 +03:00
parent 5e57d63a54
commit 22e0150a65
69 changed files with 777 additions and 494 deletions

View File

@@ -1,4 +1,4 @@
{ ... }:
{ config, ... }:
{
hardware.bluetooth = {
enable = true;
@@ -8,5 +8,8 @@
};
};
environment.persistence."/persist".directories = [ "/var/lib/bluetooth" ];
environment.persistence."/persist"."/var/lib/bluetooth" = { };
systemd.services.bluetooth.after = [
config.environment.persistence."/persist"."/var/lib/bluetooth".mount
];
}

View File

@@ -1,4 +1,4 @@
{ pkgs, ... }:
{ config, pkgs, ... }:
{
virtualisation.docker = {
enable = true;
@@ -18,7 +18,12 @@
};
environment = {
persistence."/persist".directories = [ "/var/lib/docker" ];
persistence."/persist"."/var/lib/docker" = { };
systemPackages = with pkgs; [ docker-compose ];
};
systemd = {
services.docker.after = [ config.environment.persistence."/persist"."/var/lib/docker".mount ];
sockets.docker.after = [ config.environment.persistence."/persist"."/var/lib/docker".mount ];
};
}

View File

@@ -1,6 +1,6 @@
{ inputs, pkgs, ... }:
{ config, pkgs, ... }:
{
imports = [ inputs.impermanence.nixosModules.impermanence ];
imports = [ ./options.nix ];
boot.initrd.systemd =
let
@@ -12,35 +12,35 @@
];
in
{
enable = true;
initrdBin = bins;
services.impermanence = {
description = "Rollback BTRFS subvolumes to a pristine state";
serviceConfig.Type = "oneshot";
wantedBy = [ "initrd.target" ];
before = [ "sysroot.mount" ];
after = [ "cryptsetup.target" ];
unitConfig.DefaultDependencies = "no";
serviceConfig.Type = "oneshot";
path = bins;
script = builtins.readFile ./impermanence.sh;
script = builtins.readFile ./scripts/wipe.sh;
};
};
fileSystems = {
"/persist".neededForBoot = true;
"/cache".neededForBoot = true;
};
# https://github.com/NixOS/nixpkgs/blob/master/nixos/doc/manual/administration/systemd-state.section.md
# https://github.com/NixOS/nixpkgs/pull/286140/files
# https://git.eisfunke.com/config/nixos/-/blob/e65e1dc21d06d07b454005762b177ef151f8bfb6/nixos/machine-id.nix
sops.secrets."machineId".mode = "0444";
environment.persistence."/persist" = {
hideMounts = true;
directories = [
"/etc/nixos"
"/var/lib/nixos"
"/var/lib/systemd/coredump"
"/var/log"
];
files = [ "/etc/machine-id" ];
environment = {
etc."machine-id".source = pkgs.runCommandLocal "machine-id-link" { } ''
ln -s ${config.sops.secrets."machineId".path} $out
'';
persistence."/persist" = {
"/etc/nixos" = { };
"/var/lib/nixos" = { };
"/var/lib/systemd" = { };
"/var/log" = { };
};
};
}

View File

@@ -0,0 +1,247 @@
{
config,
lib,
utils,
...
}:
let
cfg = config.environment.persistence;
# ["/home/user/" "/.screenrc"] -> ["home" "user" ".screenrc"]
splitPath =
paths:
(builtins.filter (s: builtins.typeOf s == "string" && s != "") (
builtins.concatMap (builtins.split "/") paths
));
# ["/home/user/" "/.screenrc"] -> "/home/user/.screenrc"
mergePaths =
paths:
let
prefix = lib.strings.optionalString (lib.strings.hasPrefix "/" (builtins.head paths)) "/";
path = lib.strings.concatStringsSep "/" (splitPath paths);
in
prefix + path;
# "/home/user/.screenrc" -> ["/home", "/home/user"]
parentsOf =
path:
let
prefix = lib.strings.optionalString (lib.strings.hasPrefix "/" path) "/";
split = splitPath [ path ];
parents = lib.lists.take ((lib.lists.length split) - 1) split;
in
lib.lists.foldl' (
state: item:
state
++ [
(mergePaths [
(if state != [ ] then lib.lists.last state else prefix)
item
])
]
) [ ] parents;
in
{
options.environment.persistence =
with lib;
with types;
let
isPathLike = strings.hasPrefix "/";
in
mkOption {
type = (
addCheck (attrsOf (
attrsOf (
submodule (
{ name, config, ... }:
{
options = {
enable = mkOption {
type = bool;
default = true;
description = "Whether to enable the item.";
};
service = mkOption {
type = str;
readOnly = true;
description = ''
Systemd service that prepares and syncs the item.
Can be used as a dependency in other units.
'';
};
mount = mkOption {
type = str;
readOnly = true;
description = ''
Systemd mount that binds the item.
Can be used as a dependency in other units.
'';
};
_path = mkOption {
type = str;
internal = true;
default = name;
};
_sourceRoot = mkOption {
type = str;
internal = true;
};
_source = mkOption {
type = str;
internal = true;
};
_targetRoot = mkOption {
type = str;
internal = true;
};
_target = mkOption {
type = str;
internal = true;
};
};
}
)
)
)) (attrs: lists.all isPathLike (builtins.attrNames attrs))
);
apply =
ps:
builtins.mapAttrs (
persistence: items:
builtins.mapAttrs (
_: config:
let
_path = config._path;
_sourceRoot = persistence;
_source = mergePaths [
_sourceRoot
_path
];
_targetRoot =
let
parents = lists.reverseList (parentsOf _path);
in
lists.foldl' (
acc: parent:
if acc == "/" then
lists.findFirst (
otherPersistence: lists.any (other: parent == other) (builtins.attrNames ps.${otherPersistence})
) "/" (builtins.attrNames ps)
else
acc
) "/" parents;
_target = mergePaths [
_targetRoot
_path
];
in
config
// {
inherit
_sourceRoot
_source
_targetRoot
_target
;
service = "${utils.escapeSystemdPath _target}.service";
mount = "${utils.escapeSystemdPath _target}.mount";
}
) items
) ps;
default = { };
description = "Persistence config.";
};
config =
let
all = lib.lists.flatten (builtins.concatMap builtins.attrValues (builtins.attrValues cfg));
in
{
fileSystems = builtins.mapAttrs (_: _: { neededForBoot = true; }) cfg;
systemd = {
mounts = builtins.map (c: {
description = c._path;
requiredBy = [ "local-fs.target" ];
requires = [ c.service ];
bindsTo = [ c.service ];
after = [ c.service ];
unitConfig = {
ConditionPathExists = [ (lib.strings.escape [ " " ] c._source) ];
RefuseManualStart = "yes";
RefuseManualStop = "yes";
};
what = c._source;
where = c._target;
options = lib.strings.concatStringsSep "," ([
"bind"
"X-fstrim.notrim"
"x-gvfs-hide"
]);
}) all;
services = builtins.listToAttrs (
builtins.map (c: {
name = utils.escapeSystemdPath c._target;
value = {
description = c._path;
after = [ "local-fs-pre.target" ];
requiredBy = [
"local-fs.target"
c.mount
];
before = [
"local-fs.target"
c.mount
"umount.target"
];
conflicts = [ "umount.target" ];
unitConfig.DefaultDependencies = "no";
serviceConfig = {
Type = "oneshot";
RemainAfterExit = "yes";
};
script = ''
source=${lib.strings.escapeShellArg c._sourceRoot}
target=${lib.strings.escapeShellArg c._targetRoot}
path=${lib.strings.escapeShellArg c._path}
${builtins.readFile ./scripts/start.sh}
'';
preStop = ''
source=${lib.strings.escapeShellArg c._sourceRoot}
target=${lib.strings.escapeShellArg c._targetRoot}
path=${lib.strings.escapeShellArg c._path}
${builtins.readFile ./scripts/stop.sh}
'';
};
}) all
);
};
assertions =
let
paths = builtins.map (c: c._path) all;
duplicates = lib.lists.filter (t: lib.lists.count (o: o == t) paths > 1) (lib.lists.unique paths);
in
[
{
assertion = lib.lists.length duplicates == 0;
message = "Each target must be defined under a single persistence. Duplicate targets found: ${lib.concatStringsSep ", " duplicates}";
}
];
};
}

View File

@@ -0,0 +1,19 @@
echo "Starting impermanence mount with source: $source, target: $target, path: $path."
source_current="$source"
target_current="$target"
IFS='/' read -ra path_parts <<< "$path"
unset "path_parts[-1]"
for part in "${path_parts[@]}"; do
source_current="$source_current/$part"
target_current="$target_current/$part"
if [ ! -d "$source_current" ]; then
break
fi
read -r mode owner group <<< "$(stat -c '%a %u %g' "$source_current")"
install -d -m "$mode" -o "$owner" -g "$group" "$target_current"
done

View File

@@ -0,0 +1,38 @@
echo "Stopping impermanence mount with source: $source, target: $target, path: $path."
source_current="$source"
target_current="$target"
IFS='/' read -ra path_parts <<< "$path"
unset "path_parts[-1]"
for part in "${path_parts[@]}"; do
source_current="$source_current/$part"
target_current="$target_current/$part"
if [ ! -d "$target_current" ]; then
break
fi
if [ -d "$source_current" ]; then
continue
fi
read -r mode owner group <<< "$(stat -c '%a %u %g' "$target_current")"
install -d -m "$mode" -o "$owner" -g "$group" "$source_current"
done
source=$(realpath -m "$source/$path")
target=$(realpath -m "$target/$path")
if [ ! -e "$target" ] || { [ -d "$target" ] && [ -z "$(ls -A "$target")" ]; } || { [ -f "$target" ] && [ ! -s "$target" ]; }; then
exit 0
fi
if [ -e "$source" ]; then
>&2 echo "Error: Source $source already exists. Cannot move $target to $source."
exit 1
fi
echo "Moving target $target to source $source."
mv "$target" "$source"

View File

@@ -11,7 +11,7 @@ mount /dev/mapper/luks /mnt/btrfs
if [[ -e /mnt/btrfs/@ ]]; then
mkdir -p /mnt/btrfs/@.bak
timestamp=$(date --date="@$(stat -c %Y /mnt/btrfs/@)" "+%Y-%m-%-d_%H:%M:%S")
timestamp=$(date --date="@$(stat -c %Y /mnt/btrfs/@)" "+%Y-%m-%d_%H:%M:%S")
mv /mnt/btrfs/@ "/mnt/btrfs/@.bak/$timestamp"
fi

View File

@@ -1,6 +1,9 @@
{ ... }:
{ config, ... }:
{
networking.networkmanager.enable = true;
environment.persistence."/persist".directories = [ "/etc/NetworkManager/system-connections" ];
environment.persistence."/persist"."/etc/NetworkManager/system-connections" = { };
systemd.services.NetworkManager.after = [
config.environment.persistence."/persist"."/etc/NetworkManager/system-connections".mount
];
}

View File

@@ -1,4 +1,4 @@
{ pkgs, ... }:
{ pkgs, config, ... }:
{
services = {
printing = {
@@ -19,7 +19,19 @@
};
environment.persistence."/persist" = {
directories = [ "/var/lib/cups/ppd" ];
files = [ "/var/lib/cups/printers.conf" ];
"/var/lib/cups/ppd" = { };
"/var/lib/cups/printers.conf" = { };
};
systemd = {
services.cups.after = [
config.environment.persistence."/persist"."/var/lib/cups/ppd".mount
config.environment.persistence."/persist"."/var/lib/cups/printers.conf".mount
];
sockets.cups.after = [
config.environment.persistence."/persist"."/var/lib/cups/ppd".mount
config.environment.persistence."/persist"."/var/lib/cups/printers.conf".mount
];
};
}

View File

@@ -3,7 +3,7 @@
imports = [ inputs.sops-nix.nixosModules.sops ];
environment = {
persistence."/persist".files = [ "/etc/ssh/ssh_host_ed25519_key" ];
persistence."/persist"."/etc/ssh/ssh_host_ed25519_key" = { };
systemPackages = with pkgs; [ sops ];
};

View File

@@ -6,7 +6,7 @@
};
environment = {
persistence."/persist".directories = [ "/var/lib/zsh" ];
persistence."/persist"."/var/lib/zsh" = { };
pathsToLink = [ "/share/zsh" ];
};
}