ides/ides.nix
atagen ae78cb7026 module overhaul
module
2025-01-25 11:51:48 +11:00

269 lines
8.2 KiB
Nix

{
pkgs,
config,
...
}:
{
#
# interface
#
options =
let
inherit (pkgs) lib;
inherit (lib) types mkOption;
serviceConfig =
with types;
submodule {
options = {
pkg = mkOption {
type = package;
description = "Package to use for service.";
example = "pkgs.caddy";
};
exec = mkOption {
type = str;
description = "Alternative executable name to use from `pkg`.";
example = "caddy";
default = "";
};
args = mkOption {
type = str;
description = "Arguments to supply to the service binary. Writing %CFG% in this will template to your config location.";
example = "run -c %CFG% --adapter caddyfile";
default = "";
};
config = mkOption {
description = "Options for setting the service's configuration.";
default = { };
type = submodule {
options = {
text = mkOption {
type = str;
default = "";
description = "Plaintext configuration to use.";
example = ''
http://*:8080 {
respond "hello"
}
'';
};
ext = mkOption {
type = str;
default = "";
description = "If your service config requires a file extension, set it here. This overrides `format`'s output path'.";
example = "json";
};
file = mkOption {
type = nullOr path;
description = "Path to config file. This overrides all other values.";
example = ./configs/my-config.ini;
default = null;
};
content = mkOption {
type = nullOr attrs;
description = "Attributes that define your config values.";
default = null;
example = {
this = "that";
};
};
format = mkOption {
type = nullOr (enum [
"java"
"json"
"yaml"
"toml"
"ini"
"xml"
"php"
]);
description = "Config output format.\nOne of:\n`java json yaml toml ini xml php`.";
example = "json";
default = null;
};
formatter = mkOption {
type = types.anything;
description = "Serialisation/writer function to apply to `content`.\n`format` will auto-apply the correct format if the option value is valid.\nShould take `path: attrs:` and return a storepath.";
example = "pkgs.formats.yaml {}.generate";
default = null;
};
};
};
};
};
};
in
{
serviceDefs = mkOption {
type = types.attrsOf serviceConfig;
description = "Concrete service definitions, as per submodule options.\nPlease put service-related options into `services` instead, and use this to implement them.";
};
# lol https://github.com/NixOS/nixpkgs/issues/293510
_module.args = lib.mkOption {
internal = true;
};
# for internal use
_buildIdes = mkOption {
type = types.attrs;
internal = true;
};
};
#
# implementation
#
config =
let
branchOnConfig =
cfg:
{
text,
file,
content,
contentFmt,
}:
if (cfg.text != "") then
text
else if (cfg.file != null) then
file
else if (cfg.content != { }) then
if (cfg.format != null) then
content
else if (cfg.formatter != null) then
contentFmt
else
throw "`format` or `formatter` must be set for `content` ${cfg.content}!"
else
"";
in
{
# validate and complete the service configurations
_buildIdes.finalServices = builtins.mapAttrs (
name:
{
pkg,
args ? "",
exec ? "",
config,
}:
let
bin = if (exec == "") then pkgs.lib.getExe pkg else pkgs.lib.getExe' pkg exec;
ext =
if (config.ext != "") || (config.format != null) then "." + (config.ext or config.format) else "";
# we need this to create unit names that correspond to configs
cfgHash =
let
hashContent = builtins.hashString "sha256" (builtins.toJSON config.content);
in
branchOnConfig config {
text = builtins.hashString "sha256" config.text;
file = builtins.hashFile "sha256" config.file;
content = hashContent;
contentFmt = hashContent;
};
confFile =
let
writers = {
java = pkgs.formats.javaProperties { };
json = pkgs.formats.json { };
yaml = pkgs.formats.yaml { };
ini = pkgs.formats.ini { };
toml = pkgs.formats.toml { };
xml = pkgs.formats.xml { };
php = pkgs.formats.php { finalVariable = null; };
};
confPath = "config-${name}-${cfgHash}${ext}";
in
branchOnConfig config {
text = pkgs.writeText confPath config.text;
inherit (config) file;
content = writers.${config.format}.generate confPath config.content;
contentFmt = config.formatter confPath config.content;
};
finalArgs = builtins.replaceStrings [ "%CFG%" ] [ "${confFile}" ] args;
in
{
inherit name bin;
args = finalArgs;
unitName = "shell-${name}-${cfgHash}";
}
) config.serviceDefs;
# generate service scripts and create the shell
_buildIdes.shell =
let
mkWorks =
{
name,
unitName,
bin,
args,
}:
{
runner = ''
echo "[ides]: Starting ${name}.."
systemd-run --user -G -u ${unitName} ${bin} ${args}
'';
cleaner = ''
echo "[ides]: Stopping ${name}.."
systemctl --user stop ${unitName}
'';
};
works =
let
inherit (pkgs.lib) foldlAttrs;
in
foldlAttrs
(
acc: name: svc:
let
pair = mkWorks svc;
in
{
runners = acc.runners + pair.runner;
cleaners = acc.cleaners + pair.cleaner;
}
)
{
runners = "";
cleaners = "";
}
config._buildIdes.finalServices;
inherit (pkgs) writeShellScriptBin;
runners = writeShellScriptBin "ides" works.runners;
cleaners = writeShellScriptBin "et-tu" (
works.cleaners
+ ''
systemctl --user reset-failed
''
);
restart = writeShellScriptBin "restart" "et-tu; ides";
final =
let
shellArgs = config._buildIdes.shellArgs;
in
shellArgs
// {
nativeBuildInputs = (shellArgs.nativeBuildInputs or [ ]) ++ [
runners
cleaners
restart
];
shellHook =
(shellArgs.shellHook or "")
+ ''
ides
'';
};
in
config._buildIdes.shellFn final;
};
}