135 lines
3.9 KiB
Nix
135 lines
3.9 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
ascensionsDir = "/var/lib/ascensions";
|
|
|
|
ascensionType = { name, ... }: {
|
|
options = {
|
|
incantations = mkOption {
|
|
type = with types; functionTo (listOf package);
|
|
default = [];
|
|
};
|
|
distributed = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = "Whether to perform a distributed ascension using Consul KV.";
|
|
};
|
|
requiredBy = mkOption {
|
|
type = with types; listOf str;
|
|
default = [ "${name}.service" ];
|
|
description = "Services that require this ascension.";
|
|
};
|
|
before = mkOption {
|
|
type = with types; listOf str;
|
|
default = [];
|
|
description = "Run the ascension before these services.";
|
|
};
|
|
after = mkOption {
|
|
type = with types; listOf str;
|
|
default = [];
|
|
description = "Run the ascension after these services.";
|
|
};
|
|
};
|
|
};
|
|
|
|
builtinIncantations = with pkgs; rec {
|
|
execShellWith = extraPackages: script: writeShellScript "incantation" ''
|
|
export PATH='${makeBinPath ([ coreutils ] ++ extraPackages)}'
|
|
${script}
|
|
'';
|
|
|
|
execShell = execShellWith [];
|
|
|
|
multiple = incantations: execShell (concatStringsSep " && " incantations);
|
|
|
|
move = from: to: execShell ''
|
|
test -e ${escapeShellArg from} && rm -rf ${escapeShellArg to}
|
|
mkdir -p "$(dirname ${escapeShellArg to})"
|
|
mv ${escapeShellArgs [ from to ]}
|
|
'';
|
|
|
|
chmod = mode: target: "chmod -R ${escapeShellArgs [ mode target ]}";
|
|
};
|
|
|
|
allIncantations = builtinIncantations // mapAttrs (_: mk: mk allIncantations) config.system.extraIncantations;
|
|
|
|
runIncantations = f: f allIncantations;
|
|
|
|
consul = config.services.consul.package;
|
|
in
|
|
|
|
{
|
|
options.system = {
|
|
ascensions = mkOption {
|
|
type = with types; attrsOf (submodule ascensionType);
|
|
default = {};
|
|
};
|
|
extraIncantations = mkOption {
|
|
type = with types; attrsOf (functionTo raw);
|
|
default = {};
|
|
};
|
|
};
|
|
|
|
config = {
|
|
systemd = {
|
|
tmpfiles.rules = [
|
|
"d ${ascensionsDir} 0755 root root - -"
|
|
];
|
|
|
|
services = mapAttrs' (name: asc: {
|
|
name = "ascend-${name}";
|
|
value = let
|
|
incantations = runIncantations asc.incantations;
|
|
targetLevel = toString (length incantations);
|
|
in {
|
|
description = "Ascension for ${name}";
|
|
inherit (asc) requiredBy before;
|
|
after = asc.after ++ (lib.optional asc.distributed "consul.service");
|
|
serviceConfig.Type = "oneshot";
|
|
distributed.enable = asc.distributed;
|
|
script = ''
|
|
incantations=(${concatStringsSep " " incantations})
|
|
${if asc.distributed then /*bash*/ ''
|
|
getLevel() {
|
|
${consul}/bin/consul kv get 'ascensions/${name}/currentLevel'
|
|
}
|
|
setLevel() {
|
|
${consul}/bin/consul kv put 'ascensions/${name}/currentLevel' "$1" >/dev/null
|
|
}
|
|
isEmpty() {
|
|
! getLevel >/dev/null 2>/dev/null
|
|
}
|
|
'' else /*bash*/ ''
|
|
levelFile='${ascensionsDir}/${name}'
|
|
getLevel() {
|
|
echo "$(<"$levelFile")"
|
|
}
|
|
setLevel() {
|
|
echo "$1" > "$levelFile"
|
|
}
|
|
isEmpty() {
|
|
[[ ! -e "$levelFile" ]]
|
|
}
|
|
''
|
|
}
|
|
if isEmpty; then
|
|
setLevel '${targetLevel}'
|
|
echo Initializing at level ${targetLevel}
|
|
exit 0
|
|
fi
|
|
cur=$(getLevel)
|
|
echo Current level: $cur
|
|
for lvl in $(seq $(($cur+1)) ${targetLevel}); do
|
|
echo Running incantation for level $lvl...
|
|
''${incantations[$((lvl-1))]}
|
|
setLevel "$lvl"
|
|
done
|
|
echo All incantations complete, ascended to level $(getLevel)
|
|
'';
|
|
};
|
|
}) config.system.ascensions;
|
|
};
|
|
};
|
|
}
|