module overhaul

module
This commit is contained in:
atagen 2025-01-24 16:59:48 +11:00
parent 8fe3ddc38e
commit ae78cb7026
12 changed files with 1127 additions and 164 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
name
TODO

155
README.md
View File

@ -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 <nixpkgs> {};
ides = import (fetchGit {
pkgs = import <nixpkgs> { };
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

View File

@ -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 <nixpkgs>,
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

459
docs.md Normal file
View File

@ -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\.\<name>\.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\.\<name>\.config
Options for setting the services configuration\.
*Type:*
submodule
*Default:*
` { } `
*Declared by:*
- [ides\.nix](https://git.atagen.co/atagen/ides/ides.nix)
## serviceDefs\.\<name>\.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\.\<name>\.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\.\<name>\.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\.\<name>\.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\.\<name>\.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\.\<name>\.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\.\<name>\.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\.\<name>\.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)

34
docs.nix Normal file
View File

@ -0,0 +1,34 @@
with import <nixpkgs> {};
{}: 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
''

View File

@ -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"
}
'';
};
}

View File

@ -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";
};
};
};
}

View File

@ -1,11 +1,41 @@
let
pkgs = import <nixpkgs> {};
ides = import (fetchGit {
pkgs = import <nixpkgs> { };
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";
}

View File

@ -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";
};
};
}

268
ides.nix Normal file
View File

@ -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;
};
}

6
modules/default.nix Normal file
View File

@ -0,0 +1,6 @@
{ ... }:
{
imports = [
./redis.nix
];
}

118
modules/redis.nix Normal file
View File

@ -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;
};
};
}