fs: rework for dir
to not be mandatory
This commit is contained in:
parent
5533b586d7
commit
5fb67306e4
|
@ -13,43 +13,60 @@ let
|
||||||
parent = path-lib.parent name;
|
parent = path-lib.parent name;
|
||||||
has-parent = path-lib.hasParent name;
|
has-parent = path-lib.hasParent name;
|
||||||
parent-cfg = if has-parent then cfg."${parent}" else {};
|
parent-cfg = if has-parent then cfg."${parent}" else {};
|
||||||
parent-dir = parent-cfg.dir or {};
|
parent-acl = if has-parent then parent-cfg.generated.acl else {};
|
||||||
parent-acl = parent-dir.acl or {};
|
|
||||||
in {
|
in {
|
||||||
options = {
|
options = {
|
||||||
dir = mkOption {
|
dir = mkOption {
|
||||||
type = dirEntry;
|
type = types.nullOr dirEntry;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
symlink = mkOption {
|
||||||
|
type = types.nullOr symlinkEntry;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
generated = mkOption {
|
||||||
|
type = generatedEntry;
|
||||||
|
default = {};
|
||||||
};
|
};
|
||||||
mount = mkOption {
|
mount = mkOption {
|
||||||
type = types.nullOr (mountEntryFor name);
|
type = types.nullOr (mountEntryFor name);
|
||||||
default = null;
|
default = null;
|
||||||
};
|
};
|
||||||
symlink = mkOption {
|
|
||||||
type = types.nullOr (symlinkEntryFor name);
|
|
||||||
default = null;
|
|
||||||
};
|
|
||||||
depends = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
description = ''
|
|
||||||
list of systemd units needed to be run before this directory can be made.
|
|
||||||
applies at the moment of dir or symlink creation.
|
|
||||||
'';
|
|
||||||
default = [];
|
|
||||||
};
|
|
||||||
unit = mkOption {
|
unit = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
description = "name of the systemd unit which ensures this entry";
|
description = "name of the systemd unit which ensures this entry";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
config = {
|
config = let
|
||||||
dir.acl.user = lib.mkDefault (parent-acl.user or "root");
|
default-acl = {
|
||||||
dir.acl.group = lib.mkDefault (parent-acl.group or "root");
|
user = lib.mkDefault (parent-acl.user or "root");
|
||||||
dir.acl.mode = lib.mkDefault (parent-acl.mode or "0755");
|
group = lib.mkDefault (parent-acl.group or "root");
|
||||||
|
mode = lib.mkDefault (parent-acl.mode or "0755");
|
||||||
|
};
|
||||||
|
in {
|
||||||
# we put this here instead of as a `default` to ensure that users who specify additional
|
# we put this here instead of as a `default` to ensure that users who specify additional
|
||||||
# dependencies still get a dep on the parent (unless they assign with `mkForce`).
|
# dependencies still get a dep on the parent (unless they assign with `mkForce`).
|
||||||
depends = if has-parent then [ parent-cfg.unit ] else [];
|
generated.depends = if has-parent then [ parent-cfg.unit ] else [];
|
||||||
# if defaulted, this module is responsible for creating the directory
|
|
||||||
dir.unit = lib.mkDefault ((serviceNameFor name) + ".service");
|
# populate generated items from `dir` or `symlink` shorthands
|
||||||
|
generated.acl = lib.mkMerge [
|
||||||
|
default-acl
|
||||||
|
(lib.mkIf (config.dir != null)
|
||||||
|
(sane-lib.filterNonNull config.dir.acl))
|
||||||
|
(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)
|
||||||
|
];
|
||||||
|
generated.script = lib.mkMerge [
|
||||||
|
(lib.mkIf (config.dir != null) (ensureDirScript name config.dir))
|
||||||
|
(lib.mkIf (config.symlink != null) (ensureSymlinkScript name config.symlink))
|
||||||
|
];
|
||||||
|
|
||||||
|
# make the unit file which generates the underlying thing available so that `mount` can use it.
|
||||||
|
generated.unit = (serviceNameFor name) + ".service";
|
||||||
|
|
||||||
# if defaulted, this module is responsible for finalizing the entry.
|
# if defaulted, this module is responsible for finalizing the entry.
|
||||||
# the user could override this if, say, they finalize some aspect of the entry
|
# the user could override this if, say, they finalize some aspect of the entry
|
||||||
|
@ -57,24 +74,70 @@ let
|
||||||
unit = lib.mkDefault (
|
unit = lib.mkDefault (
|
||||||
if config.mount != null then
|
if config.mount != null then
|
||||||
config.mount.unit
|
config.mount.unit
|
||||||
else if config.symlink != null then
|
else
|
||||||
config.symlink.unit
|
config.generated.unit
|
||||||
else config.dir.unit
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
# sane.fs."<path>".dir sub-options
|
# sane.fs."<path>".dir sub-options
|
||||||
dirEntry = types.submodule {
|
dirEntry = types.submodule {
|
||||||
|
# TODO: options should just be `propagatedGenerateOptions`
|
||||||
options = {
|
options = {
|
||||||
acl = mkOption {
|
acl = mkOption {
|
||||||
type = sane-types.acl;
|
type = sane-types.aclOverride;
|
||||||
|
default = {};
|
||||||
};
|
};
|
||||||
reverseDepends = mkOption {
|
reverseDepends = mkOption {
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
description = "list of systemd units which should be made to depend on this unit (controls `wantedBy` and `before`)";
|
description = "list of systemd units which should be made to depend on this unit (controls `wantedBy` and `before`)";
|
||||||
default = [];
|
default = [];
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
symlinkEntry = types.submodule {
|
||||||
|
options = {
|
||||||
|
target = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "fs path to link to";
|
||||||
|
};
|
||||||
|
acl = mkOption {
|
||||||
|
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 unit (controls `wantedBy` and `before`)";
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
generatedEntry = types.submodule {
|
||||||
|
options = {
|
||||||
|
acl = mkOption {
|
||||||
|
type = sane-types.acl;
|
||||||
|
};
|
||||||
|
depends = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
description = ''
|
||||||
|
list of systemd units needed to be run before this item can be generated.
|
||||||
|
'';
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
reverseDepends = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
description = "list of systemd units which should be made to depend on this unit (controls `wantedBy` and `before`)";
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
script.script = mkOption {
|
||||||
|
type = types.lines;
|
||||||
|
};
|
||||||
|
script.scriptArgs = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
unit = mkOption {
|
unit = mkOption {
|
||||||
type = types.str;
|
type = types.str;
|
||||||
description = "name of the systemd unit which ensures this directory";
|
description = "name of the systemd unit which ensures this directory";
|
||||||
|
@ -103,38 +166,24 @@ let
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
symlinkEntryFor = path: types.submodule {
|
mkGeneratedConfig = path: gen-opt: let
|
||||||
options = {
|
wrapper = generateWrapperScript path gen-opt;
|
||||||
target = mkOption {
|
in {
|
||||||
type = types.str;
|
|
||||||
description = "fs path to link to";
|
|
||||||
default = null;
|
|
||||||
};
|
|
||||||
unit = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = "name of the systemd unit which mounts this path";
|
|
||||||
default = mountNameFor path;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# given a dirEntry definition, evaluate its toplevel `config` output.
|
|
||||||
mkDirConfig = path: opt: {
|
|
||||||
systemd.services."${serviceNameFor path}" = {
|
systemd.services."${serviceNameFor path}" = {
|
||||||
description = "prepare ${path}";
|
description = "prepare ${path}";
|
||||||
serviceConfig.Type = "oneshot";
|
serviceConfig.Type = "oneshot";
|
||||||
|
|
||||||
script = ensure-dir-script;
|
script = wrapper.script;
|
||||||
scriptArgs = "${path} ${opt.dir.acl.user} ${opt.dir.acl.group} ${opt.dir.acl.mode}";
|
scriptArgs = builtins.concatStringsSep " " wrapper.scriptArgs;
|
||||||
|
|
||||||
after = opt.depends;
|
after = gen-opt.depends;
|
||||||
wants = opt.depends;
|
wants = gen-opt.depends;
|
||||||
# prevent systemd making this unit implicitly dependent on sysinit.target.
|
# prevent systemd making this unit implicitly dependent on sysinit.target.
|
||||||
# see: <https://www.freedesktop.org/software/systemd/man/systemd.special.html>
|
# see: <https://www.freedesktop.org/software/systemd/man/systemd.special.html>
|
||||||
unitConfig.DefaultDependencies = "no";
|
unitConfig.DefaultDependencies = "no";
|
||||||
|
|
||||||
wantedBy = opt.dir.reverseDepends;
|
wantedBy = gen-opt.reverseDepends;
|
||||||
before = opt.dir.reverseDepends;
|
before = gen-opt.reverseDepends;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -155,36 +204,16 @@ let
|
||||||
# if the underlying path disappears, this mount will be stopped.
|
# if the underlying path disappears, this mount will be stopped.
|
||||||
"x-systemd.requires=${underlying.unit}"
|
"x-systemd.requires=${underlying.unit}"
|
||||||
# the mount depends on its target directory being prepared
|
# the mount depends on its target directory being prepared
|
||||||
"x-systemd.requires=${opt.dir.unit}"
|
"x-systemd.requires=${opt.generated.unit}"
|
||||||
]
|
]
|
||||||
++ opt.mount.extraOptions;
|
++ opt.mount.extraOptions;
|
||||||
noCheck = ifBind true;
|
noCheck = ifBind true;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
# given a symlinkEntry definition, evaluate its toplevel `config` output.
|
|
||||||
mkSymlinkConfig = path: opt: {
|
|
||||||
systemd.services."${serviceNameFor path}" = {
|
|
||||||
description = "prepare ${path}";
|
|
||||||
serviceConfig.Type = "oneshot";
|
|
||||||
|
|
||||||
script = ensure-symlink-script;
|
|
||||||
scriptArgs = "${path} ${opt.target}";
|
|
||||||
|
|
||||||
after = opt.depends;
|
|
||||||
wants = opt.depends;
|
|
||||||
# prevent systemd making this unit implicitly dependent on sysinit.target.
|
|
||||||
# see: <https://www.freedesktop.org/software/systemd/man/systemd.special.html>
|
|
||||||
unitConfig.DefaultDependencies = "no";
|
|
||||||
|
|
||||||
wantedBy = opt.dir.reverseDepends;
|
|
||||||
before = opt.dir.reverseDepends;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
mkFsConfig = path: opt: mergeTopLevel [
|
mkFsConfig = path: opt: mergeTopLevel [
|
||||||
(mkDirConfig path opt)
|
(mkGeneratedConfig path opt.generated)
|
||||||
(lib.mkIf (opt.symlink != null) (mkSymlinkConfig path opt))
|
|
||||||
(lib.mkIf (opt.mount != null) (mkMountConfig path opt))
|
(lib.mkIf (opt.mount != null) (mkMountConfig path opt))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -202,33 +231,56 @@ let
|
||||||
inherit (extract top) fileSystems systemd;
|
inherit (extract top) fileSystems systemd;
|
||||||
};
|
};
|
||||||
|
|
||||||
# systemd/shell script used to create and set perms for a specific dir
|
generateWrapperScript = path: gen-opt: {
|
||||||
ensure-dir-script = ''
|
script = ''
|
||||||
path="$1"
|
fspath="$1"
|
||||||
user="$2"
|
acluser="$2"
|
||||||
group="$3"
|
aclgroup="$3"
|
||||||
mode="$4"
|
aclmode="$4"
|
||||||
|
shift 4
|
||||||
|
|
||||||
if ! test -d "$path"
|
# try to chmod/chown the result even if the user script errors
|
||||||
then
|
_status=0
|
||||||
# if the directory *doesn't* exist, try creating it
|
trap "_status=\$?" ERR
|
||||||
# if we fail to create it, ensure we raced with something else and that it's actually a directory
|
|
||||||
mkdir "$path" || test -d "$path"
|
${gen-opt.script.script}
|
||||||
fi
|
|
||||||
chmod "$mode" "$path"
|
chmod "$aclmode" "$fspath"
|
||||||
chown "$user:$group" "$path"
|
chown "$acluser:$aclgroup" "$fspath"
|
||||||
'';
|
exit "$_status"
|
||||||
|
'';
|
||||||
|
scriptArgs = [ path gen-opt.acl.user gen-opt.acl.group gen-opt.acl.mode ] ++ gen-opt.script.scriptArgs;
|
||||||
|
};
|
||||||
|
|
||||||
|
# systemd/shell script used to create and set perms for a specific dir
|
||||||
|
ensureDirScript = path: dir-cfg: {
|
||||||
|
script = ''
|
||||||
|
dirpath="$1"
|
||||||
|
|
||||||
|
if ! test -d "$dirpath"
|
||||||
|
then
|
||||||
|
# if the directory *doesn't* exist, try creating it
|
||||||
|
# if we fail to create it, ensure we raced with something else and that it's actually a directory
|
||||||
|
mkdir "$dirpath" || test -d "$dirpath"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
scriptArgs = [ path ];
|
||||||
|
};
|
||||||
|
|
||||||
# systemd/shell script used to create a symlink
|
# systemd/shell script used to create a symlink
|
||||||
ensure-symlink-script = ''
|
ensureSymlinkScript = path: link-cfg: {
|
||||||
from="$1"
|
script = ''
|
||||||
to="$2"
|
lnfrom="$1"
|
||||||
|
lnto="$2"
|
||||||
|
|
||||||
ln -sf "$2" "$1"
|
ln -sf "$lnto" "$lnfrom"
|
||||||
'';
|
'';
|
||||||
|
scriptArgs = [ path link-cfg.target ];
|
||||||
|
};
|
||||||
|
|
||||||
# return all ancestors of this path.
|
# return all ancestors of this path.
|
||||||
# e.g. ancestorsOf "/foo/bar/baz" => [ "/" "/foo" "/foo/bar" ]
|
# e.g. ancestorsOf "/foo/bar/baz" => [ "/" "/foo" "/foo/bar" ]
|
||||||
|
# TODO: move this to path-lib?
|
||||||
ancestorsOf = path: if path-lib.hasParent path then
|
ancestorsOf = path: if path-lib.hasParent path then
|
||||||
ancestorsOf (path-lib.parent path) ++ [ (path-lib.parent path) ]
|
ancestorsOf (path-lib.parent path) ++ [ (path-lib.parent path) ]
|
||||||
else
|
else
|
||||||
|
|
Loading…
Reference in New Issue
Block a user