{ 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; })); }; }