From fcbbfc4a659f99fdd8f31aba9d6bf3b454cb6aef Mon Sep 17 00:00:00 2001 From: Colin Date: Fri, 26 Jul 2024 12:18:14 +0000 Subject: [PATCH] fix s6 service ordering: unl0kr -> (wait for mount) -> sway note that the systemd-aware mount never completes -- it's stuck in 'activating' forever. that's the next challenge --- hosts/common/users/default.nix | 2 - modules/persist/stores/private.nix | 106 ++++++++++++++++++----------- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/hosts/common/users/default.nix b/hosts/common/users/default.nix index 410dfcf87..ba8b35218 100644 --- a/hosts/common/users/default.nix +++ b/hosts/common/users/default.nix @@ -32,8 +32,6 @@ in wheelNeedsPassword = false; }; - security.pam.mount.enable = true; - system.activationScripts.makeEtcShadowSandboxable = { deps = [ "users" ]; text = '' diff --git a/modules/persist/stores/private.nix b/modules/persist/stores/private.nix index 5bb0228ce..5dadfcc33 100644 --- a/modules/persist/stores/private.nix +++ b/modules/persist/stores/private.nix @@ -5,6 +5,61 @@ let persist-base = "/nix/persist"; origin = config.sane.persist.stores."private".origin; backing = sane-lib.path.concat [ persist-base "private" ]; + + gocryptfs-private = pkgs.writeShellApplication { + name = "mount.fuse.gocryptfs-private"; + runtimeInputs = with pkgs; [ + coreutils-full + gocryptfs + inotify-tools + ]; + text = '' + # backing=$1 + # facing=$2 + mountArgs=("$@") + passdir=/run/gocryptfs + passfile="$passdir/private.key" + + waitForPassfileOnce() { + local timeout=$1 + if [ -f "$passfile" ]; then + return 0 + else + # wait for some file to be created inside the directory. + # inotifywait returns 0 if the file was created. 1 or 2 if timeout was hit or it was interrupted by a different event. + inotifywait --timeout "$timeout" --event create "$passdir" + return 1 #< maybe it was created; we'll pick that up immediately, on next check + fi + } + waitForPassfile() { + # there's a race condition between testing the path and starting `inotifywait`. + # therefore, use a retry loop. exponential backoff to decrease the impact of the race condition, + # especially near the start of boot to allow for quick reboots even if/when i hit the race. + for timeout in 4 4 8 8 8 8 16 16 16 16 16 16 16 16; do + if waitForPassfileOnce "$timeout"; then + return 0 + fi + done + while true; do + if waitForPassfileOnce 30; then + return 0 + fi + done + } + tryOpenStore() { + # try to open the store (blocking), if it fails, then delete the passfile because the user probably entered the wrong password + echo "mounting with ''${mountArgs[*]}" + gocryptfs -fg "''${mountArgs[@]}" + rc=$? + # should only return if the mount errored -- probably because of a password failure! + rm -f "$passfile" + return "$rc" + } + + waitForPassfile + tryOpenStore + ''; + }; in lib.mkIf config.sane.persist.enable { @@ -28,7 +83,7 @@ lib.mkIf config.sane.persist.enable fileSystems."${origin}" = { device = backing; - fsType = "fuse.gocryptfs"; + fsType = "fuse.gocryptfs-private"; options = [ "noauto" # don't try to mount, until the user logs in! "nofail" @@ -39,50 +94,22 @@ lib.mkIf config.sane.persist.enable # "quiet" # "defaults" # "unknown flag: --defaults. Try 'gocryptfs -help'" "passfile=/run/gocryptfs/private.key" + # options so that we can block for the password file *without* systemd killing us. + # see: + "x-systemd.mount-timeout=infinity" + # "retry=10000" + "fg" ]; noCheck = true; }; - # let sane.fs know about the mount, and let systemd know to wait for the pass key before attempting a mount - sane.fs."${origin}".mount = { - depends = let - keyfile = config.sane.fs."/run/gocryptfs/private.key"; - in [ keyfile.unit ]; - }; + # let sane.fs know about the mount + sane.fs."${origin}".mount = {}; # it also needs to know that the underlying device is an ordinary folder sane.fs."${backing}" = sane-lib.fs.wanted { dir.acl.user = config.sane.defaultUser; }; - sane.fs."/run/gocryptfs/private.key".generated = let - script = pkgs.writeShellScript "wait-for-gocryptfs-private" '' - file="$1" - dir=$(dirname "$file") - - succeedOrWait() { - if [ -f "$file" ]; then - exit 0 - else - # wait for some file to be created inside the directory. - # inotifywait returns 0 if the file was created. 1 or 2 if timeout was hit or it was interrupted by a different event. - ${lib.getExe' pkgs.inotify-tools "inotifywait"} --timeout "$1" --event create "$dir" || true - fi - } - # there's a race condition between testing the path and starting `inotifywait`. - # therefore, use a retry loop. exponential backoff to decrease the impact of the race condition, - # especially near the start of boot to allow for quick reboots even if/when i hit the race. - for i in 4 4 8 8 16 16 16 16 16 16 16 16; do - succeedOrWait "$i" - done - while true; do - succeedOrWait 30 - done - ''; - in { - command = [ "${script}" "/run/gocryptfs/private.key" ]; - # no need for anyone else to be able to read the key - acl.mode = "0400"; - }; sane.fs."/run/gocryptfs" = sane-lib.fs.wanted { dir.acl.user = config.sane.defaultUser; dir.acl.mode = "0700"; @@ -95,12 +122,13 @@ lib.mkIf config.sane.persist.enable "${originUnit}".wantedBy = [ "local-fs.target" ]; }; - # TODO: could add this *specifically* to the .mount file for the encrypted fs? - system.fsPackages = [ pkgs.gocryptfs ]; # fuse needs to find gocryptfs + system.fsPackages = [ gocryptfs-private ]; sane.user.services.gocryptfs-private = { description = "wait for /mnt/persist/private to be mounted"; - startCommand = "${lib.getExe' pkgs.systemd "systemctl"} start mnt-persist-private.mount"; + # startCommand = "${lib.getExe' pkgs.systemd "systemctl"} start mnt-persist-private.mount"; + command = "sleep infinity"; + readiness.waitExists = [ "/mnt/persist/private/init" ]; partOf = [ "private-storage" ]; }; }