75fa0ae5af
GitOrigin-RevId: c3ab5ea047e6dc73df530948f7367455749d8906
213 lines
6.5 KiB
Nix
213 lines
6.5 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
inherit (pkgs.stdenv.hostPlatform) isDarwin;
|
|
inherit (lib.generators) toPlist;
|
|
|
|
cfg = config.launchd;
|
|
labelPrefix = "org.nix-community.home.";
|
|
dstDir = "${config.home.homeDirectory}/Library/LaunchAgents";
|
|
|
|
launchdConfig = { config, name, ... }: {
|
|
options = {
|
|
enable = mkEnableOption name;
|
|
config = mkOption {
|
|
type = types.submodule (import ./launchd.nix);
|
|
default = { };
|
|
example = literalExpression ''
|
|
{
|
|
ProgramArguments = [ "/usr/bin/say" "Good afternoon" ];
|
|
StartCalendarInterval = [
|
|
{
|
|
Hour = 12;
|
|
Minute = 0;
|
|
}
|
|
];
|
|
}
|
|
'';
|
|
description = ''
|
|
Define a launchd job. See {manpage}`launchd.plist(5)` for details.
|
|
'';
|
|
};
|
|
};
|
|
|
|
config = { config.Label = mkDefault "${labelPrefix}${name}"; };
|
|
};
|
|
|
|
toAgent = config: pkgs.writeText "${config.Label}.plist" (toPlist { } config);
|
|
|
|
agentPlists =
|
|
mapAttrs' (n: v: nameValuePair "${v.config.Label}.plist" (toAgent v.config))
|
|
(filterAttrs (n: v: v.enable) cfg.agents);
|
|
|
|
agentsDrv = pkgs.runCommand "home-manager-agents" { } ''
|
|
mkdir -p "$out"
|
|
|
|
declare -A plists
|
|
plists=(${
|
|
concatStringsSep " "
|
|
(mapAttrsToList (name: value: "['${name}']='${value}'") agentPlists)
|
|
})
|
|
|
|
for dest in "''${!plists[@]}"; do
|
|
src="''${plists[$dest]}"
|
|
ln -s "$src" "$out/$dest"
|
|
done
|
|
'';
|
|
in {
|
|
meta.maintainers = with maintainers; [ midchildan ];
|
|
|
|
options.launchd = {
|
|
enable = mkOption {
|
|
type = types.bool;
|
|
default = isDarwin;
|
|
defaultText = literalExpression "pkgs.stdenv.hostPlatform.isDarwin";
|
|
description = ''
|
|
Whether to enable Home Manager to define per-user daemons by making use
|
|
of launchd's LaunchAgents.
|
|
'';
|
|
};
|
|
|
|
agents = mkOption {
|
|
type = with types; attrsOf (submodule launchdConfig);
|
|
default = { };
|
|
description = "Define LaunchAgents.";
|
|
};
|
|
};
|
|
|
|
config = mkMerge [
|
|
{
|
|
assertions = [{
|
|
assertion = (cfg.enable && agentPlists != { }) -> isDarwin;
|
|
message = let names = lib.concatStringsSep ", " (attrNames agentPlists);
|
|
in "Must use Darwin for modules that require Launchd: " + names;
|
|
}];
|
|
}
|
|
|
|
(mkIf isDarwin {
|
|
home.extraBuilderCommands = ''
|
|
ln -s "${agentsDrv}" $out/LaunchAgents
|
|
'';
|
|
|
|
home.activation.checkLaunchAgents =
|
|
hm.dag.entryBefore [ "writeBoundary" ] ''
|
|
checkLaunchAgents() {
|
|
local oldDir newDir dstDir err
|
|
oldDir=""
|
|
err=0
|
|
if [[ -n "''${oldGenPath:-}" ]]; then
|
|
oldDir="$(readlink -m "$oldGenPath/LaunchAgents")" || err=$?
|
|
if (( err )); then
|
|
oldDir=""
|
|
fi
|
|
fi
|
|
newDir=${escapeShellArg agentsDrv}
|
|
dstDir=${escapeShellArg dstDir}
|
|
|
|
local oldSrcPath newSrcPath dstPath agentFile agentName
|
|
|
|
find -L "$newDir" -maxdepth 1 -name '*.plist' -type f -print0 \
|
|
| while IFS= read -rd "" newSrcPath; do
|
|
agentFile="''${newSrcPath##*/}"
|
|
agentName="''${agentFile%.plist}"
|
|
dstPath="$dstDir/$agentFile"
|
|
oldSrcPath="$oldDir/$agentFile"
|
|
|
|
if [[ ! -e "$dstPath" ]]; then
|
|
continue
|
|
fi
|
|
|
|
if ! cmp --quiet "$oldSrcPath" "$dstPath"; then
|
|
errorEcho "Existing file '$dstPath' is in the way of '$newSrcPath'"
|
|
exit 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
checkLaunchAgents
|
|
'';
|
|
|
|
# NOTE: Launch Agent configurations can't be symlinked from the Nix store
|
|
# because it needs to be owned by the user running it.
|
|
home.activation.setupLaunchAgents =
|
|
hm.dag.entryAfter [ "writeBoundary" ] ''
|
|
setupLaunchAgents() {
|
|
local oldDir newDir dstDir domain err
|
|
oldDir=""
|
|
err=0
|
|
if [[ -n "''${oldGenPath:-}" ]]; then
|
|
oldDir="$(readlink -m "$oldGenPath/LaunchAgents")" || err=$?
|
|
if (( err )); then
|
|
oldDir=""
|
|
fi
|
|
fi
|
|
newDir="$(readlink -m "$newGenPath/LaunchAgents")"
|
|
dstDir=${escapeShellArg dstDir}
|
|
domain="gui/$UID"
|
|
err=0
|
|
|
|
local srcPath dstPath agentFile agentName i bootout_retries
|
|
bootout_retries=10
|
|
|
|
find -L "$newDir" -maxdepth 1 -name '*.plist' -type f -print0 \
|
|
| while IFS= read -rd "" srcPath; do
|
|
agentFile="''${srcPath##*/}"
|
|
agentName="''${agentFile%.plist}"
|
|
dstPath="$dstDir/$agentFile"
|
|
|
|
if cmp --quiet "$srcPath" "$dstPath"; then
|
|
continue
|
|
fi
|
|
if [[ -f "$dstPath" ]]; then
|
|
for (( i = 0; i < bootout_retries; i++ )); do
|
|
$DRY_RUN_CMD /bin/launchctl bootout "$domain/$agentName" || err=$?
|
|
if [[ -v DRY_RUN ]]; then
|
|
break
|
|
fi
|
|
if (( err != 9216 )) &&
|
|
! /bin/launchctl print "$domain/$agentName" &> /dev/null; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
if (( i == bootout_retries )); then
|
|
warnEcho "Failed to stop '$domain/$agentName'"
|
|
return 1
|
|
fi
|
|
fi
|
|
$DRY_RUN_CMD install -Dm444 -T "$srcPath" "$dstPath"
|
|
$DRY_RUN_CMD /bin/launchctl bootstrap "$domain" "$dstPath"
|
|
done
|
|
|
|
if [[ ! -e "$oldDir" ]]; then
|
|
return
|
|
fi
|
|
|
|
find -L "$oldDir" -maxdepth 1 -name '*.plist' -type f -print0 \
|
|
| while IFS= read -rd "" srcPath; do
|
|
agentFile="''${srcPath##*/}"
|
|
agentName="''${agentFile%.plist}"
|
|
dstPath="$dstDir/$agentFile"
|
|
if [[ -e "$newDir/$agentFile" ]]; then
|
|
continue
|
|
fi
|
|
|
|
$DRY_RUN_CMD /bin/launchctl bootout "$domain/$agentName" || :
|
|
if [[ ! -e "$dstPath" ]]; then
|
|
continue
|
|
fi
|
|
if ! cmp --quiet "$srcPath" "$dstPath"; then
|
|
warnEcho "Skipping deletion of '$dstPath', since its contents have diverged"
|
|
continue
|
|
fi
|
|
$DRY_RUN_CMD rm -f $VERBOSE_ARG "$dstPath"
|
|
done
|
|
}
|
|
|
|
setupLaunchAgents
|
|
'';
|
|
})
|
|
];
|
|
}
|