nix-files/modules/programs.nix

128 lines
3.8 KiB
Nix

{ config, lib, pkgs, sane-lib, ... }:
let
inherit (builtins) any elem map;
inherit (lib)
filterAttrs
hasAttrByPath
getAttrFromPath
mapAttrs
mapAttrsToList
mkDefault
mkIf
mkOption
optional
optionalAttrs
splitString
types
;
inherit (sane-lib) joinAttrsets;
cfg = config.sane.programs;
pkgSpec = types.submodule ({ name, ... }: {
options = {
package = mkOption {
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).
'';
default =
let
pkgPath = splitString "." name;
in
# package can be inferred by the attr name, allowing shorthand like
# `sane.programs.nano.enable = true;`
# this indexing will throw if the package doesn't exist and the user forgets to specify
# a valid source explicitly.
getAttrFromPath pkgPath pkgs;
};
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).
'';
};
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;
};
dir = 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";
};
};
});
toPkgSpec = types.coercedTo types.package (p: { package = p; }) pkgSpec;
configs = mapAttrsToList (name: p: {
assertions = map (sug: {
assertion = cfg ? "${sug}";
message = ''program "${sug}" referenced by "${name}", but not defined'';
}) p.suggestedPrograms;
# conditionally add to system PATH
environment.systemPackages = optional
(p.package != null && p.enableFor.system)
p.package;
# conditionally add to user(s) PATH
users.users = mapAttrs (user: en: {
packages = optional (p.package != null && en) p.package;
}) p.enableFor.user;
# conditionally persist relevant user dirs
sane.users = mapAttrs (user: en: optionalAttrs en {
persist.plaintext = p.dir;
persist.private = p.private;
}) p.enableFor.user;
}) cfg;
in
{
options = {
sane.programs = mkOption {
type = types.attrsOf toPkgSpec;
default = {};
};
};
config =
let
take = f: {
assertions = f.assertions;
environment.systemPackages = f.environment.systemPackages;
users.users = f.users.users;
sane.users = f.sane.users;
};
in
take (sane-lib.mkTypedMerge take configs);
}