Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix impermanence in combination with sysusers/userborn #223

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions create-directories.bash
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,10 @@ fi
realSource="$(realpath -m "$sourceBase$target")"
if [[ ! -d "$realSource" ]]; then
printf "Warning: Source directory '%s' does not exist; it will be created for you with the following permissions: owner: '%s:%s', mode: '%s'.\n" "$realSource" "$user" "$group" "$mode"
mkdir --mode="$mode" "$realSource"
chown "$user:$group" "$realSource"
mkdir "$realSource"
fi

[[ -d "$target" ]] || mkdir "$target"

# synchronize perms between source and target
chown --reference="$realSource" "$target"
chmod --reference="$realSource" "$target"
chown "$user:$group" "$realSource" "$target"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See commit message for reasoning.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit skeptical of this, since it would apply mode changes to already existing directories, whether a mode is supplied for the directory or not. I'm not really sure what's not working for you, though. Are the initial directories created with the incorrect permissions?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I guess this isn't critical for the main purpose of this PR, so maybe it could be moved to its own?

chmod "$mode" "$realSource" "$target"
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
Loading