Impermanence
Modules to help you handle persistent state on systems with ephemeral root storage.
The premises of the modules are that you
- have a root filesystem which somehow gets wiped on reboot - e.g. using tmpfs on /
- have a mount point where state is kept between reboots
- want to create links from temporary storage to persistent storage, so that specified files and folders persist between reboots
Contact
Join the matrix room to chat about the project.
Usage
There are currently two modules: one for NixOS
and one for home-manager
.
NixOS
To use the module, import it into your configuration with
{
imports = [ /path/to/impermanence/nixos.nix ];
}
This adds the environment.persistence
option, which is an
attribute set of submodules, where the attribute name is the path
to persistent storage.
Usage is shown best with an example:
{
environment.persistence."/persistent" = {
hideMounts = true;
directories = [
"/var/log"
"/var/lib/bluetooth"
"/var/lib/nixos"
"/var/lib/systemd/coredump"
"/etc/NetworkManager/system-connections"
{ directory = "/var/lib/colord"; user = "colord"; group = "colord"; mode = "u=rwx,g=rx,o="; }
];
files = [
"/etc/machine-id"
{ file = "/etc/nix/id_rsa"; parentDirectory = { mode = "u=rwx,g=,o="; }; }
];
users.talyz = {
directories = [
"Downloads"
"Music"
"Pictures"
"Documents"
"Videos"
"VirtualBox VMs"
{ directory = ".gnupg"; mode = "0700"; }
{ directory = ".ssh"; mode = "0700"; }
{ directory = ".nixops"; mode = "0700"; }
{ directory = ".local/share/keyrings"; mode = "0700"; }
".local/share/direnv"
];
files = [
".screenrc"
];
};
};
}
- ~”/persistent”~ is the path to your persistent storage location
This allows for multiple different persistent storage locations. If you, for example, have one location you back up and one you don’t, you can use both by defining two separate attributes under
environment.persistence
. directories
are all directories you want to bind mount to persistent storage. A directory can be represented either as a string, simply denoting its path, or as a submodule. The submodule representation is useful when the default assumptions, mainly regarding permissions, are incorrect. The available options are:directory
, the path to the directory you want to bind mount to persistent storage. Only setting this option is equivalent to the string representation.persistentStoragePath
, the path to persistent storage. Defaults to theenvironment.persistence
submodule name, i.e. ~”/persistent”~ in the example. This should most likely be left to its default value - don’t change it unless you’re certain you really need to.user
, the user who should own the directory. If the directory doesn’t already exist in persistent storage, it will be created and this user will be its owner. This also applies to any parent directories which don’t yet exist. Changing this once the directory has been created has no effect.group
, the group who should own the directory. If the directory doesn’t already exist in persistent storage, it will be created and this group will be its owner. This also applies to any parent directories which don’t yet exist. Changing this once the directory has been created has no effect.mode
, the permissions to set for the directory. If the directory doesn’t already exist in persistent storage, it will be created with this mode. Can be either an octal mode (e.g.0700
) or a symbolic mode (e.g.u=rwx,g=,o=
). Parent directories that don’t yet exist are created with default permissions. Changing this once the directory has been created has no effect.
files
are all files you want to link or bind to persistent storage. A file can be represented either as a string, simply denoting its path, or as a submodule. The submodule representation is useful when the default assumptions, mainly regarding the permissions of its parent directory, are incorrect. The available options are:file
, the path to the file you want to bind mount to persistent storage. Only setting this option is equivalent to the string representation.persistentStoragePath
, the path to persistent storage. Defaults to theenvironment.persistence
submodule name, i.e. ~”/persistent”~ in the example. This should most likely be left to its default value - don’t change it unless you’re certain you really need to.parentDirectory
, the permissions that should be applied to the file’s parent directory, if it doesn’t already exist. Available options areuser
,group
andmode
. See their definition indirectories
above.
If the file exists in persistent storage, it will be bind mounted to the target path; otherwise it will be symlinked.
hideMounts
allows you to specify whether to hide the bind mounts from showing up as mounted drives in the file manager. If enabled, it sets the mount optionx-gvfs-hide
on all the bind mounts.users.talyz
handles files and directories intalyz
’s home directoryThe
users
option defines a set of submodules which correspond to the users’ names. Thedirectories
andfiles
options of each submodule work like their root counterparts, but the paths are automatically prefixed with with the user’s home directory.If the user has a non-standard home directory (i.e. not
/home/<username>
), theusers.<username>.home
option has to be set to this path - it can’t currently be automatically deduced due to a limitation innixpkgs
.
Important note: Make sure your persistent volumes are marked with
neededForBoot
, otherwise you will run into problems.
home-manager
Usage of the home-manager
module is very similar to the one of the
NixOS
module - the key differences are that the persistence
option
is now under home
, rather than environment
, and the addition of
the submodule option removePrefixDirectory
.
Important note: You have to use the home-manager
NixOS
module (in
the nixos
directory of home-manager
’s repo) in order for this
module to work as intended.
To use the module, import it into your configuration with
{
imports = [ /path/to/impermanence/home-manager.nix ];
}
This adds the home.persistence
option, which is an attribute set
of submodules, where the attribute name is the path to persistent
storage.
Usage is shown best with an example:
{
home.persistence."/persistent/home/talyz" = {
directories = [
"Downloads"
"Music"
"Pictures"
"Documents"
"Videos"
"VirtualBox VMs"
".gnupg"
".ssh"
".nixops"
".local/share/keyrings"
".local/share/direnv"
{
directory = ".local/share/Steam";
method = "symlink";
}
];
files = [
".screenrc"
];
allowOther = true;
};
}
- ~”/persistent/home/talyz”~ is the path to your persistent storage location
directories
are all directories you want to link to persistent storage- It is possible to switch the linking
method
between bindfs (the default) and symbolic links.
- It is possible to switch the linking
files
are all files you want to link to persistent storage. These are symbolic links to their target location.allowOther
allows other users, such asroot
, to access files through the bind mounted directories listed indirectories
. Useful forsudo
operations, Docker, etc. Requires the NixOS configurationprograms.fuse.userAllowOther = true
.
Additionally, the home-manager
module allows for compatibility
with dotfiles
repos structured for use with GNU Stow, where the
files linked to are one level deeper than where they should end
up. This can be achieved by setting removePrefixDirectory
to true
:
{
home.persistence."/etc/nixos/home-talyz-nixpkgs/dotfiles" = {
removePrefixDirectory = true;
files = [
"screen/.screenrc"
];
directories = [
"fish/.config/fish"
];
};
}
In the example, the .screenrc
file and .config/fish
directory
should be linked to from the home directory; removePrefixDirectory
removes the first part of the path when deciding where to put the
links.
Note: When using bindfs
fuse filesystem for directories, the names of
the directories you add will be visible in the /etc/mtab
file and in the
output of mount
to all users.
Further reading
The following blog posts provide more information on the concept of ephemeral roots:
- https://elis.nu/blog/2020/05/nixos-tmpfs-as-root/ — @etu’s blog post walks the reader through a NixOS-on-tmpfs installation.
- https://grahamc.com/blog/erase-your-darlings — @grahamc’s blog post details why one would want to erase their state at every boot, as well as how to achieve this using ZFS snapshots.