mirror of
https://github.com/nix-community/home-manager.git
synced 2025-03-31 04:04:32 +00: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
|
||||
# because it needs to be owned by the user running it.
|
||||
home.activation.setupLaunchAgents =
|
||||
lib.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=""
|
||||
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() {
|
||||
local oldDir newDir dstDir domain
|
||||
|
||||
newDir="$(readlink -m "$newGenPath/LaunchAgents")"
|
||||
dstDir=${lib.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
|
||||
if [[ -n "''${oldGenPath:-}" ]]; then
|
||||
oldDir="$(readlink -m "$oldGenPath/LaunchAgents")"
|
||||
if [[ ! -d "$oldDir" ]]; then
|
||||
verboseEcho "No previous LaunchAgents directory found"
|
||||
oldDir=""
|
||||
fi
|
||||
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"
|
||||
else
|
||||
oldDir=""
|
||||
fi
|
||||
|
||||
verboseEcho "Setting up LaunchAgents in $dstDir"
|
||||
[[ -d "$dstDir" ]] || run mkdir -p "$dstDir"
|
||||
|
||||
verboseEcho "Processing new/updated LaunchAgents..."
|
||||
find -L "$newDir" -maxdepth 1 -name '*.plist' -type f | while read -r srcPath; do
|
||||
processAgent "$srcPath" "$dstDir" "$domain"
|
||||
done
|
||||
|
||||
if [[ ! -e "$oldDir" ]]; then
|
||||
# Skip cleanup if there's no previous generation
|
||||
if [[ -z "$oldDir" || ! -d "$oldDir" ]]; then
|
||||
verboseEcho "LaunchAgents setup complete"
|
||||
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
|
||||
|
||||
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"
|
||||
verboseEcho "Cleaning up removed LaunchAgents..."
|
||||
find -L "$oldDir" -maxdepth 1 -name '*.plist' -type f | while read -r srcPath; do
|
||||
removeAgent "$srcPath" "$dstDir" "$newDir" "$domain"
|
||||
done
|
||||
}
|
||||
|
||||
setupLaunchAgents
|
||||
|
||||
# Restore errexit
|
||||
if [[ -o errexit ]]; then
|
||||
set -e
|
||||
fi
|
||||
'';
|
||||
})
|
||||
];
|
||||
|
|
Loading…
Add table
Reference in a new issue