mirror of
https://github.com/nix-community/home-manager.git
synced 2025-11-08 19:46:05 +01:00
launchd: refactor setupLaunchAgents
This commit is contained in:
parent
86d2e3b005
commit
b431496538
1 changed files with 148 additions and 59 deletions
|
|
@ -93,81 +93,170 @@ in {
|
||||||
# NOTE: Launch Agent configurations can't be symlinked from the Nix store
|
# NOTE: Launch Agent configurations can't be symlinked from the Nix store
|
||||||
# because it needs to be owned by the user running it.
|
# because it needs to be owned by the user running it.
|
||||||
home.activation.setupLaunchAgents =
|
home.activation.setupLaunchAgents =
|
||||||
lib.hm.dag.entryAfter [ "writeBoundary" ] ''
|
lib.hm.dag.entryAfter [ "writeBoundary" ] # Bash
|
||||||
|
''
|
||||||
|
# Disable errexit to ensure we process all agents even if some fail
|
||||||
|
set +e
|
||||||
|
|
||||||
|
# Stop an agent if it's running
|
||||||
|
bootoutAgent() {
|
||||||
|
local domain="$1"
|
||||||
|
local agentName="$2"
|
||||||
|
|
||||||
|
verboseEcho "Stopping agent '$domain/$agentName'..."
|
||||||
|
local bootout_output
|
||||||
|
bootout_output=$(run /bin/launchctl bootout "$domain/$agentName" 2>&1) || {
|
||||||
|
# Only show warning if it's not the common "No such process" error
|
||||||
|
if [[ "$bootout_output" != *"No such process"* ]]; then
|
||||||
|
warnEcho "Failed to stop agent '$domain/$agentName': $bootout_output"
|
||||||
|
else
|
||||||
|
verboseEcho "Agent '$domain/$agentName' was not running"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Give the system a moment to fully unload the agent
|
||||||
|
sleep 1
|
||||||
|
}
|
||||||
|
|
||||||
|
installAndBootstrapAgent() {
|
||||||
|
local srcPath="$1"
|
||||||
|
local dstPath="$2"
|
||||||
|
local domain="$3"
|
||||||
|
local agentName="$4"
|
||||||
|
|
||||||
|
verboseEcho "Installing agent file to $dstPath"
|
||||||
|
if ! run install -Dm444 -T "$srcPath" "$dstPath"; then
|
||||||
|
errorEcho "Failed to install agent file for '$agentName'"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
verboseEcho "Starting agent '$domain/$agentName'"
|
||||||
|
local bootstrap_output
|
||||||
|
bootstrap_output=$(run /bin/launchctl bootstrap "$domain" "$dstPath" 2>&1) || {
|
||||||
|
local error_code=$?
|
||||||
|
|
||||||
|
if [[ "$bootstrap_output" == *"Bootstrap failed: 5: Input/output error"* ]]; then
|
||||||
|
errorEcho "Failed to start agent '$domain/$agentName' with I/O error (code 5)"
|
||||||
|
errorEcho "This typically happens when the agent wasn't unloaded before attempting to bootstrap the new agent."
|
||||||
|
else
|
||||||
|
errorEcho "Failed to start agent '$domain/$agentName' with error: $bootstrap_output"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
verboseEcho "Successfully started agent '$domain/$agentName'"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
processAgent() {
|
||||||
|
local srcPath="$1"
|
||||||
|
local dstDir="$2"
|
||||||
|
local domain="$3"
|
||||||
|
|
||||||
|
local agentFile="''${srcPath##*/}"
|
||||||
|
local agentName="''${agentFile%.plist}"
|
||||||
|
local dstPath="$dstDir/$agentFile"
|
||||||
|
|
||||||
|
# Skip if unchanged
|
||||||
|
if cmp -s "$srcPath" "$dstPath"; then
|
||||||
|
verboseEcho "Agent '$agentName' is already up-to-date"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
verboseEcho "Processing agent '$agentName'"
|
||||||
|
|
||||||
|
# Stop/Unload agent if it's already running
|
||||||
|
if [[ -f "$dstPath" ]]; then
|
||||||
|
bootoutAgent "$domain" "$agentName"
|
||||||
|
fi
|
||||||
|
|
||||||
|
installAndBootstrapAgent "$srcPath" "$dstPath" "$domain" "$agentName"
|
||||||
|
# Note: We continue processing even if this agent fails
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAgent() {
|
||||||
|
local srcPath="$1"
|
||||||
|
local dstDir="$2"
|
||||||
|
local newDir="$3"
|
||||||
|
local domain="$4"
|
||||||
|
|
||||||
|
local agentFile="''${srcPath##*/}"
|
||||||
|
local agentName="''${agentFile%.plist}"
|
||||||
|
local dstPath="$dstDir/$agentFile"
|
||||||
|
|
||||||
|
if [[ -e "$newDir/$agentFile" ]]; then
|
||||||
|
verboseEcho "Agent '$agentName' still exists in new generation, skipping cleanup"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -e "$dstPath" ]]; then
|
||||||
|
verboseEcho "Agent file '$dstPath' already removed"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! cmp -s "$srcPath" "$dstPath"; then
|
||||||
|
warnEcho "Skipping deletion of '$dstPath', since its contents have diverged"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stop and remove the agent
|
||||||
|
bootoutAgent "$domain" "$agentName"
|
||||||
|
|
||||||
|
verboseEcho "Removing agent file '$dstPath'"
|
||||||
|
if run rm -f $VERBOSE_ARG "$dstPath"; then
|
||||||
|
verboseEcho "Successfully removed agent file for '$agentName'"
|
||||||
|
else
|
||||||
|
warnEcho "Failed to remove agent file '$dstPath'"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
setupLaunchAgents() {
|
setupLaunchAgents() {
|
||||||
local oldDir newDir dstDir domain err
|
local oldDir newDir dstDir domain
|
||||||
oldDir=""
|
|
||||||
err=0
|
|
||||||
if [[ -n "''${oldGenPath:-}" ]]; then
|
|
||||||
oldDir="$(readlink -m "$oldGenPath/LaunchAgents")" || err=$?
|
|
||||||
if (( err )); then
|
|
||||||
oldDir=""
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
newDir="$(readlink -m "$newGenPath/LaunchAgents")"
|
newDir="$(readlink -m "$newGenPath/LaunchAgents")"
|
||||||
dstDir=${lib.escapeShellArg dstDir}
|
dstDir=${lib.escapeShellArg dstDir}
|
||||||
domain="gui/$UID"
|
domain="gui/$UID"
|
||||||
err=0
|
|
||||||
|
|
||||||
local srcPath dstPath agentFile agentName i bootout_retries
|
if [[ -n "''${oldGenPath:-}" ]]; then
|
||||||
bootout_retries=10
|
oldDir="$(readlink -m "$oldGenPath/LaunchAgents")"
|
||||||
|
if [[ ! -d "$oldDir" ]]; then
|
||||||
|
verboseEcho "No previous LaunchAgents directory found"
|
||||||
|
oldDir=""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
oldDir=""
|
||||||
|
fi
|
||||||
|
|
||||||
find -L "$newDir" -maxdepth 1 -name '*.plist' -type f -print0 \
|
verboseEcho "Setting up LaunchAgents in $dstDir"
|
||||||
| while IFS= read -rd "" srcPath; do
|
[[ -d "$dstDir" ]] || run mkdir -p "$dstDir"
|
||||||
agentFile="''${srcPath##*/}"
|
|
||||||
agentName="''${agentFile%.plist}"
|
|
||||||
dstPath="$dstDir/$agentFile"
|
|
||||||
|
|
||||||
if cmp --quiet "$srcPath" "$dstPath"; then
|
verboseEcho "Processing new/updated LaunchAgents..."
|
||||||
continue
|
find -L "$newDir" -maxdepth 1 -name '*.plist' -type f | while read -r srcPath; do
|
||||||
fi
|
processAgent "$srcPath" "$dstDir" "$domain"
|
||||||
if [[ -f "$dstPath" ]]; then
|
|
||||||
for (( i = 0; i < bootout_retries; i++ )); do
|
|
||||||
run /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
|
|
||||||
run install -Dm444 -T "$srcPath" "$dstPath"
|
|
||||||
run /bin/launchctl bootstrap "$domain" "$dstPath"
|
|
||||||
done
|
done
|
||||||
|
|
||||||
if [[ ! -e "$oldDir" ]]; then
|
# Skip cleanup if there's no previous generation
|
||||||
|
if [[ -z "$oldDir" || ! -d "$oldDir" ]]; then
|
||||||
|
verboseEcho "LaunchAgents setup complete"
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
find -L "$oldDir" -maxdepth 1 -name '*.plist' -type f -print0 \
|
verboseEcho "Cleaning up removed LaunchAgents..."
|
||||||
| while IFS= read -rd "" srcPath; do
|
find -L "$oldDir" -maxdepth 1 -name '*.plist' -type f | while read -r srcPath; do
|
||||||
agentFile="''${srcPath##*/}"
|
removeAgent "$srcPath" "$dstDir" "$newDir" "$domain"
|
||||||
agentName="''${agentFile%.plist}"
|
|
||||||
dstPath="$dstDir/$agentFile"
|
|
||||||
if [[ -e "$newDir/$agentFile" ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
run /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
|
|
||||||
run rm -f $VERBOSE_ARG "$dstPath"
|
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
setupLaunchAgents
|
setupLaunchAgents
|
||||||
|
|
||||||
|
# Restore errexit
|
||||||
|
if [[ -o errexit ]]; then
|
||||||
|
set -e
|
||||||
|
fi
|
||||||
'';
|
'';
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue