fs/impermanence: more precisely control unit dependencies/ordering

This commit is contained in:
colin 2023-01-04 11:22:26 +00:00
parent 592d17b725
commit 2ba6116f10
5 changed files with 72 additions and 59 deletions

View File

@ -7,7 +7,10 @@ let
# see nixpkgs/nixos/modules/services/networking/dhcpcd.nix
hasDHCP = config.networking.dhcpcd.enable &&
(config.networking.useDHCP || any (i: i.useDHCP == true) (attrValues config.networking.interfaces));
mkSymlink = target: {
symlink.target = target;
wantedBeforeBy = [ "multi-user.target" ];
};
in
{
options = {
@ -103,14 +106,14 @@ in
];
# convenience
sane.fs."/home/colin/knowledge".symlink.target = "/home/colin/private/knowledge";
sane.fs."/home/colin/nixos".symlink.target = "/home/colin/dev/nixos";
sane.fs."/home/colin/Videos/servo".symlink.target = "/mnt/servo-media/Videos";
sane.fs."/home/colin/Videos/servo-incomplete".symlink.target = "/mnt/servo-media/incomplete";
sane.fs."/home/colin/Music/servo".symlink.target = "/mnt/servo-media/Music";
sane.fs."/home/colin/knowledge" = mkSymlink "/home/colin/private/knowledge";
sane.fs."/home/colin/nixos" = mkSymlink "/home/colin/dev/nixos";
sane.fs."/home/colin/Videos/servo" = mkSymlink "/mnt/servo-media/Videos";
sane.fs."/home/colin/Videos/servo-incomplete" = mkSymlink "/mnt/servo-media/incomplete";
sane.fs."/home/colin/Music/servo" = mkSymlink "/mnt/servo-media/Music";
# used by password managers, e.g. unix `pass`
sane.fs."/home/colin/.password-store".symlink.target = "/home/colin/knowledge/secrets/accounts";
sane.fs."/home/colin/.password-store" = mkSymlink "/home/colin/knowledge/secrets/accounts";
sane.impermanence.dirs.sys.plaintext = mkIf cfg.guest.enable [
# intentionally allow other users to write to the guest folder

View File

@ -32,6 +32,21 @@ let
type = types.nullOr (mountEntryFor name);
default = null;
};
wantedBy = mkOption {
type = types.listOf types.str;
default = [];
description = ''
list of units or targets which, when activated, should trigger this fs entry to be created.
'';
};
wantedBeforeBy = mkOption {
type = types.listOf types.str;
default = [];
description = ''
list of units or targets which, when activated, should first start and wait for this fs entry to be created.
if this unit fails, it will not block the targets in this list.
'';
};
unit = mkOption {
type = types.str;
description = "name of the systemd unit which ensures this entry";
@ -56,10 +71,6 @@ let
(lib.mkIf (config.symlink != null)
(sane-lib.filterNonNull config.symlink.acl))
];
generated.reverseDepends = lib.mkMerge [
(lib.mkIf (config.dir != null) config.dir.reverseDepends)
(lib.mkIf (config.symlink != null) config.symlink.reverseDepends)
];
# actually generate the item
generated.script = lib.mkMerge [
@ -90,11 +101,6 @@ let
type = sane-types.aclOverride;
default = {};
};
reverseDepends = mkOption {
type = types.listOf types.str;
description = "list of systemd units which should be made to depend on this item (controls `wantedBy` and `before`)";
default = [];
};
};
};
@ -109,18 +115,11 @@ let
type = types.str;
description = "fs path to link to";
};
reverseDepends = propagatedGenerateMod.options.reverseDepends // {
# symlinks are terminal, so by default create them during startup
default = [ "multi-user.target" ];
};
};
};
generatedEntry = types.submodule {
options = {
# we use a stricter acl type here, so don't inherit that.
inherit (propagatedGenerateMod.options) reverseDepends;
acl = mkOption {
type = sane-types.acl;
};
@ -153,9 +152,11 @@ let
description = "fs path to bind-mount from";
default = null;
};
extraOptions = mkOption {
depends = mkOption {
type = types.listOf types.str;
description = "extra fstab options for this mount";
description = ''
list of systemd units needed to be run before this entry can be mounted
'';
default = [];
};
unit = mkOption {
@ -166,7 +167,8 @@ let
};
};
mkGeneratedConfig = path: gen-opt: let
mkGeneratedConfig = path: opt: let
gen-opt = opt.generated;
wrapper = generateWrapperScript path gen-opt;
in {
systemd.services."${serviceNameFor path}" = {
@ -182,8 +184,8 @@ let
# see: <https://www.freedesktop.org/software/systemd/man/systemd.special.html>
unitConfig.DefaultDependencies = "no";
wantedBy = gen-opt.reverseDepends;
before = gen-opt.reverseDepends;
before = opt.wantedBeforeBy;
wantedBy = opt.wantedBy ++ opt.wantedBeforeBy;
};
};
@ -198,6 +200,10 @@ let
device = ifBind opt.mount.bind;
options = (if isBind then ["bind"] else [])
++ [
# disable defaults: don't require this to be mount as part of local-fs.target
# we'll handle that stuff precisely.
"noauto"
"nofail"
# x-systemd options documented here:
# - <https://www.freedesktop.org/software/systemd/man/systemd.mount.html>
# we can't mount this until after the underlying path is prepared.
@ -206,14 +212,16 @@ let
# the mount depends on its target directory being prepared
"x-systemd.requires=${opt.generated.unit}"
]
++ opt.mount.extraOptions;
++ (builtins.map (unit: "x-systemd.requires=${unit}") opt.mount.depends)
++ (builtins.map (unit: "x-systemd.before=${unit}") opt.wantedBeforeBy)
++ (builtins.map (unit: "x-systemd.wanted-by=${unit}") (opt.wantedBy ++ opt.wantedBeforeBy));
noCheck = ifBind true;
};
});
mkFsConfig = path: opt: mergeTopLevel [
(mkGeneratedConfig path opt.generated)
(mkGeneratedConfig path opt)
(lib.mkIf (opt.mount != null) (mkMountConfig path opt))
];

View File

@ -34,12 +34,19 @@ let
/mnt/crypt/private/var/private/www/root.
'';
};
extraOptions = mkOption {
defaultOrdering.wantedBeforeBy = mkOption {
type = types.listOf types.str;
default = [];
default = [ "local-fs.target" ];
description = ''
extra fstab options to include in all mounts downstream of this store.
e.g. ["noauto" "x-systemd.wanted-by=<blah>"] to automount but only after the store is explicitly unlocked.
list of units or targets which would prefer that everything in this store
be initialized before they run, but failing to do so should not error the items in this list.
'';
};
defaultOrdering.wantedBy = mkOption {
type = types.listOf types.str;
default = [ ];
description = ''
list of units or targets which, upon activation, should activate all units in this store.
'';
};
};
@ -168,7 +175,7 @@ in
# inherit perms & make sure we don't mount until after the mount point is setup correctly.
dir.acl = dir-acl;
mount.bind = backing-path;
mount.extraOptions = store.extraOptions;
inherit (store.defaultOrdering) wantedBy wantedBeforeBy;
};
sane.fs."${backing-path}" = {
# ensure the backing path has same perms as the mount point.

View File

@ -34,7 +34,14 @@ lib.mkIf config.sane.impermanence.enable
noCheck = true;
};
# let sane.fs know about our fileSystem and automatically add the appropriate dependencies
sane.fs."${store.device}".mount = {};
sane.fs."${store.device}".mount = {
# technically the dependency on the keyfile is extraneous because that *happens* to
# be needed to init the store.
depends = let
cryptfile = config.sane.fs."${store.underlying.path}/gocryptfs.conf";
keyfile = config.sane.fs."${store.underlying.key}";
in [ keyfile.unit cryptfile.unit ];
};
# let sane.fs know how to initialize the gocryptfs store,
# and that it MUST do so
@ -50,8 +57,6 @@ lib.mkIf config.sane.impermanence.enable
script.scriptArgs = [ store.underlying.path store.underlying.key ];
# we need the key in order to initialize the store
depends = [ config.sane.fs."${store.underlying.key}".unit ];
# the store must be initialized before we can mount it
reverseDepends = [ config.sane.fs."${store.device}".unit ];
};
# let sane.fs know how to generate the key for gocryptfs

View File

@ -12,14 +12,14 @@ lib.mkIf config.sane.impermanence.enable
# /home/colin/foo/bar when stored in `private` is visible at
# /home/colin/private/foo/bar
prefix = "/home/colin";
# fstab options inherited by all members of the store
extraOptions = let
defaultOrdering = let
private-unit = config.sane.fs."/home/colin/private".unit;
in [
"noauto"
# auto mount when ~/private is mounted
"x-systemd.wanted-by=${private-unit}"
];
in {
# auto create only after ~/private is mounted
wantedBy = [ private-unit ];
# we can't create things in private before local-fs.target
wantedBeforeBy = [ ];
};
};
fileSystems."/home/colin/private" = {
@ -27,6 +27,7 @@ lib.mkIf config.sane.impermanence.enable
fsType = "fuse.gocryptfs";
options = [
"noauto" # don't try to mount, until the user logs in!
"nofail"
"allow_other" # root ends up being the user that mounts this, so need to make it visible to `colin`.
"nodev"
"nosuid"
@ -36,20 +37,9 @@ lib.mkIf config.sane.impermanence.enable
noCheck = true;
};
sane.fs."/home/colin/private" = {
# let sane.fs know that this corresponds to a fileSystems entry
mount = {};
dir.reverseDepends = [
# ensure the directory is created during boot, and before user logs in.
"multi-user.target"
];
};
sane.fs."/nix/persist/home/colin/private" = {
dir.reverseDepends = [
# ensure the directory is created during boot, and before user logs in.
"multi-user.target"
];
};
# let sane.fs know about the endpoints
sane.fs."/home/colin/private".mount = {};
sane.fs."/nix/persist/home/colin/private".dir = {};
# TODO: could add this *specifically* to the .mount file for the encrypted fs?
system.fsPackages = [ pkgs.gocryptfs ]; # fuse needs to find gocryptfs