modules/users: prototype s6 integration: ~/.config/s6/{sources,compiled}

This commit is contained in:
Colin 2024-03-17 05:40:31 +00:00
parent 787e6af646
commit a5c36d39f4
2 changed files with 130 additions and 0 deletions

View File

@ -241,6 +241,7 @@ let
in
{
imports = [
./s6-rc.nix
./systemd.nix
];

129
modules/users/s6-rc.nix Normal file
View File

@ -0,0 +1,129 @@
{ lib, pkgs, ... }:
let
normalizeName = name: lib.removeSuffix ".service" (lib.removeSuffix ".target" name);
# infers the service type from the arguments and dispatches appropriately
genService = { name, run, depends }: if run != null then
genService' (normalizeName name) "longrun" depends [
(pkgs.writeTextFile {
name = "s6-${name}-run";
destination = "/${normalizeName name}/run";
text = run;
})
]
else
# TODO: a bundle can totally have dependencies. i can't just map them *all* to contents.
# genService' (normalizeName name) "bundle" [] (
# (builtins.map
# (d: pkgs.writeTextFile {
# name = "s6-${name}-contains-${d}";
# destination = "/${normalizeName name}/contents.d/${normalizeName d}";
# text = "";
# })
# depends
# ) ++ [
# # in case the bundle has no contents, ensure `contents.d` still gets made
# (pkgs.runCommandLocal "s6-${name}-contains.d" {} ''
# mkdir -p $out/"${normalizeName name}"/contents.d
# '')
# ]
# )
genService' (normalizeName name) "bundle" [] [
(pkgs.writeTextFile {
name = "s6-${name}-contents";
destination = "/${normalizeName name}/contents";
text = lib.concatStringsSep "\n" (builtins.map normalizeName depends);
})
]
;
genService' = name: type: depends: others: pkgs.symlinkJoin {
name = "s6-${name}";
paths = others ++ [
(pkgs.writeTextFile {
name = "s6-${name}-type";
destination = "/${name}/type";
text = type;
})
] ++ builtins.map
(d: pkgs.writeTextFile {
name = "s6-${name}-depends-${d}";
destination = "/${name}/dependencies.d/${normalizeName d}";
text = "";
})
depends;
};
# create a directory, containing N subdirectories:
# - svc-a/
# - type
# - run
# - svc-b/
# - type
# - run
# - ...
genServices = svcs: pkgs.symlinkJoin {
name = "s6-user-services";
paths = builtins.map genService svcs;
};
# output is a directory containing:
# - db
# - lock
# - n
# - resolve.cdb
# - servicedirs/
# - svc-a/
# - svc-b/
# - ...
#
# this can then be used by s6-rc-init, like:
# s6-svscan scandir &
# s6-rc-init -c $compiled -l $PWD/live -d $PWD/scandir
# s6-rc -l $PWD/live start svc-a
compileServices = sources: with pkgs; stdenv.mkDerivation {
name = "s6-user-services";
src = sources;
nativeBuildInputs = [ s6-rc ];
buildPhase = ''
s6-rc-compile $out $src
'';
};
# transform the `user.services` attrset into a s6 services list.
s6SvcsFromConfigServices = services: lib.mapAttrsToList
(name: service: {
inherit name;
run = service.serviceConfig.ExecStart;
depends = service.wants ++ builtins.attrNames (
lib.filterAttrs (_: cfg: lib.elem name cfg.wantedBy) services
);
})
services
;
# in the systemd service management, these targets are implicitly defined and used
# to accomplish something like run-levels, or service groups.
# map them onto s6 "bundles". their contents are determined via reverse dependency mapping (`wantedBy` of every other service).
implicitServices = {
"default.target" = {
serviceConfig.ExecStart = null;
wants = [];
wantedBy = [];
};
"graphical-session.target" = {
serviceConfig.ExecStart = null;
wants = [];
wantedBy = [];
};
};
in
{
options.sane.users = with lib; mkOption {
type = types.attrsOf (types.submodule ({ config, ...}: let
sources = genServices (s6SvcsFromConfigServices (implicitServices // config.services));
in {
fs.".config/s6/sources".symlink.target = sources;
fs.".config/s6/compiled".symlink.target = compileServices sources;
}));
};
}