2023-04-24 06:49:56 +00:00
|
|
|
{ config, lib, options, pkgs, sane-lib, ... }:
|
2023-02-02 12:31:13 +00:00
|
|
|
let
|
2023-04-23 23:21:08 +00:00
|
|
|
inherit (builtins) any attrValues elem map;
|
2023-02-02 12:31:13 +00:00
|
|
|
inherit (lib)
|
2023-04-26 00:17:04 +00:00
|
|
|
concatMapAttrs
|
2023-02-02 12:31:13 +00:00
|
|
|
filterAttrs
|
|
|
|
hasAttrByPath
|
|
|
|
getAttrFromPath
|
|
|
|
mapAttrs
|
2023-04-26 00:17:04 +00:00
|
|
|
mapAttrs'
|
2023-02-02 12:31:13 +00:00
|
|
|
mapAttrsToList
|
|
|
|
mkDefault
|
|
|
|
mkIf
|
2023-02-05 19:34:32 +00:00
|
|
|
mkMerge
|
2023-02-02 12:31:13 +00:00
|
|
|
mkOption
|
2023-02-03 03:58:23 +00:00
|
|
|
optional
|
2023-02-02 12:31:13 +00:00
|
|
|
optionalAttrs
|
|
|
|
splitString
|
|
|
|
types
|
|
|
|
;
|
|
|
|
inherit (sane-lib) joinAttrsets;
|
|
|
|
cfg = config.sane.programs;
|
2023-04-23 23:21:08 +00:00
|
|
|
pkgSpec = types.submodule ({ config, name, ... }: {
|
2023-02-02 12:31:13 +00:00
|
|
|
options = {
|
|
|
|
package = mkOption {
|
2023-02-03 03:58:23 +00:00
|
|
|
type = types.nullOr types.package;
|
|
|
|
description = ''
|
|
|
|
package, or `null` if the program is some sort of meta set (in which case it much EXPLICITLY be set null).
|
|
|
|
'';
|
2023-02-03 04:23:26 +00:00
|
|
|
default =
|
|
|
|
let
|
|
|
|
pkgPath = splitString "." name;
|
|
|
|
in
|
|
|
|
# package can be inferred by the attr name, allowing shorthand like
|
2023-02-03 05:26:57 +00:00
|
|
|
# `sane.programs.nano.enable = true;`
|
2023-02-03 04:23:26 +00:00
|
|
|
# this indexing will throw if the package doesn't exist and the user forgets to specify
|
|
|
|
# a valid source explicitly.
|
|
|
|
getAttrFromPath pkgPath pkgs;
|
2023-02-02 12:31:13 +00:00
|
|
|
};
|
|
|
|
enableFor.system = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = any (en: en) (
|
|
|
|
mapAttrsToList
|
|
|
|
(otherName: otherPkg:
|
|
|
|
otherName != name && elem name otherPkg.suggestedPrograms && otherPkg.enableSuggested && otherPkg.enableFor.system
|
|
|
|
)
|
|
|
|
cfg
|
|
|
|
);
|
|
|
|
description = ''
|
|
|
|
place this program on the system PATH
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
enableFor.user = mkOption {
|
|
|
|
type = types.attrsOf types.bool;
|
|
|
|
default = joinAttrsets (mapAttrsToList (otherName: otherPkg:
|
|
|
|
optionalAttrs
|
|
|
|
(otherName != name && elem name otherPkg.suggestedPrograms && otherPkg.enableSuggested)
|
|
|
|
(filterAttrs (user: en: en) otherPkg.enableFor.user)
|
|
|
|
) cfg);
|
|
|
|
description = ''
|
|
|
|
place this program on the PATH for some specified user(s).
|
|
|
|
'';
|
|
|
|
};
|
2023-04-23 23:21:08 +00:00
|
|
|
enabled = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
description = ''
|
|
|
|
generated (i.e. read-only) value indicating if the program is enabled either for any user or for the system.
|
|
|
|
'';
|
|
|
|
};
|
2023-02-02 12:31:13 +00:00
|
|
|
suggestedPrograms = mkOption {
|
|
|
|
type = types.listOf types.str;
|
|
|
|
default = [];
|
|
|
|
description = ''
|
|
|
|
list of other programs a user may want to enable alongside this one.
|
|
|
|
for example, the gnome desktop environment would suggest things like its settings app.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
enableSuggested = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = true;
|
|
|
|
};
|
2023-04-24 07:22:33 +00:00
|
|
|
persist = {
|
|
|
|
plaintext = mkOption {
|
|
|
|
type = types.listOf types.str;
|
|
|
|
default = [];
|
|
|
|
description = "list of home-relative paths to persist for this package";
|
|
|
|
};
|
|
|
|
private = mkOption {
|
|
|
|
type = types.listOf types.str;
|
|
|
|
default = [];
|
|
|
|
description = "list of home-relative paths to persist (in encrypted format) for this package";
|
|
|
|
};
|
2023-02-02 12:31:13 +00:00
|
|
|
};
|
2023-04-24 06:49:56 +00:00
|
|
|
fs = mkOption {
|
|
|
|
type = types.attrs;
|
|
|
|
default = {};
|
|
|
|
description = "files to populate when this program is enabled";
|
|
|
|
};
|
2023-04-26 00:17:04 +00:00
|
|
|
secrets = mkOption {
|
|
|
|
type = types.attrsOf types.path;
|
|
|
|
default = {};
|
|
|
|
description = ''
|
|
|
|
fs paths to link to some decrypted secret.
|
|
|
|
the secret will have same owner as the user under which the program is enabled.
|
|
|
|
'';
|
|
|
|
};
|
2023-02-02 12:31:13 +00:00
|
|
|
};
|
|
|
|
|
2023-04-23 23:21:08 +00:00
|
|
|
config = {
|
|
|
|
enabled = config.enableFor.system || any (en: en) (attrValues config.enableFor.user);
|
|
|
|
};
|
2023-02-02 12:31:13 +00:00
|
|
|
});
|
|
|
|
toPkgSpec = types.coercedTo types.package (p: { package = p; }) pkgSpec;
|
|
|
|
|
2023-02-04 00:43:00 +00:00
|
|
|
configs = mapAttrsToList (name: p: {
|
|
|
|
assertions = map (sug: {
|
|
|
|
assertion = cfg ? "${sug}";
|
|
|
|
message = ''program "${sug}" referenced by "${name}", but not defined'';
|
|
|
|
}) p.suggestedPrograms;
|
|
|
|
|
2023-02-02 12:31:13 +00:00
|
|
|
# conditionally add to system PATH
|
2023-02-03 03:58:23 +00:00
|
|
|
environment.systemPackages = optional
|
|
|
|
(p.package != null && p.enableFor.system)
|
|
|
|
p.package;
|
2023-04-24 06:49:56 +00:00
|
|
|
|
2023-02-02 12:31:13 +00:00
|
|
|
# conditionally add to user(s) PATH
|
2023-02-03 04:23:26 +00:00
|
|
|
users.users = mapAttrs (user: en: {
|
|
|
|
packages = optional (p.package != null && en) p.package;
|
2023-02-02 12:31:13 +00:00
|
|
|
}) p.enableFor.user;
|
2023-04-24 06:49:56 +00:00
|
|
|
|
|
|
|
# conditionally persist relevant user dirs and create files
|
2023-02-02 12:31:13 +00:00
|
|
|
sane.users = mapAttrs (user: en: optionalAttrs en {
|
2023-04-26 00:17:04 +00:00
|
|
|
inherit (p) persist;
|
|
|
|
fs = mkMerge [
|
|
|
|
p.fs
|
|
|
|
(mapAttrs
|
|
|
|
# link every secret into the fs
|
|
|
|
# TODO: user the user's *actual* home directory, don't guess.
|
|
|
|
(homePath: _src: sane-lib.fs.wantedSymlinkTo "/run/secrets/home/${user}/${homePath}")
|
|
|
|
p.secrets
|
|
|
|
)
|
|
|
|
];
|
2023-02-02 12:31:13 +00:00
|
|
|
}) p.enableFor.user;
|
2023-04-26 00:17:04 +00:00
|
|
|
|
|
|
|
# make secrets available for each user
|
|
|
|
sops.secrets = concatMapAttrs
|
|
|
|
(user: en: optionalAttrs en (
|
|
|
|
mapAttrs'
|
|
|
|
(homePath: src: {
|
|
|
|
# TODO: user the user's *actual* home directory, don't guess.
|
2023-04-26 03:46:18 +00:00
|
|
|
# XXX: name CAN'T START WITH '/', else sops creates the directories funny.
|
|
|
|
# TODO: report this upstream.
|
|
|
|
name = "home/${user}/${homePath}";
|
2023-04-26 00:17:04 +00:00
|
|
|
value = {
|
|
|
|
owner = user;
|
|
|
|
sopsFile = src;
|
|
|
|
format = "binary";
|
|
|
|
};
|
|
|
|
})
|
|
|
|
p.secrets
|
|
|
|
))
|
|
|
|
p.enableFor.user;
|
|
|
|
|
2023-02-02 12:31:13 +00:00
|
|
|
}) cfg;
|
|
|
|
in
|
|
|
|
{
|
|
|
|
options = {
|
|
|
|
sane.programs = mkOption {
|
|
|
|
type = types.attrsOf toPkgSpec;
|
|
|
|
default = {};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
config =
|
|
|
|
let
|
|
|
|
take = f: {
|
2023-02-04 00:43:00 +00:00
|
|
|
assertions = f.assertions;
|
2023-02-02 12:31:13 +00:00
|
|
|
environment.systemPackages = f.environment.systemPackages;
|
|
|
|
users.users = f.users.users;
|
|
|
|
sane.users = f.sane.users;
|
2023-04-26 00:17:04 +00:00
|
|
|
sops.secrets = f.sops.secrets;
|
2023-02-02 12:31:13 +00:00
|
|
|
};
|
2023-02-05 19:34:32 +00:00
|
|
|
in mkMerge [
|
|
|
|
(take (sane-lib.mkTypedMerge take configs))
|
|
|
|
{
|
|
|
|
# expose the pkgs -- as available to the system -- as a build target.
|
|
|
|
system.build.pkgs = pkgs;
|
|
|
|
}
|
|
|
|
];
|
2023-02-02 12:31:13 +00:00
|
|
|
}
|