Migration from impermanence to Preservation

This section lists individual differences between impermanence and Preservation, to better understand them in context of a complete configuration Examples may be helpful.

The following points need to be considered when migrating an existing impermanence configuration to Preservation:

Global enable switch

The module must be explicitly enabled by setting preservation.enable to true.

Handling of existing state

Coming from a setup with impermanence it is important to make sure existing persistent state is preserved correctly, meaning the ownership and mode of preservation remains the same. There is no exhaustive list of files and directories requiring special treatment but at least the following needs to be considered:

SSH host keys

Correct ownership and mode of SSH host keys is very important for sshd to accept connections. Getting this wrong, e.g. not restricted enough, may cause your host to become inaccessible via SSH, forcing you to use other means of logging into the machine.

The following config may be used to preserve the RSA and Ed25519 host keys with preservation:

preservation.preserveAt."/persistent".files = [
  { file = "/etc/ssh/ssh_host_rsa_key"; how = "symlink"; configureParent = true; }
  { file = "/etc/ssh/ssh_host_ed25519_key"; how = "symlink"; configureParent = true; }
];

The above config does not include access modes for the key files because the preservation mode is symlink and the link's target is not touched by preservation without an explicit createLinkTarget = true.

Secrets and other files requiring special access modes

Any files and directories that need to have a mode that differs from the default (0644 for files and 0755 for directories) must be configured explicitly to avoid having the default mode applied.

When to persist

Files and directories that need to be persisted early, must be explicitly configured. For example /etc/machine-id:

This file needs to be persisted very early, by explicitly setting inInitrd to true:

preservation.preserveAt."/persistent".files = [
  { file = "/etc/machine-id"; inInitrd = true; }
];

How to persist

The mode of preservation must be set explicitly for some files and directories. This can be done by setting how to either symlink or bindmount (default). For most cases the default is sufficient but sometimes a symlink may be needed, for example /var/lib/systemd/random-seed.

This file is expected to not exist before it is initialized. A symlink can be used to cause its creation to happen on the persistent volume:

preservation.preserveAt."/persistent".files = [
  {
    file = "/var/lib/systemd/random-seed";
    # create a symlink on the volatile volume
    how = "symlink";
    # prepare the preservation early during startup
    inInitrd = true;
  }
];

Note that no file is created at the symlink's target, unless createLinkTarget is set to true.

Intermediate path components

Any directory that is not preserved itself but is a parent of a preserved file or directory is called an intermediate path component here. Regarding the ownership and permissions of these intermediate path components, the following needs to be considered.

Intermediate path components of user-specific files and directories

Parent directories of a preserved user-specific file or directory are created with the respective user as their owner and permissions 0755. This is the case for all intermediate path components up to, but not including, the user's home directory.

Example

Consider the following preservation config:

preservation.preserveAt."/persistent".users.alice.directories = [
  ".local/state/nvim"
];

This config will cause preservation to configure a bind-mount for the subdirectory nvim, causing its contents to be preserved. The intermediate path components .local and state will be created if necessary, but their contents are not preserved. Owner is set to alice, group to alice's primary group, i.e. users.users.alice.group and the mode is set to 0755.

Intermediate path components of system-wide files and directories

For system-wide files and directories, missing components of a preserved path that do not already exist, are created by systemd-tmpfiles with default ownership root:root and mode 0755.

Should such directories require different ownership or mode, the intended way to create and configure them is via systemd-tmpfiles directly.

Example

Consider a preserved file /foo/bar/baz:

preservation.preserveAt."/persistent".files = [
  { file = "/foo/bar/baz"; user = "baz"; group = "baz"; };
];

This would create the file with desired ownership on both the volatile and persistent volumes. However, the parent directories that did not exist before, i.e. /foo and /foo/bar, are created with ownership root:root and mode 0755.

Preservations allows the configuration of immediate parents, so the permissions for /foo/bar can be configured:

preservation.preserveAt."/persistent".files = [
  {
    file = "/foo/bar/baz"; user = "baz"; group = "baz";
    configureParent = true;
    parent.user = "baz";
    parent.group = "bar";
  };
];

Now the parent directory /foo/bar is configured with ownership baz:bar. But the first path component /foo still has systemd-tmpfiles' default ownership and the configuration becomes quite convoluted.

Solution

To create or configure intermediate path components of a persisted path, systemd-tmpfiles may be used directly:

# configure preservation of single file
preservation.preserveAt."/persistent".files = [
  { file = "/foo/bar/baz"; user = "baz"; group = "bar"; };
];

# create and configure parents of preserved file on the volatile volume with custom permissions
# The Preservation module also uses `settings.preservation` here.
systemd.tmpfiles.settings.preservation = {
  "/foo".d = { user = "foo"; group = "bar"; mode = "0775"; };
  "/foo/bar".d = { user = "bar"; group = "bar"; mode = "0755"; };
};

See tmpfiles.d(5) for available configuration options.