apparently the /log shorthand is only applicable to base `s6-supervise`, and not `s6-rc`. "pipeline"s are the s6-rc equivalent: <https://wiki.gentoo.org/wiki/S6-rc#Longrun_pipelining>
194 lines
5.9 KiB
Nix
194 lines
5.9 KiB
Nix
{ lib, pkgs, ... }:
|
|
let
|
|
logBase = "$HOME/.local/state/s6/logs";
|
|
normalizeName = name: lib.removeSuffix ".service" (lib.removeSuffix ".target" name);
|
|
|
|
# infers the service type from the arguments and dispatches appropriately
|
|
genService = { name, run, finish, depends }: let
|
|
name' = normalizeName name;
|
|
runFile = pkgs.writeTextFile {
|
|
name = "s6-${name'}-run";
|
|
destination = "/${name'}/run";
|
|
executable = true;
|
|
# TODO: consider using `makeWrapper`/`makeBinaryWrapper`?
|
|
text = ''
|
|
#!/bin/sh
|
|
echo "starting: s6-${name'}"
|
|
${run} 2>&1
|
|
'';
|
|
};
|
|
finishFile = pkgs.writeTextFile {
|
|
# TODO: use 'writeShellScript'?
|
|
name = "s6-${name'}-finish";
|
|
destination = "/${name'}/finish";
|
|
executable = true;
|
|
text = ''
|
|
#!/bin/sh
|
|
${finish}
|
|
'';
|
|
};
|
|
in if run != null then
|
|
genService' name' "longrun" depends (
|
|
[ runFile ]
|
|
++ lib.optionals (finish != null) [ finishFile ]
|
|
)
|
|
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' name' "bundle" [] [
|
|
(pkgs.writeTextFile {
|
|
name = "s6-${name'}-contents";
|
|
destination = "/${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;
|
|
finish = service.serviceConfig.ExecStopPost;
|
|
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"
|
|
|
|
if [ -n "$S6_TARGET" ]; then
|
|
s6-rc -l "$LIVE" start "$S6_TARGET"
|
|
fi
|
|
|
|
echo 's6 initialized: Ctrl+C to stop'
|
|
wait "$SVSCAN"
|
|
}
|
|
function startS6WithLogging() {
|
|
# TODO: might not want to create log dir here: move to nix fs/persistence.
|
|
mkdir -p "${logBase}"
|
|
startS6 2>&1 | tee /dev/stderr | s6-log -- T "${logBase}/catchall"
|
|
}
|
|
|
|
primarySessionCommands+=('startS6WithLogging &')
|
|
'';
|
|
}));
|
|
};
|
|
}
|