mirror of
https://github.com/ItsDrike/nixdots
synced 2024-12-26 18:04:34 +00:00
Add impermanence config
This commit is contained in:
parent
935b4094a2
commit
6051c29dd2
|
@ -3,10 +3,14 @@
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
|
||||||
home-manager = {
|
home-manager = {
|
||||||
url = "github:nix-community/home-manager";
|
url = "github:nix-community/home-manager";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# doesn't offer much above properly used symlinks but it is convenient
|
||||||
|
impermanence.url = "github:nix-community/impermanence";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = {self, nixpkgs, ...} @ inputs: let
|
outputs = {self, nixpkgs, ...} @ inputs: let
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./hardware-configuration.nix
|
./hardware-configuration.nix
|
||||||
./impermanence.nix
|
|
||||||
];
|
];
|
||||||
|
|
||||||
boot.supportedFilesystems = [ "btrfs" ];
|
boot.supportedFilesystems = [ "btrfs" ];
|
||||||
|
@ -23,11 +22,26 @@
|
||||||
system = {
|
system = {
|
||||||
hostname = "herugrim";
|
hostname = "herugrim";
|
||||||
username = "itsdrike";
|
username = "itsdrike";
|
||||||
|
|
||||||
|
impermanence.root = {
|
||||||
|
enable = true;
|
||||||
|
# Some people use /nix/persist/system for this, leaving persistent files in /nix subvolume
|
||||||
|
# I much prefer using a standalone subvolume for this though.
|
||||||
|
persistentMountPoint = "/persist";
|
||||||
|
# Configure automatic root subvolume wiping on boot from initrd
|
||||||
|
autoBtrfsWipe = {
|
||||||
|
devicePath = "/dev/disk/by-label/NIXROOT";
|
||||||
|
subvolumePath = "root";
|
||||||
|
cleanSnapshotPath = "root-blank";
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
device = {
|
device = {
|
||||||
virtual-machine = false;
|
virtual-machine = false;
|
||||||
cpu.type = "intel";
|
cpu.type = "intel";
|
||||||
};
|
};
|
||||||
|
|
||||||
home-manager = {
|
home-manager = {
|
||||||
enable = true;
|
enable = true;
|
||||||
stateVersion = "23.11";
|
stateVersion = "23.11";
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
{ config, pkgs, ... }:
|
|
||||||
let
|
|
||||||
impermanence = builtins.fetchTarball "https://github.com/nix-community/impermanence/archive/master.tar.gz";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [ "${impermanence}/nixos.nix" ];
|
|
||||||
|
|
||||||
users = {
|
|
||||||
# This option makes it that users are not mutable outside our configuration
|
|
||||||
# If you are using impermanence, this will actually be the case regardless of this setting,
|
|
||||||
# however, setting this explicitly is a good idea, because nix will warn us if
|
|
||||||
# our users don't have passwords set
|
|
||||||
mutableUsers = false;
|
|
||||||
|
|
||||||
# Each existing user needs to have a password file defined here
|
|
||||||
# otherwise, they will not be available to login.
|
|
||||||
# These password files can be generated using the following command:
|
|
||||||
# mkpasswd -m sha-512 > /persist/system/passwords/myuser
|
|
||||||
users = {
|
|
||||||
root = {
|
|
||||||
# password file needs to be in a volume marked `neededForRoot = true`
|
|
||||||
hashedPasswordFile = "/persist/passwords/root";
|
|
||||||
};
|
|
||||||
itsdrike = {
|
|
||||||
hashedPasswordFile = "/persist/passwords/itsdrike";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Some people use /nix/persist/system instead, leaving the persistent files in /nix subvolume
|
|
||||||
# I much prefer using a standalone subvolume for this though.
|
|
||||||
environment.persistence."/persist/system" = {
|
|
||||||
hideMounts = true;
|
|
||||||
directories = [
|
|
||||||
"/etc/nixos" # nixos configuration source
|
|
||||||
"/etc/NetworkManager/system-connections" # saved network connections
|
|
||||||
"/var/db/sudo" # keeps track of who got the sudo lecture already
|
|
||||||
"/var/lib/systemd/coredump" # recorded coredumps
|
|
||||||
];
|
|
||||||
files = [
|
|
||||||
"/etc/machine-id"
|
|
||||||
|
|
||||||
# ssh stuff
|
|
||||||
"/etc/ssh/ssh_host_ed25519_key"
|
|
||||||
"/etc/ssh/ssh_host_ed25519_key.pub"
|
|
||||||
"/etc/ssh/ssh_host_rsa_key"
|
|
||||||
"/etc/ssh/ssh_host_rsa_key.pub"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# For some reason, NetworkManager needs this instead of the impermanence mode to not get screwed up
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"L /var/lib/NetworkManager/secret_key - - - - /persist/system/var/lib/NetworkManager/secret_key"
|
|
||||||
"L /var/lib/NetworkManager/seen-bssids - - - - /persist/system/var/lib/NetworkManager/seen-bssids"
|
|
||||||
"L /var/lib/NetworkManager/timestamps - - - - /persist/system/var/lib/NetworkManager/timestamps"
|
|
||||||
];
|
|
||||||
|
|
||||||
boot.initrd.systemd = {
|
|
||||||
enable = true; # This enables systemd support in stage 1 - required for below setup
|
|
||||||
services.rollback = {
|
|
||||||
description = "Rollback BTRFS root subvolume to a pristine state";
|
|
||||||
wantedBy = [ "initrd.target" ];
|
|
||||||
# make sure it's done after decryption (i.e. LUKS/TPM process)
|
|
||||||
after = [ "systemd-cryptsetup@cryptfs.service" ];
|
|
||||||
# mount the root fs before clearing
|
|
||||||
before = [ "sysroot.mount" ];
|
|
||||||
unitConfig.DefaultDependencies = "no";
|
|
||||||
serviceConfig.Type = "oneshot";
|
|
||||||
script = ''
|
|
||||||
mkdir -p /mnt
|
|
||||||
|
|
||||||
# We first mount the btrfs root to /mnt
|
|
||||||
# so we can manipulate btrfs subvolumes.
|
|
||||||
mount /dev/mapper/cryptfs /mnt
|
|
||||||
|
|
||||||
# While we're tempted to just delete /root and create
|
|
||||||
# a new snapshot from /root-blank, /root is already
|
|
||||||
# populated at this point with a number of subvolumes,
|
|
||||||
# which makes `btrfs subvolume delete` fail.
|
|
||||||
# So, we remove them first.
|
|
||||||
#
|
|
||||||
# /root contains subvolumes:
|
|
||||||
# - /root/var/lib/portables
|
|
||||||
# - /root/var/lib/machines
|
|
||||||
#
|
|
||||||
# These are probably related to systemd-nspawn, but
|
|
||||||
# since I don't use it, I'm not 100% sure.
|
|
||||||
# Anyhow, deleting these subvolumes hasn't resulted in
|
|
||||||
# any issues so far, except for fairly benign-looking
|
|
||||||
# errors from systemd-tmpfiles.
|
|
||||||
btrfs subvolume list -o /mnt/root |
|
|
||||||
cut -f9 -d' ' |
|
|
||||||
while read subvolume; do
|
|
||||||
echo "deleting /$subvolume subvolume..."
|
|
||||||
btrfs subvolume delete "/mnt/$subvolume"
|
|
||||||
done &&
|
|
||||||
echo "deleting /root subvolume..." &&
|
|
||||||
btrfs subvolume delete /mnt/root
|
|
||||||
|
|
||||||
echo "restoring blank /root subvolume..."
|
|
||||||
btrfs subvolume snapshot /mnt/root-blank /mnt/root
|
|
||||||
|
|
||||||
# Once we're done rolling back to a blank snapshot,
|
|
||||||
# we can unmount /mnt and continue on the boot process.
|
|
||||||
umount /mnt
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
inherit (lib) mkOption;
|
inherit (lib) mkOption;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [ ];
|
imports = [
|
||||||
|
./impermanence.nix
|
||||||
|
];
|
||||||
|
|
||||||
options.myOptions.system = {
|
options.myOptions.system = {
|
||||||
hostname = mkOption {
|
hostname = mkOption {
|
||||||
|
|
81
options/system/impermanence.nix
Normal file
81
options/system/impermanence.nix
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
{ lib, config, ... }: with lib; let
|
||||||
|
inherit (lib) mkEnableOption mkOption literalExpression;
|
||||||
|
|
||||||
|
cfg = config.myOptions.system.impermanence;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.myOptions.system.impermanence = {
|
||||||
|
root = {
|
||||||
|
enable = mkEnableOption ''
|
||||||
|
the Impermanence module for persisting important state directories.
|
||||||
|
'';
|
||||||
|
|
||||||
|
extraFiles = mkOption {
|
||||||
|
default = [];
|
||||||
|
example = literalExpression ''["/etc/nix/id_rsa"]'';
|
||||||
|
description = ''
|
||||||
|
Additional files in root to link to persistent storage.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraDirectories = mkOption {
|
||||||
|
default = [];
|
||||||
|
example = literalExpression ''["/etc/nix/id_rsa"]'';
|
||||||
|
description = ''
|
||||||
|
Additional directories in root to link to persistent storage.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
persistentMountPoint = mkOption {
|
||||||
|
default = "/persist";
|
||||||
|
description = ''
|
||||||
|
Path to a persistent directory (usually a mount point to a
|
||||||
|
standalone partition / subvolume), which will hold the persistent
|
||||||
|
system state files.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
autoBtrfsWipe = {
|
||||||
|
enable = mkOption {
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Enable automatic wiping of the root BTRFS subvolume from initrd.
|
||||||
|
|
||||||
|
Generally, you will want to keep this enabled, as otherwise setting up
|
||||||
|
impermanence is pointless. However in case you're using a non-BTRFS
|
||||||
|
system, or you wish to set up a custom handling for this auto-wiping,
|
||||||
|
which the current handling doesn't support, disable this.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
devicePath = mkOption {
|
||||||
|
default = "/dev/mapper/cryptfs";
|
||||||
|
description = ''
|
||||||
|
Path to the BTRFS block device containing the subvolume to be wiped.
|
||||||
|
|
||||||
|
This device will be mounted from initrd.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
subvolumePath = mkOption {
|
||||||
|
default = "root";
|
||||||
|
description = ''
|
||||||
|
Path to the BTRFS subvolume to be wiped.
|
||||||
|
|
||||||
|
This is a relative path, starting from the BTRFS root.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
cleanSnapshotPath = mkOption {
|
||||||
|
default = "root-blank";
|
||||||
|
description = ''
|
||||||
|
Path to the BTRFS snapshot (subvolume) to be restore
|
||||||
|
`myOptions.system.impermanence.root.autoWipe.btrfsSubvolume` to.
|
||||||
|
|
||||||
|
This should be a blank snapshot to achieve a complete wipe.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ _: {
|
||||||
./boot
|
./boot
|
||||||
./services
|
./services
|
||||||
./nix
|
./nix
|
||||||
|
./impermanence
|
||||||
./programs.nix
|
./programs.nix
|
||||||
./system.nix
|
./system.nix
|
||||||
./network.nix
|
./network.nix
|
||||||
|
|
7
system/impermanence/default.nix
Normal file
7
system/impermanence/default.nix
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{ inputs, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
inputs.impermanence.nixosModules.impermanence
|
||||||
|
./root.nix
|
||||||
|
];
|
||||||
|
}
|
110
system/impermanence/root.nix
Normal file
110
system/impermanence/root.nix
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
{ config }: let
|
||||||
|
cfgSystem = config.myOptions.system;
|
||||||
|
cfg = config.myOptions.system.impermanence.root;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
users = {
|
||||||
|
# This option makes it that users are not mutable outside of our configuration.
|
||||||
|
# If you're using root impermanence, this will actually be the case regardless
|
||||||
|
# of this setting, however, setting this explicitly is a good idea, because nix
|
||||||
|
# will warn us if our users don't have passwords set, preventing lock outs.
|
||||||
|
mutableUsers = false;
|
||||||
|
|
||||||
|
# Each existing user needs to have a password file defined here, otherwise
|
||||||
|
# they will not be available to login. These password files can be generated with:
|
||||||
|
# mkpasswd -m sha-512 > /persist/passwords/myuser
|
||||||
|
users = {
|
||||||
|
root = {
|
||||||
|
hashedPasswordFile = "${cfg.persistentMountPoint}/passwords/root";
|
||||||
|
};
|
||||||
|
${cfgSystem.username} = {
|
||||||
|
hashedPasswordFile = "${cfg.persistentMountPoint}/passwords/${cfgSystem.username}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
environment.persistence."${cfg.persistentMountPoint}/system" = {
|
||||||
|
hideMounts = true;
|
||||||
|
directories = [
|
||||||
|
"/etc/nixos" # NixOS configuration source
|
||||||
|
"/etc/NetworkManager/system-connections" # saved network connections
|
||||||
|
"/var/db/sudo" # keeps track of who got the sudo lecture already
|
||||||
|
"/var/lib/systemd/coredump" # captured coredumps
|
||||||
|
] ++ cfg.extraDirectories;
|
||||||
|
|
||||||
|
files = [
|
||||||
|
"/etc/machine-id"
|
||||||
|
] ++ cfg.extraFiles;
|
||||||
|
};
|
||||||
|
|
||||||
|
# For some reason, NetworkManager needs this instead of the impermanence mode
|
||||||
|
# to not get screwed up
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"L /var/lib/NetworkManager/secret_key - - - - ${cfg.persistentMountPoint}/system/var/lib/NetworkManager/secret_key"
|
||||||
|
"L /var/lib/NetworkManager/seen-bssids - - - - ${cfg.persistentMountPoint}/system/var/lib/NetworkManager/seen-bssids"
|
||||||
|
"L /var/lib/NetworkManager/timestamps - - - - ${cfg.persistentMountPoint}/system/var/lib/NetworkManager/timestamps"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Define host key paths in the persistent mount point instead of using impermanence for these.
|
||||||
|
# This works better, because these keys also get auto-created if they don't already exist.
|
||||||
|
services.openssh.hostKeys = mkForce [
|
||||||
|
{
|
||||||
|
bits = 4096;
|
||||||
|
path = "${cfg.persistentMountPoint}/system/etc/ssh/ssh_host_rsa_key";
|
||||||
|
type = "rsa";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
bits = 4096;
|
||||||
|
path = "${cfg.persistentMountPoint}/system/etc/ssh/ssh_host_ed25519_key";
|
||||||
|
type = "ed25519";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
boot.initrd.systemd = let
|
||||||
|
cfgWipe = cfg.autoBtrfsWipe;
|
||||||
|
in {
|
||||||
|
enable = true; # This enables systemd support in stage 1 - required for below setup
|
||||||
|
services.rollback-root = {
|
||||||
|
description = "Rollback BTRFS root subvolume to a pristine state";
|
||||||
|
enable = cfgWipe.enable;
|
||||||
|
wantedby = [ "initrd.target" ];
|
||||||
|
# Make sure it's done after decryption (i.e. LUKS/TPM process)
|
||||||
|
after = [ "systemd-cryptsetup@cryptfs.service" ];
|
||||||
|
# mount the root fs before clearing
|
||||||
|
before = [ "sysroot.mount" ];
|
||||||
|
unitConfig.DefaultDependencies = "no";
|
||||||
|
serviceConfig.Type = "oneshot";
|
||||||
|
script = ''
|
||||||
|
# Mount the BTRFS root to /mnt, so we can manipulate the subvolumes
|
||||||
|
mount --mkdir ${cfgWipe.devicePath} /mnt
|
||||||
|
|
||||||
|
# To restore the root subvolume, we will first delete it, and then create
|
||||||
|
# a new snapshot from the blank snapshot, which will become our new root subvolume
|
||||||
|
|
||||||
|
# However, at this point, root subvol is already populated and contains a number
|
||||||
|
# of subvolumes, which would make `btrfs subvolume delete` fail.
|
||||||
|
#
|
||||||
|
# These existing subvolumes get created automatically, and we can safely remove
|
||||||
|
# them. They are: /srv, /var/lib/portables, /var/lib/machines, /var/tmp
|
||||||
|
sudo btrfs subvolume list -o "/mnt/${cfgWipe.subvolumePath}" | cut -f9 -d' ' |
|
||||||
|
while read subvolme; do
|
||||||
|
echo "deleting $subvolume subvolume..." &&
|
||||||
|
btrfs subvolume delete "/mnt/$subvolume"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Now we can remove the root subvolume, and restore it from a snapshot
|
||||||
|
echo "deleting ${cfgWipe.subvolumePath} (root) subvolume..."
|
||||||
|
btrfs subvolume delete "/mnt/${cfg.subvolumePath}"
|
||||||
|
echo "restoring ${cfgWipe.subvolumePath} (root) subvolume..."
|
||||||
|
btrfs subvolume snapshot "/mnt/${cfgWipe.cleanSnapshotPath}"
|
||||||
|
"/mnt/${cfgWipe.subvolumePath}"
|
||||||
|
|
||||||
|
# Once we're done rolling back to a blank snapshot,
|
||||||
|
# we can unmount /mnt and continue on the boot process
|
||||||
|
umount /mnt
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue