From 2ba6116f10ba44a66a4f5be996e245374d60850f Mon Sep 17 00:00:00 2001 From: colin Date: Wed, 4 Jan 2023 11:22:26 +0000 Subject: [PATCH] fs/impermanence: more precisely control unit dependencies/ordering --- hosts/common/users.nix | 17 ++++---- modules/fs/default.nix | 54 ++++++++++++++----------- modules/impermanence/default.nix | 17 +++++--- modules/impermanence/stores/crypt.nix | 11 +++-- modules/impermanence/stores/private.nix | 32 +++++---------- 5 files changed, 72 insertions(+), 59 deletions(-) diff --git a/hosts/common/users.nix b/hosts/common/users.nix index 513a60f0..5207e47a 100644 --- a/hosts/common/users.nix +++ b/hosts/common/users.nix @@ -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 diff --git a/modules/fs/default.nix b/modules/fs/default.nix index 006ebd64..4aa869dd 100644 --- a/modules/fs/default.nix +++ b/modules/fs/default.nix @@ -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: 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: # - # 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)) ]; diff --git a/modules/impermanence/default.nix b/modules/impermanence/default.nix index 76995f56..85154fe5 100644 --- a/modules/impermanence/default.nix +++ b/modules/impermanence/default.nix @@ -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="] 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. diff --git a/modules/impermanence/stores/crypt.nix b/modules/impermanence/stores/crypt.nix index 9f94a405..297c7afb 100644 --- a/modules/impermanence/stores/crypt.nix +++ b/modules/impermanence/stores/crypt.nix @@ -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 diff --git a/modules/impermanence/stores/private.nix b/modules/impermanence/stores/private.nix index d48340de..33213579 100644 --- a/modules/impermanence/stores/private.nix +++ b/modules/impermanence/stores/private.nix @@ -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