2020-06-04 21:46:38 +00:00
|
|
|
{ pkgs, config, lib, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
let
|
|
|
|
cfg = config.home.persistence;
|
|
|
|
|
|
|
|
persistentStoragePaths = attrNames cfg;
|
|
|
|
|
2020-06-07 12:36:10 +00:00
|
|
|
inherit (pkgs.callPackage ./lib.nix { }) splitPath dirListToPath concatPaths sanitizeName;
|
2020-06-04 21:46:38 +00:00
|
|
|
in
|
|
|
|
{
|
|
|
|
options = {
|
|
|
|
|
|
|
|
home.persistence = mkOption {
|
|
|
|
default = { };
|
2020-06-05 17:37:07 +00:00
|
|
|
type = with types; attrsOf (
|
|
|
|
submodule {
|
2020-06-04 21:46:38 +00:00
|
|
|
options =
|
|
|
|
{
|
|
|
|
directories = mkOption {
|
2020-06-07 08:12:37 +00:00
|
|
|
type = with types; listOf str;
|
2020-06-04 21:46:38 +00:00
|
|
|
default = [ ];
|
|
|
|
};
|
|
|
|
|
|
|
|
files = mkOption {
|
2020-06-07 08:12:37 +00:00
|
|
|
type = with types; listOf str;
|
2020-06-04 21:46:38 +00:00
|
|
|
default = [ ];
|
|
|
|
};
|
|
|
|
|
|
|
|
removePrefixDirectory = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
config = {
|
|
|
|
home.file =
|
|
|
|
let
|
|
|
|
link = file:
|
|
|
|
pkgs.runCommand
|
2020-06-07 12:36:10 +00:00
|
|
|
"${sanitizeName file}"
|
2020-06-04 21:46:38 +00:00
|
|
|
{ }
|
|
|
|
"ln -s '${file}' $out";
|
|
|
|
|
2020-06-07 12:36:10 +00:00
|
|
|
mkLinkNameValuePair = persistentStoragePath: file: {
|
2020-06-05 17:37:07 +00:00
|
|
|
name =
|
|
|
|
if cfg.${persistentStoragePath}.removePrefixDirectory then
|
2020-06-07 12:36:10 +00:00
|
|
|
dirListToPath (tail (splitPath [ file ]))
|
2020-06-05 17:37:07 +00:00
|
|
|
else
|
2020-06-07 12:36:10 +00:00
|
|
|
file;
|
|
|
|
value = { source = link (concatPaths [ persistentStoragePath file ]); };
|
2020-06-04 21:46:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
mkLinksToPersistentStorage = persistentStoragePath:
|
2020-06-05 17:37:07 +00:00
|
|
|
listToAttrs (map
|
|
|
|
(mkLinkNameValuePair persistentStoragePath)
|
2020-06-07 12:36:10 +00:00
|
|
|
(cfg.${persistentStoragePath}.files)
|
2020-06-05 17:37:07 +00:00
|
|
|
);
|
2020-06-04 21:46:38 +00:00
|
|
|
in
|
2020-06-05 17:37:07 +00:00
|
|
|
foldl' recursiveUpdate { } (map mkLinksToPersistentStorage persistentStoragePaths);
|
2020-06-04 21:46:38 +00:00
|
|
|
|
2020-06-07 12:36:10 +00:00
|
|
|
systemd.user.services =
|
|
|
|
let
|
|
|
|
mkBindMountService = persistentStoragePath: dir:
|
|
|
|
let
|
|
|
|
mountDir =
|
|
|
|
if cfg.${persistentStoragePath}.removePrefixDirectory then
|
|
|
|
dirListToPath (tail (splitPath [ dir ]))
|
|
|
|
else
|
|
|
|
dir;
|
|
|
|
targetDir = concatPaths [ persistentStoragePath dir ];
|
|
|
|
mountPoint = concatPaths [ config.home.homeDirectory mountDir ];
|
|
|
|
name = "bindMount-${sanitizeName targetDir}";
|
|
|
|
startScript = pkgs.writeShellScript name ''
|
|
|
|
set -eu
|
|
|
|
if ! ${pkgs.utillinux}/bin/mount | ${pkgs.gnugrep}/bin/grep -E '${mountPoint}( |/)'; then
|
|
|
|
${pkgs.bindfs}/bin/bindfs -f --no-allow-other "${targetDir}" "${mountPoint}"
|
|
|
|
else
|
|
|
|
echo "There is already an active mount at or below ${mountPoint}!" >&2
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
'';
|
|
|
|
stopScript = pkgs.writeShellScript "unmount-${name}" ''
|
2020-06-20 12:56:23 +00:00
|
|
|
triesLeft=6
|
|
|
|
while (( triesLeft > 0 )); do
|
|
|
|
if fusermount -u "${mountPoint}"; then
|
|
|
|
exit 0
|
|
|
|
else
|
|
|
|
(( triesLeft-- ))
|
|
|
|
if (( triesLeft == 0 )); then
|
|
|
|
echo "Couldn't perform regular unmount of ${mountPoint}. Attempting lazy unmount."
|
|
|
|
fusermount -uz "${mountPoint}"
|
|
|
|
else
|
|
|
|
sleep 5
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
done
|
2020-06-07 12:36:10 +00:00
|
|
|
'';
|
|
|
|
in
|
|
|
|
{
|
|
|
|
inherit name;
|
|
|
|
value = {
|
|
|
|
Unit = {
|
|
|
|
Description = "Bind mount ${targetDir} at ${mountPoint}";
|
|
|
|
PartOf = [ "graphical-session-pre.target" ];
|
|
|
|
|
|
|
|
# Don't restart the unit, it could corrupt data and
|
|
|
|
# crash programs currently reading from the mount.
|
|
|
|
X-RestartIfChanged = false;
|
|
|
|
};
|
|
|
|
|
|
|
|
Install.WantedBy = [ "default.target" ];
|
|
|
|
|
|
|
|
Service = {
|
|
|
|
ExecStart = "${startScript}";
|
|
|
|
ExecStop = "${stopScript}";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
mkBindMountServicesForPath = persistentStoragePath:
|
|
|
|
listToAttrs (map
|
|
|
|
(mkBindMountService persistentStoragePath)
|
|
|
|
cfg.${persistentStoragePath}.directories
|
|
|
|
);
|
|
|
|
in
|
|
|
|
builtins.foldl'
|
|
|
|
recursiveUpdate
|
|
|
|
{ }
|
|
|
|
(map mkBindMountServicesForPath persistentStoragePaths);
|
|
|
|
|
2020-06-04 21:46:38 +00:00
|
|
|
home.activation =
|
|
|
|
let
|
|
|
|
dag = config.lib.dag;
|
|
|
|
|
2020-06-07 12:36:10 +00:00
|
|
|
mkBindMount = persistentStoragePath: dir:
|
2020-06-04 21:46:38 +00:00
|
|
|
let
|
2020-06-07 12:36:10 +00:00
|
|
|
mountDir =
|
|
|
|
if cfg.${persistentStoragePath}.removePrefixDirectory then
|
|
|
|
dirListToPath (tail (splitPath [ dir ]))
|
|
|
|
else
|
|
|
|
dir;
|
2020-06-04 21:46:38 +00:00
|
|
|
targetDir = concatPaths [ persistentStoragePath dir ];
|
2020-06-07 12:36:10 +00:00
|
|
|
mountPoint = concatPaths [ config.home.homeDirectory mountDir ];
|
|
|
|
systemctl = "XDG_RUNTIME_DIR=\${XDG_RUNTIME_DIR:-/run/user/$(id -u)} ${config.systemd.user.systemctlPath}";
|
2020-06-05 17:37:07 +00:00
|
|
|
in
|
|
|
|
''
|
2020-06-04 21:46:38 +00:00
|
|
|
if [[ ! -e "${targetDir}" ]]; then
|
|
|
|
mkdir -p "${targetDir}"
|
|
|
|
fi
|
2020-06-07 12:36:10 +00:00
|
|
|
if [[ ! -e "${mountPoint}" ]]; then
|
|
|
|
mkdir -p "${mountPoint}"
|
|
|
|
fi
|
|
|
|
if ${pkgs.utillinux}/bin/mount | grep "${mountPoint}"; then
|
|
|
|
if ! ${pkgs.utillinux}/bin/mount | grep "${mountPoint}" | grep "${targetDir}"; then
|
|
|
|
# The target directory changed, so we need to remount
|
|
|
|
echo "remounting ${mountPoint}"
|
|
|
|
${systemctl} --user stop bindMount-${sanitizeName targetDir}
|
|
|
|
${pkgs.bindfs}/bin/bindfs --no-allow-other "${targetDir}" "${mountPoint}"
|
|
|
|
mountedPaths["${mountPoint}"]=1
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
${pkgs.bindfs}/bin/bindfs --no-allow-other "${targetDir}" "${mountPoint}"
|
|
|
|
mountedPaths["${mountPoint}"]=1
|
|
|
|
fi
|
2020-06-04 21:46:38 +00:00
|
|
|
'';
|
|
|
|
|
2020-06-07 12:36:10 +00:00
|
|
|
mkBindMountsForPath = persistentStoragePath:
|
|
|
|
concatMapStrings
|
|
|
|
(mkBindMount persistentStoragePath)
|
|
|
|
cfg.${persistentStoragePath}.directories;
|
|
|
|
|
|
|
|
mkUnmount = persistentStoragePath: dir:
|
|
|
|
let
|
|
|
|
mountDir =
|
|
|
|
if cfg.${persistentStoragePath}.removePrefixDirectory then
|
|
|
|
dirListToPath (tail (splitPath [ dir ]))
|
|
|
|
else
|
|
|
|
dir;
|
|
|
|
mountPoint = concatPaths [ config.home.homeDirectory mountDir ];
|
|
|
|
in
|
|
|
|
''
|
|
|
|
if [[ -n ''${mountedPaths["${mountPoint}"]+x} ]]; then
|
2020-06-20 12:56:23 +00:00
|
|
|
triesLeft=3
|
|
|
|
while (( triesLeft > 0 )); do
|
|
|
|
if fusermount -u "${mountPoint}"; then
|
|
|
|
break
|
|
|
|
else
|
|
|
|
(( triesLeft-- ))
|
|
|
|
if (( triesLeft == 0 )); then
|
|
|
|
echo "Couldn't perform regular unmount of ${mountPoint}. Attempting lazy unmount."
|
|
|
|
fusermount -uz "${mountPoint}" || true
|
|
|
|
else
|
|
|
|
sleep 1
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
done
|
2020-06-07 12:36:10 +00:00
|
|
|
fi
|
|
|
|
'';
|
|
|
|
|
|
|
|
mkUnmountsForPath = persistentStoragePath:
|
|
|
|
concatMapStrings
|
|
|
|
(mkUnmount persistentStoragePath)
|
|
|
|
cfg.${persistentStoragePath}.directories;
|
|
|
|
|
2020-06-04 21:46:38 +00:00
|
|
|
in
|
2020-06-07 12:36:10 +00:00
|
|
|
mkIf (any (path: cfg.${path}.directories != [ ]) persistentStoragePaths) {
|
|
|
|
createAndMountPersistentStoragePaths =
|
|
|
|
dag.entryBefore
|
|
|
|
[ "writeBoundary" ]
|
|
|
|
''
|
|
|
|
declare -A mountedPaths
|
|
|
|
${(concatMapStrings mkBindMountsForPath persistentStoragePaths)}
|
|
|
|
'';
|
|
|
|
|
|
|
|
unmountPersistentStoragePaths =
|
|
|
|
dag.entryBefore
|
|
|
|
[ "createAndMountPersistentStoragePaths" ]
|
|
|
|
''
|
|
|
|
unmountBindMounts() {
|
|
|
|
${concatMapStrings mkUnmountsForPath persistentStoragePaths}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Run the unmount function on error to clean up stray
|
|
|
|
# bind mounts
|
|
|
|
trap "unmountBindMounts" ERR
|
|
|
|
'';
|
|
|
|
|
|
|
|
runUnmountPersistentStoragePaths =
|
|
|
|
dag.entryBefore
|
|
|
|
[ "reloadSystemD" ]
|
|
|
|
''
|
|
|
|
unmountBindMounts
|
|
|
|
'';
|
|
|
|
};
|
2020-06-04 21:46:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
}
|