Skip to content

Commit

Permalink
fix impermanence in combination with sysusers/userborn
Browse files Browse the repository at this point in the history
Both remove user creation from the action phase to a systemd service.
This is hopefully also easier to debug, since we now can look at a
systemd service instead for log output.
  • Loading branch information
Mic92 committed Oct 4, 2024
1 parent d0b38e5 commit 2bd09dc
Showing 1 changed file with 187 additions and 161 deletions.
348 changes: 187 additions & 161 deletions nixos.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{ pkgs, config, lib, utils, ... }:
{ pkgs, config, lib, utils, options, ... }:

let
inherit (lib)
Expand Down Expand Up @@ -50,6 +50,7 @@ let

cfg = config.environment.persistence;
users = config.users.users;

allPersistentStoragePaths = zipAttrsWith (_name: flatten) (filter (v: v.enable) (attrValues cfg));
inherit (allPersistentStoragePaths) files directories;
mountFile = pkgs.runCommand "impermanence-mount-file" { buildInputs = [ pkgs.bash ]; } ''
Expand All @@ -72,10 +73,158 @@ let
# Create all fileSystems bind mount entries for a specific
# persistent storage path.
bindMounts = listToAttrs (map mkBindMountNameValuePair directories);
# Script to create directories in persistent and ephemeral
# storage. The directory structure's mode and ownership mirror
# those of persistentStoragePath/dir.
createDirectories = pkgs.runCommand "impermanence-create-directories" { buildInputs = [ pkgs.bash ]; } ''
cp ${./create-directories.bash} $out
patchShebangs $out
'';

mkDirWithPerms =
{ dirPath
, persistentStoragePath
, user
, group
, mode
, enableDebugging
, ...
}:
let
args = [
persistentStoragePath
dirPath
user
group
mode
enableDebugging
];
in
''
${createDirectories} ${escapeShellArgs args}
'';

# Build an activation script which creates all persistent
# storage directories we want to bind mount.
dirCreationScript =
let
# The parent directories of files.
fileDirs = unique (catAttrs "parentDirectory" files);

# All the directories actually listed by the user and the
# parent directories of listed files.
explicitDirs = directories ++ fileDirs;

# Home directories have to be handled specially, since
# they're at the permissions boundary where they
# themselves should be owned by the user and have stricter
# permissions than regular directories, whereas its parent
# should be owned by root and have regular permissions.
#
# This simply collects all the home directories and sets
# the appropriate permissions and ownership.
homeDirs =
foldl'
(state: dir:
let
defaultPerms = {
mode = "0755";
user = "root";
group = "root";
};
homeDir = {
directory = dir.home;
dirPath = dir.home;
home = null;
mode = "0700";
user = dir.user;
group = users.${dir.user}.group;
inherit defaultPerms;
inherit (dir) persistentStoragePath enableDebugging;
};
in
if dir.home != null then
if !(elem homeDir state) then
state ++ [ homeDir ]
else
state
else
state
)
[ ]
explicitDirs;

# Generate entries for all parent directories of the
# argument directories, listed in the order they need to
# be created. The parent directories are assigned default
# permissions.
mkParentDirs = dirs:
let
# Create a new directory item from `dir`, the child
# directory item to inherit properties from and
# `path`, the parent directory path.
mkParent = dir: path: {
directory = path;
dirPath =
if dir.home != null then
concatPaths [ dir.home path ]
else
path;
inherit (dir) persistentStoragePath home enableDebugging;
inherit (dir.defaultPerms) user group mode;
};
# Create new directory items for all parent
# directories of a directory.
mkParents = dir:
map (mkParent dir) (parentsOf dir.directory);
in
unique (flatten (map mkParents dirs));

# Parent directories of home folders. This is usually only
# /home, unless the user's home is in a non-standard
# location.
homeDirParents = mkParentDirs homeDirs;

# Parent directories of all explicitly listed directories.
parentDirs = mkParentDirs explicitDirs;

# All directories in the order they should be created.
allDirs = homeDirParents ++ homeDirs ++ parentDirs ++ explicitDirs;
in
pkgs.writeShellScript "impermanence-run-create-directories" ''
_status=0
trap "_status=1" ERR
${concatMapStrings mkDirWithPerms allDirs}
exit $_status
'';

mkPersistFile = { filePath, persistentStoragePath, enableDebugging, ... }:
let
mountPoint = filePath;
targetFile = concatPaths [ persistentStoragePath filePath ];
args = escapeShellArgs [
mountPoint
targetFile
enableDebugging
];
in
''
${mountFile} ${args}
'';

persistFileScript =
pkgs.writeShellScript "impermanence-persist-files" ''
_status=0
trap "_status=1" ERR
${concatMapStrings mkPersistFile files}
exit $_status
'';

