scripts/eitri-agent-launchagent.sh
Ref: Size: 4.4 KiB History
#!/usr/bin/env bash
#
# Install this Mac's eitri-agent as a LaunchAgent, so it starts with the
# session and is restarted if it crashes. The macOS counterpart of
# eitri-agent.service.
#
# ./eitri-agent-launchagent.sh install [/path/to/eitri-agent]
# ./eitri-agent-launchagent.sh uninstall
# ./eitri-agent-launchagent.sh status
#
# No privileges anywhere. A Mac runs guests through vfkit, which needs none, so
# the agent needs none either — and the two things every agent does need, a
# state directory it can create and a binary it can replace when it upgrades,
# are both satisfied by keeping them in the running account's own space.
#
# A LaunchAgent and not a LaunchDaemon, because a Mac host is almost always
# somebody's Mac. An agent runs in the login session, which is where the person
# using that machine already is. A dedicated always-on host that must run guests
# with nobody logged in wants a LaunchDaemon instead — same plist, moved to
# /Library/LaunchDaemons with a UserName key naming this account, and the
# binary and state left exactly where they are here.
#
# A generated plist rather than a shipped one: launchd does not expand ~ or any
# variable in ProgramArguments, so every path has to be absolute and resolved on
# the machine it will run on.
set -euo pipefail
LABEL="sh.eitri.agent"
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
LOG_DIR="$HOME/Library/Logs"
DOMAIN="gui/$(id -u)"
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
cmd_install() {
local bin="${1:-}"
if [[ -z $bin ]]; then
bin="$(command -v eitri-agent || true)"
[[ -n $bin ]] || die "eitri-agent is not on PATH — pass its path: $0 install /path/to/eitri-agent"
fi
bin="$(cd "$(dirname "$bin")" && pwd)/$(basename "$bin")"
[[ -x $bin ]] || die "not executable: $bin"
# The agent replaces this file when it upgrades itself, so the account
# running it has to own it. A root-owned prefix like /usr/local/bin looks
# tidy and quietly disables every future upgrade.
[[ -w $bin ]] || die "$bin is not writable by $(id -un) — self-upgrade would fail. Keep the binary somewhere this account owns."
command -v vfkit >/dev/null || printf 'warning: vfkit is not on PATH; guests will fail Preflight until `brew install vfkit`\n' >&2
mkdir -p "$(dirname "$PLIST")" "$LOG_DIR"
cat > "$PLIST" <<-PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>$LABEL</string>
<key>ProgramArguments</key>
<array>
<string>$bin</string>
</array>
<key>RunAtLoad</key>
<true/>
<!-- Restart a crash, but let a clean stop be a stop: the agent exits 0
on SIGTERM, so bootout stays a stop rather than a fight. -->
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>ThrottleInterval</key>
<integer>5</integer>
<!-- Homebrew's directory is absent from a launchd job's default PATH,
and vfkit lives there. Without this the agent starts and then
refuses every create, correctly but avoidably. -->
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
</dict>
<key>StandardOutPath</key>
<string>$LOG_DIR/eitri-agent.log</string>
<key>StandardErrorPath</key>
<string>$LOG_DIR/eitri-agent.log</string>
</dict>
</plist>
PLIST
plutil -lint "$PLIST" >/dev/null || die "generated plist is malformed: $PLIST"
# bootout first so install is idempotent; ignore "not loaded".
launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null || true
launchctl bootstrap "$DOMAIN" "$PLIST"
printf 'installed %s\n binary %s\n log %s/eitri-agent.log\n' "$PLIST" "$bin" "$LOG_DIR"
cmd_status
}
cmd_uninstall() {
# bootout, NOT `unload -w`: the -w flag writes to the persistent override
# database and leaves the label DISABLED, so every later load fails with an
# error that says nothing about why.
launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null || true
rm -f "$PLIST"
printf 'removed %s (state and binary left alone)\n' "$PLIST"
}
cmd_status() {
launchctl print "$DOMAIN/$LABEL" 2>/dev/null | grep -E '^\s+(state|pid|last exit code) ' ||
printf 'not loaded\n'
}
case "${1:-}" in
install) shift; cmd_install "$@" ;;
uninstall) cmd_uninstall ;;
status) cmd_status ;;
*) sed -n '3,10p' "$0" | sed 's/^# \{0,1\}//'; exit 1 ;;
esac