nixdots/docs/02_IMPERMANENCE.md

248 lines
10 KiB
Markdown
Raw Normal View History

2024-04-08 02:30:24 +00:00
# Impermanence
The word impermanence means temporary or short-lived. When you see this term in NixOS, it refers to the practice
of automatically resetting/wiping your system after each reboot.
This will mean that your root directory will be wiped after every reboot. Such a setup is possible because
NixOS only needs `/boot` and `/nix` in order to boot, all other system files are simply links to files in `/nix`.
This guide assumes you're following from the [INSTALLATION](./01_INSTALLATION.md) guide, which means you have a working
setup with BTRFS file-system. Note that you will need the `/persist` and `/var/log` (and `/root`) subvolumes if you want
to proceed with this guide.
Note that setting up impermanence is completely optional, and if you do not wish to do so, you can simply skip this
guide and move on to the next one. If you're unsure whether impermanence is worth setting up, check out the
[Why section](#why).
## Auto-wipe root partition
To reset the root subvolume on every boot, we can simply delete it and create a new one in its place. We will be doing
this from initrd, which runs in a temporary file-system, before the actual file-system is properly mounted (following
fstab). This makes it a perfect place to run a script, which will wipe the root subvolume before each boot.
I will set this up using a systemd-based initrd, because I will need systemd for TPM unlocking later on. If you don't
care about that, it is also possible to do this without systemd. You can a guide for such setup
[here](https://mt-caret.github.io/blog/posts/2020-06-29-optin-state.html#darling-erasure). That said, I find this to be
a cleaner setup than the non-systemd one anyway, so it might be worth it for you to follow this regardless. However, do
note that using systemd in initrd may result in slightly slower boot times.
To achieve this, let's add the following to our `configuration.nix`:
```nix
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 = ''
# Mount the BTRFS root to /mnt so we can manipulate btrfs subvolumes
mount --mkdir /dev/mapper/cryptfs /mnt
# Simply deleting a subvolume with btrfs subvolume delete will not work,
# if that subvolume contains other btrfs subvolumes. Because of that, we
# instead use this function to delete subvolumes, whihc will first perform
# a recursive deletion of any nested subvolumes.
#
# This is necessary, because the root subvolume will actually usually contain
# other subvolumes, even if the user haven't created those explicitly. It seems
# that NixOS creates these automatically. Namely, I observed these in root subvol:
# - root/srv
# - root/var/lib/portables
# - root/var/lib/machines
# - root/var/tmp
delete_subvolume_recursively() {
IFS=$'\n'
for x in $(btrfs subvolume list -o "$1" | cut -f 9- -d ' '); do
delete_subvolume_recursively "/mnt/$x"
done
echo "Deleting subvolume $1"
btrfs subvolume delete "$1"
}
# Recreate the root subvolume
delete_subvolume_recursively "/mnt/root"
echo "Re-creating root subvolume"
btrfs subvolume create "/mnt/root"
# we can now unmount /mnt and continue on the boot process.
umount /mnt
'';
};
};
```
## Impermanence
What this implies is that certain files, such as saved networks for network-manager will be deleted on each reboot.
While a little clunky, [Impermanence](https://github.com/nix-community/impermanence) is a great solution to our problem.
Impermanence adds a `environment.persistence."<dirName>"` option, that we can use to make certain directories or files
permanent. A sample configuration module for this can look like so:
```nix
{ config, pkgs, ... }:
let
impermanence = builtins.fetchTarball "https://github.com/nix-community/impermanence/archive/master.tar.gz";
in
{
imports = [ "${impermanence}/nixos.nix" ];
# 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"
];
};
# 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"
];
# 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 = "/persist/system/etc/ssh/ssh_host_rsa_key";
type = "rsa";
}
{
bits = 4096;
path = "/persist/system/etc/ssh/ssh_host_ed25519_key";
type = "ed25519";
}
];
}
```
You can put this module in `/etc/nixos/impermanence.nix`, and add it to your `imports` in `configuration.nix`.
Additionally, you may also want to move the `boot.initrd.systemd` configuration to this file.
Alternatively, you can of course also extend your `configuration.nix` adding this in directly, and keeping
everything in the same place.
### User configuration
Note that with impermanence, your user passwords will get erased too (with the `/etc/shadow` file). To avoid this,
you can create password files, which will contain the password hashes for each user:
```shell
mkdir -p /persist/passwords
chmod 700 /persist/passwords
mkpasswd -m sha-512 > /persist/passwords/root
mkpasswd -m sha-512 > /persist/passwords/itsdrike
chmod 600 /persist/passwords/*
```
And declare these in our `configuration.nix` or `impermanence.nix`
```nix
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 = {
# password file needs to be in a volume marked `neededForBoot = true`
hashedPasswordFile = "/persist/passwords/root";
};
itsdrike = {
hashedPasswordFile = "/persist/passwords/itsdrike";
};
};
};
```
### Copy the configuration
While NixOS will take care of creating the specified symlinks, you will want to move the relevant files and directories
to where the symlinks are pointing at before rebooting.
```shell
mkdir -p /persist/system/etc
cp -r {,/persist/system}/etc/nixos
cp {,/persist/system}/etc/machine-id
mkdir -p /persist/system/var/db
cp -r {,/persist/system}/var/db/sudo
mkdir -p /persist/system/var/lib/systemd
cp -r {,/persist/system}/var/lib/systemd/coredump
mkdir -p /persist/system/etc/NetworkManager
cp -r {,/persist/system}/etc/NetworkManager/system-connections
sudo mkdir -p /persist/system/var/lib/NetworkManager
sudo cp {,/persist/system}/var/lib/NetworkManager/secret_key
sudo cp {,/persist/system}/var/lib/NetworkManager/timestamps
sudo cp {,/persist/system}/var/lib/NetworkManager/seen-bssids # if this fails, read the note below and repeat
... # Copy any other files/dirs you have configured
```
> [!NOTE]
> In case `/var/lib/NetworkManager/seen-bssids` doesn't (yet) exist, you can just create a file
> like this in it's place:
> `echo "[seen-bssids]" > /persist/system/var/lib/NetworkManager/seen-bssids`
## Rebuild
Once you have declared all the files that you wish to persist, you can now rebuild your configuration for the next boot,
and reboot.
> [!TIP]
> If you want to test out whether it worked, you can create a file somewhere on the root subvolume
> and make sure that it will no longer be there after the reboot: `touch /test_flag`
```shell
nixos-rebuild boot
reboot
```
You should now be back in your system, with the root subvolume wiped and auto-reconstructed by NixOS.
2024-04-12 16:25:26 +00:00
You can now move on to the next file: [SECURE_BOOT](./03_SECURE_BOOT.md).
2024-04-08 02:30:24 +00:00
## Why?
Honestly, why not?
Automatic root partition wiping will force you into declaring all of your files which you actually care about
persisting, which allows you to create incredibly small backups of only those files which actually matter. No more
creating backups of the entire file-system for absolutely no reason.
Additionally, doing this is just a great practice in general, as it will mean recreating your entire system from a clean
slate, from an immutable `/nix/store`, which means even in the unlikely case, that your system got affected by some kind
of malware, it will simply be gone after the next reboot. (Unless it affected the images in `/boot`, at which point all
bets are off.)
## Sources / Attribution
- <https://nixos.wiki/wiki/Impermanence>
- <https://mt-caret.github.io/blog/posts/2020-06-29-optin-state.html>
- <https://git.notashelf.dev/NotAShelf/nyx/src/branch/main/docs/notes/2023-03-14-impermanence.md>