{ 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"; executable = true; # text = run; text = '' #!/bin/sh ${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 # # N.B.: it seems the $compiled dir needs to be rw, for s6 to write lock files within it. # so `cp` and `chmod -R 600` it, first. 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 || lib.elem "${name}.service" 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; # N.B.: `compiled` needs to be writable (for locks -- maybe i can use symlinks to dodge this someday), # so write this nearby and copy it over to `compiled` later fs.".config/s6/compiled-static".symlink.target = compileServices sources; fs.".profile".symlink.text = '' function startS6() { local S6_TARGET="''${1:-default}" local COMPILED=$HOME/.config/s6/compiled local LIVE=$HOME/.config/s6/live local SCANDIR=$HOME/.config/s6/scandir rm -rf $SCANDIR mkdir $SCANDIR s6-svscan $SCANDIR & local SVSCAN=$! # the scandir is just links back into the compiled dir, # so the compiled dir therefore needs to be writable: rm -rf $COMPILED cp --dereference -R $COMPILED-static $COMPILED chmod -R 0700 $COMPILED s6-rc-init -c $COMPILED -l $LIVE -d $SCANDIR # echo default: deps # s6-rc-db -c $COMPILED contents default # echo graphical-session: deps # s6-rc-db -c $COMPILED contents graphical-session if [ -n "$S6_TARGET" ]; then s6-rc -l $LIVE start "$S6_TARGET" fi echo "s6 initialized: Ctrl+C to stop" wait $SVSCAN } primarySessionCommands+=('startS6 &') ''; })); }; }