useSystemdActivation = (options.systemd ? sysusers && config.systemd.sysusers.enable) ||
(options.services ? userborn && config.services.userborn.enable);
in
{
options = {

environment.persistence = mkOption {
default = { };
type =
Expand Down Expand Up @@ -519,172 +668,49 @@ in
};
};
};
services = (map mkPersistFileService files) ++ lib.optional useSystemdActivation {
"persist-storage-dirs" = {
description = "Create persistent storage directories";
wantedBy = [ "sysinit.target" ];
after = [ "systemd-sysusers.service" ];
path = [ pkgs.util-linux ];
unitConfig.DefaultDependencies = false;
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = dirCreationScript;
};
};
"persist-files" = {
description = "Persist files to persistent storage";
wantedBy = [ "sysinit.target" ];
after = [ "systemd-sysusers.service" "persist-storage-dirs.service" ];
path = [ pkgs.util-linux ];
unitConfig.DefaultDependencies = false;
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = persistFileScript;
};
};
};
in
foldl' recursiveUpdate { } (map mkPersistFileService files);
foldl' recursiveUpdate { } services;

fileSystems = mkIf (directories != [ ]) bindMounts;
# So the mounts still make it into a VM built from `system.build.vm`
virtualisation.fileSystems = mkIf (directories != [ ]) bindMounts;

system.activationScripts =
let
# Script to create directories in persistent and ephemeral
# storage. The directory structure's mode and ownership mirror
# those of persistentStoragePath/dir.
createDirectories = pkgs.runCommand "impermanence-create-directories" { buildInputs = [ pkgs.bash ]; } ''
cp ${./create-directories.bash} $out
patchShebangs $out
'';

mkDirWithPerms =
{ dirPath
, persistentStoragePath
, user
, group
, mode
, enableDebugging
, ...
}:
let
args = [
persistentStoragePath
dirPath
user
group
mode
enableDebugging
];
in
''
${createDirectories} ${escapeShellArgs args}
'';

# Build an activation script which creates all persistent
# storage directories we want to bind mount.
dirCreationScript =
let
# The parent directories of files.
fileDirs = unique (catAttrs "parentDirectory" files);

# All the directories actually listed by the user and the
# parent directories of listed files.
explicitDirs = directories ++ fileDirs;

# Home directories have to be handled specially, since
# they're at the permissions boundary where they
# themselves should be owned by the user and have stricter
# permissions than regular directories, whereas its parent
# should be owned by root and have regular permissions.
#
# This simply collects all the home directories and sets
# the appropriate permissions and ownership.
homeDirs =
foldl'
(state: dir:
let
defaultPerms = {
mode = "0755";
user = "root";
group = "root";
};
homeDir = {
directory = dir.home;
dirPath = dir.home;
home = null;
mode = "0700";
user = dir.user;
group = users.${dir.user}.group;
inherit defaultPerms;
inherit (dir) persistentStoragePath enableDebugging;
};
in
if dir.home != null then
if !(elem homeDir state) then
state ++ [ homeDir ]
else
state
else
state
)
[ ]
explicitDirs;

# Generate entries for all parent directories of the
# argument directories, listed in the order they need to
# be created. The parent directories are assigned default
# permissions.
mkParentDirs = dirs:
let
# Create a new directory item from `dir`, the child
# directory item to inherit properties from and
# `path`, the parent directory path.
mkParent = dir: path: {
directory = path;
dirPath =
if dir.home != null then
concatPaths [ dir.home path ]
else
path;
inherit (dir) persistentStoragePath home enableDebugging;
inherit (dir.defaultPerms) user group mode;
};
# Create new directory items for all parent
# directories of a directory.
mkParents = dir:
map (mkParent dir) (parentsOf dir.directory);
in
unique (flatten (map mkParents dirs));

# Parent directories of home folders. This is usually only
# /home, unless the user's home is in a non-standard
# location.
homeDirParents = mkParentDirs homeDirs;

# Parent directories of all explicitly listed directories.
parentDirs = mkParentDirs explicitDirs;

# All directories in the order they should be created.
allDirs = homeDirParents ++ homeDirs ++ parentDirs ++ explicitDirs;
in
pkgs.writeShellScript "impermanence-run-create-directories" ''
_status=0
trap "_status=1" ERR
${concatMapStrings mkDirWithPerms allDirs}
exit $_status
'';

mkPersistFile = { filePath, persistentStoragePath, enableDebugging, ... }:
let
mountPoint = filePath;
targetFile = concatPaths [ persistentStoragePath filePath ];
args = escapeShellArgs [
mountPoint
targetFile
enableDebugging
];
in
''
${mountFile} ${args}
'';

persistFileScript =
pkgs.writeShellScript "impermanence-persist-files" ''
_status=0
trap "_status=1" ERR
${concatMapStrings mkPersistFile files}
exit $_status
'';
in
{
"createPersistentStorageDirs" = {
deps = [ "users" "groups" ];
text = "${dirCreationScript}";
};
"persist-files" = {
deps = [ "createPersistentStorageDirs" ];
text = "${persistFileScript}";
};
system.activationScripts = lib.mkIf (!useSystemdActivation) {
"createPersistentStorageDirs" = {
deps = [ "users" "groups" ];
text = "${dirCreationScript}";
};
"persist-files" = {
deps = [ "createPersistentStorageDirs" ];
text = "${persistFileScript}";
};
};

# Create the mountpoints of directories marked as needed for boot
# which are also persisted. For this to work, it has to run at
Expand Down

0 comments on commit 2bd09dc

Please sign in to comment.