From ae78cb70269cc4b95ec4f3fe4fbea05f511cfcf2 Mon Sep 17 00:00:00 2001 From: atagen Date: Fri, 24 Jan 2025 16:59:48 +1100 Subject: [PATCH] module overhaul module --- .gitignore | 1 + README.md | 155 ++++++++------- default.nix | 111 +++++------ docs.md | 459 ++++++++++++++++++++++++++++++++++++++++++++ docs.nix | 34 ++++ example/caddy.nix | 18 +- example/flake.nix | 59 ++++-- example/shell.nix | 46 ++++- flake.nix | 16 +- ides.nix | 268 ++++++++++++++++++++++++++ modules/default.nix | 6 + modules/redis.nix | 118 ++++++++++++ 12 files changed, 1127 insertions(+), 164 deletions(-) create mode 100644 docs.md create mode 100644 docs.nix create mode 100644 ides.nix create mode 100644 modules/default.nix create mode 100644 modules/redis.nix diff --git a/.gitignore b/.gitignore index f121bdb..55bd456 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ name +TODO diff --git a/README.md b/README.md index bc5390f..f2fe961 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # ides ## idempotent devshell ephemeral services -ides provides automatic idempotent launching of ephemeral services +ides provides automated, idempotent launching of ephemeral services in your devshell, @@ -31,83 +31,119 @@ right here, right now. ## the bottom line -your dev environment now includes your service dependencies! +your dev environment now reproducibly includes your service dependencies! ## how ? - bring ides into your nix expression (flake input/fetchGit) -- set it up by invoking its `use` function on your nixpkgs instance -- use `mkShell` like you normally would, but with *spicy extras* +- import it + - optionally provide a `pkgs` instance, mkShell-like function, or ides modules +- use ides like a normal `mkShell`, but with *spicy extras* - make sure you run `et-tu` before you log out! here's how: -### service configuration (caddy.nix) -```nix -{ - pkg = pkgs.caddy; - args = "run -c %CFG% --adapter caddyfile"; - config = '' - http://*:8080 { - respond "hello" - } - ''; -} -``` -we template the provided config's path as %CFG% in the `args` option. - -### classic nix(tm) +### classic nix(tm) ([shell.nix](example/shell.nix)) ```nix let - pkgs = import {}; - ides = import (fetchGit { + pkgs = import { }; + + ides = fetchGit { url = "https://git.atagen.co/atagen/ides"; - }); - mkShell = ides.use pkgs; + }; + + mkIdes = import ides { + # optional instantiation args + inherit pkgs; + shell = pkgs.mkShell.override { + stdenv = pkgs.stdenvNoCC; + }; + modules = [ ]; + }; in - mkShell { - noCC = true; - services.caddy = import ./caddy.nix; - } +mkIdes { + # ides-specific options + imports = [ ./caddy.nix ]; + services.redis = { + enable = true; + port = 6889; + logLevel = "verbose"; + }; + # regular mkShell options + nativeBuildInputs = [ pkgs.hello ]; + someEnv = "this"; +} ``` -### flake enjoyers +### flake enjoyers ([flake.nix](example/flake.nix)) ```nix { inputs = { ides.url = "git+https://git.atagen.co/atagen/ides"; }; - outputs = { - nixpkgs, - ides, - ... - }: let - pkgs = nixpkgs.legacyPackages.x86_64-linux; - mkShell = ides.lib.use pkgs; - in { - devShells.x86_64-linux.default = mkShell { - noCC = true; - services = { - caddy = import ./caddy.nix; + outputs = + { + nixpkgs, + ides, + ... + }: + let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + mkIdes = import ides { + inherit pkgs; + shell = pkgs.mkShell.override { + stdenv = pkgs.stdenvNoCC; + }; + modules = [ ]; + }; + in + { + devShells.x86_64-linux.default = mkIdes { + imports = [ ./caddy.nix ]; + services.redis = { + enable = true; + port = 6889; + logLevel = "verbose"; + }; + nativeBuildInputs = [ pkgs.hello ]; + someEnv = "this"; }; }; - }; } ``` -### options -- `services: attrset of service configs`: set up your services for ides -- `noCC: bool`: sets whether to use mkShell or mkShellNoCC -- `...`: all other options are passed directly to mkShell as per usual +### concrete service definition ([caddy.nix](example/caddy.nix)) +```nix +{ pkgs, ... }: +{ + # as simple as possible + serviceDefs.caddy = { + pkg = pkgs.caddy; + # ides injects the config path whereever %CFG% is used in `args` + args = "run -c %CFG% --adapter caddyfile"; + config.text = '' + http://*:8888 { + respond "hello" + } + ''; + }; +} +``` +here, we use a simple plaintext config, but ides also supports converting +attribute sets into the following formats (via `config.content` & `config.format`): +- `json` +- `yaml` +- `toml` +- `ini` +- `xml` +- `php` +- `java` -#### service config attributes - - `pkg`: the package to launch as a service - - `args`: the arguments to the service. writing `%CFG%` in this will template to your config location - - `ext`: in case your service is picky about its file extension, set it here - - `config`: your service config. +### writing a service module +see [the provided redis module](modules/redis.nix) for an example -if plaintext isn't your thing, check out pkgs.writers and lib.generators -for ways to generate json, yaml, etc from nix attribute sets. +### more detail +for fully commented examples, see [here](example) ### cli in case you need manual control, an ides shell provides commands: @@ -115,17 +151,6 @@ in case you need manual control, an ides shell provides commands: - `et-tu`: shut down the service set - `restart`: do both of the above in succession -## why not reuse (nixpkgs/hm/...) module system ? -ides was originally conceived with this in mind, but in practice, -it is rather difficult to decouple the module systems from the -deployments they are intended to fulfill. +### documentation +see [module docs](docs.md) -occasional prodding is ongoing, and some activity appears to have -begin in nixpkgs to modularise services, which would allow ides -to take full advantage of the enormous nixos ecosystem. - -## acknowledgements -- me -- bald gang -- nixpkgs manual authors -- devenv, for the idea of flake services diff --git a/default.nix b/default.nix index 34d083c..e6e7eb7 100644 --- a/default.nix +++ b/default.nix @@ -1,68 +1,51 @@ +# import stage args { - use = pkgs: { - inherit pkgs; - __functor = self: shell: let - inherit (pkgs) writeText writeShellScriptBin; - inherit (pkgs.lib) getExe foldlAttrs; - inherit (builtins) hashString removeAttrs; + pkgs ? import , + shell ? pkgs.mkShell, + modules ? [ ], + ... +}: +# shell creation args +{ + services ? { }, + imports ? [ ], + ... +}@args: +let + # filter ides args out + # for passthrough to mkShell + shellArgs = builtins.removeAttrs args [ + "services" + "serviceDefs" + "imports" + ]; + # include some premade services + baseModules = [ ./modules/redis.nix ]; + # eval the config + eval = pkgs.lib.evalModules { + modules = + [ + # ides + ./ides.nix + # service config and build params + ( + { ... }: + { + inherit services; + _buildIdes.shellFn = shell; + _buildIdes.shellArgs = shellArgs; + } + ) + ] + ++ baseModules + ++ modules + ++ imports; - noCC = shell.noCC or false; + specialArgs = { + inherit pkgs; + }; - mkWorks = { - pkg, - args ? "", - config, - ext ? "", - }: let - bin = getExe pkg; - - name = pkg.pname; - unitName = "shell-${name}-${cfgHash}"; - - cfgHash = hashString "sha256" config; - finalConf = writeText "config-${name}-${cfgHash}${ext}" config; - - finalArgs = builtins.replaceStrings ["%CFG%"] [finalConf.outPath] args; - in { - runner = '' - echo "[ides]: Starting ${name}.." - systemd-run --user -G -u ${unitName} ${bin} ${finalArgs} - ''; - cleaner = '' - echo "[ides]: Stopping ${name}.." - systemctl --user stop ${unitName} - ''; - }; - - works = - foldlAttrs (acc: name: svc: let - pair = mkWorks svc; - in { - runners = acc.runners + pair.runner; - cleaners = acc.cleaners + pair.cleaner; - }) { - runners = ""; - cleaners = ""; - } (shell.services or {}); - - runners = writeShellScriptBin "ides" works.runners; - cleaners = writeShellScriptBin "et-tu" (works.cleaners - + '' - systemctl --user reset-failed - ''); - restart = writeShellScriptBin "restart" "et-tu; ides"; - - final = - (removeAttrs shell ["services" "noCC"]) - // { - nativeBuildInputs = (shell.nativeBuildInputs or []) ++ [runners cleaners restart]; - shellHook = (shell.shellHook or "") + '' - ides - ''; - }; - in - if noCC - then self.pkgs.mkShellNoCC final - else self.pkgs.mkShell final; + class = "ides"; }; -} +in +eval.config._buildIdes.shell diff --git a/docs.md b/docs.md new file mode 100644 index 0000000..a5487f5 --- /dev/null +++ b/docs.md @@ -0,0 +1,459 @@ +## serviceDefs + +Concrete service definitions, as per submodule options\. +Please put service-related options into ` services ` instead, and use this to implement them\. + + + +*Type:* +attribute set of (submodule) + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## serviceDefs\.\\.args + + + +Arguments to supply to the service binary\. Writing %CFG% in this will template to your config location\. + + + +*Type:* +string + + + +*Default:* +` "" ` + + + +*Example:* +` "run -c %CFG% --adapter caddyfile" ` + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## serviceDefs\.\\.config + + + +Options for setting the service’s configuration\. + + + +*Type:* +submodule + + + +*Default:* +` { } ` + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## serviceDefs\.\\.config\.content + + + +Attributes that define your config values\. + + + +*Type:* +null or (attribute set) + + + +*Default:* +` null ` + + + +*Example:* + +``` +{ + this = "that"; +} +``` + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## serviceDefs\.\\.config\.ext + + + +If your service config requires a file extension, set it here\. This overrides ` format `’s output path’\. + + + +*Type:* +string + + + +*Default:* +` "" ` + + + +*Example:* +` "json" ` + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## serviceDefs\.\\.config\.file + + + +Path to config file\. This overrides all other values\. + + + +*Type:* +null or path + + + +*Default:* +` null ` + + + +*Example:* +` /home/bolt/code/ides/configs/my-config.ini ` + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## serviceDefs\.\\.config\.format + + + +Config output format\. +One of: +` java json yaml toml ini xml php `\. + + + +*Type:* +null or one of “java”, “json”, “yaml”, “toml”, “ini”, “xml”, “php” + + + +*Default:* +` null ` + + + +*Example:* +` "json" ` + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## serviceDefs\.\\.config\.formatter + + + +Serialisation/writer function to apply to ` content `\. +` format ` will auto-apply the correct format if the option value is valid\. +Should take ` path: attrs: ` and return a storepath\. + + + +*Type:* +anything + + + +*Default:* +` null ` + + + +*Example:* +` "pkgs.formats.yaml {}.generate" ` + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## serviceDefs\.\\.config\.text + + + +Plaintext configuration to use\. + + + +*Type:* +string + + + +*Default:* +` "" ` + + + +*Example:* + +``` +'' + http://*:8080 { + respond "hello" + } +'' +``` + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## serviceDefs\.\\.exec + + + +Alternative executable name to use from ` pkg `\. + + + +*Type:* +string + + + +*Default:* +` "" ` + + + +*Example:* +` "caddy" ` + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## serviceDefs\.\\.pkg + + + +Package to use for service\. + + + +*Type:* +package + + + +*Example:* +` "pkgs.caddy" ` + +*Declared by:* + - [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix) + + + +## services\.redis\.enable + + + +Whether to enable Enable Redis… + + + +*Type:* +boolean + + + +*Default:* +` false ` + + + +*Example:* +` true ` + +*Declared by:* + - [modules/redis\.nix](https://git.atagen.co/atagen/ides/modules/redis.nix) + + + +## services\.redis\.bind + + + +List of IPs to bind to\. + + + +*Type:* +list of string + + + +*Default:* + +``` +[ + "127.0.0.1" + "::1" +] +``` + +*Declared by:* + - [modules/redis\.nix](https://git.atagen.co/atagen/ides/modules/redis.nix) + + + +## services\.redis\.databases + + + +Number of databases\. + + + +*Type:* +signed integer + + + +*Default:* +` 16 ` + +*Declared by:* + - [modules/redis\.nix](https://git.atagen.co/atagen/ides/modules/redis.nix) + + + +## services\.redis\.extraConfig + + + +Additional config directives\. + + + +*Type:* +string + + + +*Default:* +` "" ` + +*Declared by:* + - [modules/redis\.nix](https://git.atagen.co/atagen/ides/modules/redis.nix) + + + +## services\.redis\.logLevel + + + +Logging verbosity level\. + + + +*Type:* +one of “debug”, “verbose”, “notice”, “warning”, “nothing” + + + +*Default:* +` "notice" ` + +*Declared by:* + - [modules/redis\.nix](https://git.atagen.co/atagen/ides/modules/redis.nix) + + + +## services\.redis\.port + + + +Port to bind to\. + + + +*Type:* +integer between 1024 and 65535 (both inclusive) + + + +*Default:* +` 6379 ` + +*Declared by:* + - [modules/redis\.nix](https://git.atagen.co/atagen/ides/modules/redis.nix) + + + +## services\.redis\.socket + + + +Unix socket to bind to\. + + + +*Type:* +null or string + + + +*Default:* +` null ` + +*Declared by:* + - [modules/redis\.nix](https://git.atagen.co/atagen/ides/modules/redis.nix) + + + +## services\.redis\.socketPerms + + + +Permissions for the unix socket\. + + + +*Type:* +null or signed integer + + + +*Default:* +` null ` + +*Declared by:* + - [modules/redis\.nix](https://git.atagen.co/atagen/ides/modules/redis.nix) + + diff --git a/docs.nix b/docs.nix new file mode 100644 index 0000000..b8c745c --- /dev/null +++ b/docs.nix @@ -0,0 +1,34 @@ +with import {}; + {}: let + eval = lib.evalModules { + specialArgs = {inherit pkgs;}; + modules = [./ides.nix ./modules]; + }; + optionsDoc = nixosOptionsDoc { + inherit (eval) options; + + transformOptions = opt: + opt + // { + # Clean up declaration sites to not refer to the NixOS source tree. + declarations = let + devDir = toString /home/bolt/code/ides; + inherit (lib) hasPrefix removePrefix; + in + map + (decl: + if hasPrefix (toString devDir) (toString decl) + then let + subpath = removePrefix "/" (removePrefix (toString devDir) (toString decl)); + in { + url = "https://git.atagen.co/atagen/ides/${subpath}"; + name = subpath; + } + else decl) + opt.declarations; + }; + }; + in + runCommand "docs.md" {} '' + cat ${optionsDoc.optionsCommonMark} > $out + '' diff --git a/example/caddy.nix b/example/caddy.nix index f33f0fd..d4234d8 100644 --- a/example/caddy.nix +++ b/example/caddy.nix @@ -1,9 +1,13 @@ +{ pkgs, ... }: { - pkg = pkgs.caddy; - args = "run -c %CFG% --adapter caddyfile"; - config = '' - http://*:8080 { - respond "hello" - } - ''; + # simplest possible concrete service definition + serviceDefs.caddy = { + pkg = pkgs.caddy; + args = "run -c %CFG% --adapter caddyfile"; + config.text = '' + http://*:8888 { + respond "hello" + } + ''; + }; } diff --git a/example/flake.nix b/example/flake.nix index a5ed4bc..dad8091 100644 --- a/example/flake.nix +++ b/example/flake.nix @@ -1,20 +1,53 @@ { + inputs = { ides.url = "git+https://git.atagen.co/atagen/ides"; }; - outputs = { - nixpkgs, - ides, - ... - }: let - pkgs = nixpkgs.legacyPackages.x86_64-linux; - mkShell = ides.lib.use pkgs; - in { - devShells.x86_64-linux.default = mkShell { - noCC = true; - services = { - caddy = import ./caddy.nix; + + outputs = + { + nixpkgs, + ides, + ... + }: + let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + + # create an instance of ides to spawn shells from + mkIdes = import ides { + # ides needs a pkgs instance to work with in flake mode + inherit pkgs; + + # all other args here are optional + + # shell function to wrap - + # could also use pkgs.mkShellNoCC, but override demonstrates + # more clearly how to change any aspect of the shell + shell = pkgs.mkShell.override { + stdenv = pkgs.stdenvNoCC; + }; + + # input for extra modules that provide service options + # see modules/redis.nix in ides source for example + modules = [ ]; + # if you want to include a premade service def, + # use `imports` in the shell instead + }; + in + { + devShells.x86_64-linux.default = mkIdes { + # import a concrete service definition + imports = [ ./caddy.nix ]; + # use the options provided by a module + services.redis = { + enable = true; + port = 6889; + logLevel = "verbose"; + }; + # use normal mkShell options + nativeBuildInputs = [ pkgs.hello ]; + someEnv = "this"; }; }; - }; + } diff --git a/example/shell.nix b/example/shell.nix index 45a6b0d..2fc5c1f 100644 --- a/example/shell.nix +++ b/example/shell.nix @@ -1,11 +1,41 @@ let - pkgs = import {}; - ides = import (fetchGit { + pkgs = import { }; + + ides = fetchGit { url = "https://git.atagen.co/atagen/ides"; - }); - mkShell = ides.use pkgs; + }; + + # create an instance of ides to spawn shells from + mkIdes = import ides { + # all args here are optional + + # define the pkgs instance to work with + inherit pkgs; + + # shell function to wrap - + # could also use pkgs.mkShellNoCC, but override demonstrates + # more clearly how to change any aspect of the shell + shell = pkgs.mkShell.override { + stdenv = pkgs.stdenvNoCC; + }; + + # input for extra modules that provide service options + # see modules/redis.nix in ides source for example + modules = [ ]; + # if you want to include a premade service def, + # use `imports` in the shell instead + }; in - mkShell { - noCC = true; - services.caddy = import ./caddy.nix; - } +mkIdes { + # import a concrete service definition + imports = [ ./caddy.nix ]; + # use the options provided by a module + services.redis = { + enable = true; + port = 6889; + logLevel = "verbose"; + }; + # use normal mkShell options + nativeBuildInputs = [ pkgs.hello ]; + someEnv = "this"; +} diff --git a/flake.nix b/flake.nix index e203823..b88af06 100644 --- a/flake.nix +++ b/flake.nix @@ -1,9 +1,11 @@ { - outputs = {...}: { - lib = import ./default.nix; - templates.default = { - path = ./example; - description = "the ides template"; - }; - }; + outputs = + { ... }: + { + lib = import ./default.nix; + templates.default = { + path = ./example; + description = "the ides template"; + }; + }; } diff --git a/ides.nix b/ides.nix new file mode 100644 index 0000000..c0822a1 --- /dev/null +++ b/ides.nix @@ -0,0 +1,268 @@ +{ + 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; + }; +} diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..b55abbe --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,6 @@ +{ ... }: +{ + imports = [ + ./redis.nix + ]; +} diff --git a/modules/redis.nix b/modules/redis.nix new file mode 100644 index 0000000..f21e7ea --- /dev/null +++ b/modules/redis.nix @@ -0,0 +1,118 @@ +{ + config, + pkgs, + lib, + ... +}: +{ + # create some options + options.services.redis = + let + inherit (lib) mkOption types; + in + { + enable = lib.mkEnableOption "Enable Redis."; + + bind = mkOption { + type = with types; listOf str; + description = "List of IPs to bind to."; + default = [ + "127.0.0.1" + "::1" + ]; + }; + + port = mkOption { + type = types.ints.between 1024 65535; + description = "Port to bind to."; + default = 6379; + }; + + socket = mkOption { + type = with types; nullOr str; + description = "Unix socket to bind to."; + default = null; + }; + + socketPerms = mkOption { + type = with types; nullOr int; + description = "Permissions for the unix socket."; + default = null; + }; + + logLevel = mkOption { + type = types.enum [ + "debug" + "verbose" + "notice" + "warning" + "nothing" + ]; + description = "Logging verbosity level."; + default = "notice"; + }; + + databases = mkOption { + type = types.int; + description = "Number of databases."; + default = 16; + }; + + # escape hatch due to redis config being massive + extraConfig = mkOption { + type = types.str; + description = "Additional config directives."; + default = ""; + }; + }; + + config.serviceDefs.redis = + let + cfg = config.services.redis; + in + lib.mkIf cfg.enable { + pkg = pkgs.redis; + # make sure we get the server binary, not cli + exec = "redis-server"; + args = "%CFG%"; + config = { + ext = ".conf"; + # these need to be made to match redis config + # variable names here + content = { + inherit (cfg) bind port databases; + unixsocket = cfg.socket; + unixsocketperm = cfg.socketPerms; + loglevel = cfg.logLevel; + }; + # a formatter needs to take in a set of + # attrs and write out a file + formatter = + let + # set up serialisation for all types + serialise = { + int = builtins.toString; + bool = b: if b then "yes" else "no"; + string = s: s; + path = builtins.toString; + null = _: _; + list = builtins.concatStringsSep " "; + float = builtins.toString; + set = throw "cannot serialise a set in redis format"; + lambda = throw "cannot serialise a lambda, wtf?"; + }; + in + # create a lambda that can serialise to redis config + path: attrs: + let + text = + (lib.foldlAttrs ( + acc: n: v: + if (v != null) then acc + "${n} ${serialise.${builtins.typeOf v} v}" + "\n" else acc + ) "" attrs) + + cfg.extraConfig; + in + (pkgs.writeText path text).outPath; + }; + }; +}