6 praktiske LaunchAgents, du kan oprette på Mac
macOS kan meget på egen hånd - men med LaunchAgents kan det hele køre på autopilot.
Forestil dig en Mac, der selv rydder op i rodet i din ~/Downloads, der starter din backup så snart du tilslutter dit eksterne drev, og som aldrig glemmer at opdatere dine programmer eller synkronisere dine Git-projekter
Alt imens du nyder en kop kaffe.
Det lyder som magi, men hemmeligheden hedder LaunchAgents - macOS’ indbyggede jobplanlægger, der kan affyre scripts på klokkeslæt, når et netværk ændrer sig, eller når et bestemt drev dukker op på skrivebordet.
I denne artikel viser vi dig seks konkrete eksempler på, hvordan du med få linjer XML og shell-script kan:
- Automatisere daglige rutiner
- Beskytte dine data uden at løfte en finger
- Holde din Mac ren, opdateret og lynhurtig
Hver opskrift er spækket med tips til RunAtLoad, KeepAlive, StartCalendarInterval og andre nøglefelter, så du hurtigt kan tilpasse dem til dine egne behov. Klar til at tage kontrollen over din Macs bagkulisser? Lad os dykke ned i de praktiske eksempler.
Opryd Downloads-mappen automatisk hver nat
De fleste af os lader ~/Downloads vokse sig uoverskuelig stor. Et lille LaunchAgent kan rydde op hver nat, helt uden manuel indgriben.
1. Shell-scriptet
Gem først et simpelt oprydnings-script, fx ~/Scripts/cleanup-downloads.sh:
#!/bin/zsh# Slet eller arkivér filer ældre end 14 dage i ~/DownloadsTARGET="$HOME/Downloads"ARCHIVE="$HOME/Downloads/Arkiv"# Opret arkiv-mappe hvis den manglermkdir -p "$ARCHIVE"# Flyt i stedet for at slette - skift mv til rm hvis du vil slettefind "$TARGET" -type f -mtime +14 -maxdepth 1 ! -path "$ARCHIVE/*" -exec mv {} "$ARCHIVE" \;# Ryd arkivet efter 90 dagefind "$ARCHIVE" -type f -mtime +90 -deleteGiv scriptet køretilladelse:
chmod +x ~/Scripts/cleanup-downloads.sh2. Launchagent-filen
Opret nu ~/Library/LaunchAgents/dk.itforum.cleanup-downloads.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>dk.itforum.cleanup-downloads</string> <!-- Kør scriptet så snart agenten indlæses (f.eks. efter login) --> <key>RunAtLoad</key> <true/> <!-- Planlagt kørsel hver nat kl. 03:15 --> <key>StartCalendarInterval</key> <dict> <key>Hour</key> <integer>3</integer> <key>Minute</key> <integer>15</integer> </dict> <key>ProgramArguments</key> <array> <string>/bin/zsh</string> <string>-c</string> <string>$HOME/Scripts/cleanup-downloads.sh</string> </array> <!-- Hvor skal output gemmes? --> <key>StandardOutPath</key> <string>$HOME/Library/Logs/cleanup-downloads.log</string> <key>StandardErrorPath</key> <string>$HOME/Library/Logs/cleanup-downloads.err</string></dict></plist>- RunAtLoad sørger for, at agenten også kører med det samme, når du logger ind eller genindlæser den.
-
StartCalendarInterval definerer det daglige job. Du kan i stedet bruge flere nøgler som
Weekdayhvis den kun skal køre på hverdage. - StandardOutPath og StandardErrorPath gemmer loggen - guld værd, når du fejlsøger.
3. Indlæs agenten
launchctl load ~/Library/LaunchAgents/dk.itforum.cleanup-downloads.plistNæste nat - og hver nat fremover - bliver din Downloads-mappe holdt slank. Vil du stoppe den igen, bruger du:
launchctl unload ~/Library/LaunchAgents/dk.itforum.cleanup-downloads.plistTip: Vil du hellere slette direkte, udskift mv med rm -f. Husk at teste med en kopi først!
Tag backup når et eksternt drev monteres
Fordi macOS kun kører Time Machine-backup én gang i timen, kan det være nyttigt at lave en mere målrettet løsning, som straks kører, så snart dit eksterne Backup-drev monteres. Det klarer et LaunchAgent, der vågner på ændringer i /Volumes og - med en PathState-test - kontrollerer, om det ønskede volumen findes, før scriptet skydes af.
1. Forbered backup-scriptet
#!/bin/zsh# ~/scripts/run_rsync_backup.shSRC="$HOME/Documents/"DST="/Volumes/Backup/MacDocs/"# En simpel forsikring imod dobbelte kørslerLOCKFILE="/tmp/rsync-backup.lock"if [[ -e $LOCKFILE ]]; then echo "Backup kører allerede" >&2 exit 0fitouch "$LOCKFILE"# Kør rsync med Time-Machine-lignende flag/usr/bin/rsync -a --delete --human-readable --progress \ --exclude=".DS_Store" "$SRC" "$DST"rm -f "$LOCKFILE"
Giv scriptet køretilladelse:
chmod +x ~/scripts/run_rsync_backup.sh
2. Opret plist-filen
Læg følgende i ~/Library/LaunchAgents/com.itforum.backupdisk.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>com.itforum.backupdisk</string> <key>ProgramArguments</key> <array> <string>/Users/USERNAME/scripts/run_rsync_backup.sh</string> </array> <key>RunAtLoad</key> <true/> <!-- Sørg for at processer ikke stabler sig --> <key>ThrottleInterval</key> <integer>600</integer> <!-- Vågn når /Volumes ændres --> <key>WatchPaths</key> <array> <string>/Volumes</string> </array> <!-- Men kør kun hvis /Volumes/Backup findes --> <key>KeepAlive</key> <dict> <key>PathState</key> <dict> <key>/Volumes/Backup</key> <true/> </dict> </dict> <!-- Logfiler --> <key>StandardOutPath</key> <string>/Users/USERNAME/Library/Logs/backupdisk.out.log</string> <key>StandardErrorPath</key> <string>/Users/USERNAME/Library/Logs/backupdisk.err.log</string></dict></plist>
Vigtige nøgler forklaret
-
WatchPaths: Agenten vækkes hver gang der mountes/afmonteres noget i/Volumes. -
KeepAlive > PathState: Sørger for, at job kun køres, hvis den angivne mappe stadig eksisterer - ellers ignoreres eventet. -
ThrottleInterval: Hindrer gentagne kørsler, hvis drevet hurtigt mountes/afmountes flere gange.
3. Indlæs og test
launchctl load ~/Library/LaunchAgents/com.itforum.backupdisk.plist# Afmontér og montér drevet igen for at testetail -f ~/Library/Logs/backupdisk.out.log
Når alt spiller, vil din Mac automatisk spejle de valgte mapper til det eksterne drev hver gang det dukker op - lige så hurtigt og lydløst som Time Machine, men med fuld kontrol over, hvad der sikkerhedskopieres.
Start og hold liv i vigtige menulinje-scripts ved login
Apple tilbyder Login Items, men de giver ingen garanti for, at en proces kommer op at køre igen, hvis den stopper uventet. Her skinner LaunchAgents, fordi de - med de rette nøgler - både kan starte dit menulinje-script når du logger ind og holde det i live bagefter.
Komplet .plist-eksempel
<?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>dk.itforum.menubar.batterywatch</string> <!-- Kør straks ved login --> <key>RunAtLoad</key> <true/> <!-- Genstart hvis scriptet dør med fejl (SuccessfulExit = false) --> <key>KeepAlive</key> <dict> <key>SuccessfulExit</key> <false/> </dict> <!-- Selve kommandoen --> <key>ProgramArguments</key> <array> <string>/usr/local/bin/python3</string> <string>/Users/<YOUR_USER>/Scripts/batterywatch.py</string> </array> <!-- Sørg for at scriptet finder Homebrew-bin m.m. --> <key>EnvironmentVariables</key> <dict> <key>PATH</key> <string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string> </dict> <!-- Hvorfra vi kalder - nyttigt til relative stier og logfiler --> <key>WorkingDirectory</key> <string>/Users/<YOUR_USER>/Scripts</string> <!-- KUN når en grafisk bruger er logget ind (Aqua) --> <key>LimitLoadToSessionType</key> <string>Aqua</string> <!-- Logfiler gør fejlsøgning nemmere --> <key>StandardOutPath</key> <string>/Users/<YOUR_USER>/Library/Logs/batterywatch.out.log</string> <key>StandardErrorPath</key> <string>/Users/<YOUR_USER>/Library/Logs/batterywatch.err.log</string></dict></plist>Sådan gør du trin for trin
- Kopier filen til
~/Library/LaunchAgents/og giv den et sigende navn, f.eks.dk.itforum.menubar.batterywatch.plist. - Tjek stierne under
ProgramArguments,WorkingDirectoryog logfil-placeringerne, så de passer til din bruger. - Indlæs agenten uden at genstarte macOS:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dk.itforum.menubar.batterywatch.plist - Vil du fjerne eller genindlæse den, bruger du:
launchctl bootout gui/$(id -u) dk.itforum.menubar.batterywatch
launchctl bootstrap ...igen for at genaktivere.
Nøglerne i praksis
| Nøgle | Funktion |
|---|---|
| RunAtLoad | Starter agenten umiddelbart efter den er indlæst - typisk lige efter login. |
| KeepAlive | Overvåger processen. Med SuccessfulExit = false genstartes den ved nedbrud. |
| ProgramArguments | Erstat det gamle Program; giver klar rækkefølge for binær + parametre. |
| EnvironmentVariables (PATH) | Sørger for, at Homebrew-programmer og andre bruger-binære kan findes. |
| WorkingDirectory | Nyttigt til scripts, der forventer relative filer eller skriver logge lokalt. |
| LimitLoadToSessionType = Aqua | Agenten kører kun i en grafisk session - ikke på SSH eller på login-skærmen. |
Tips til fejlsøgning
- Kig først i dine
.out.logog.err.log. - Kør
launchctl print gui/$(id -u)/dk.itforum.menubar.batterywatchfor at se status og seneste exit-kode. - Hvis agenten nægter at starte, kan
plutil -lintafsløre XML-fejl.
Med denne opsætning får du en robust erstatning for de primitive Login Items, og dit menulinje-værktøj lever videre, selv når det får et uventet chok.
Ugentlig vedligeholdelse: opdateringer og oprydning
Et af de mest klassiske “set-and-forget”-scenarier til LaunchAgents er et ugentligt vedligeholdelsesjob, der kører brew update/upgrade, rydder gamle caches og komprimerer ældre logfiler. Nedenfor finder du et komplet eksempel bestående af 1) et shell-script og 2) den tilhørende .plist-fil. Begge placeres i brugerens hjemmemappe, typisk under ~/Scripts og ~/Library/LaunchAgents.
1. Vedligeholdelsesscriptet
#!/bin/zsh## weekly-maintenance.sh - kører hver søndag kl. 03:00#LOG_DIR="$HOME/Library/Logs/weekly-maintenance"mkdir -p "$LOG_DIR"exec >>"$LOG_DIR/run.log" 2>>"$LOG_DIR/error.log"echo "===== $(date) - Starter ugentlig vedligehold ====="# 1) Homebrewif command -v brew >/dev/null; then brew update && brew upgrade && brew cleanupelse echo "Homebrew ikke fundet"fi# 2) Ryd Safari- og systemcaches ældre end 30 dagefind $HOME/Library/Caches -type f -mtime +30 -delete# 3) Komprimér logfiler ældre end 14 dagefind /var/log -type f -mtime +14 -name '*.log' -exec gzip {} \;# 4) Notifikation, hvis fejl blev loggetif [[ -s "$LOG_DIR/error.log" ]]; then osascript -e 'display notification "Se error.log for detaljer" with title "Ugentlig vedligeholdelse: fejl" sound name "Submarine"'fiecho "===== $(date) - Færdig ====="Tip: Vil du minimere påvirkningen af systemet, kan du også prefixe tunge kommandoer med nice -n 10 eller ionice -c2 -n7 (fra brew install iosched), men LaunchAgents giver os tilsvarende muligheder direkte i .plist-filen - se næste afsnit.
2. Launchagent-filen
<?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>dk.itforum.weeklymaintenance</string> <key>ProgramArguments</key> <array> <string>/bin/zsh</string> <string>$HOME/Scripts/weekly-maintenance.sh</string> </array> <!-- Kør hver søndag kl. 03:00 --> <key>StartCalendarInterval</key> <dict> <key>Weekday</key><integer>0</integer> <!-- Søndag --> <key>Hour</key><integer>3</integer> <key>Minute</key><integer>0</integer> </dict> <!-- Kør også ved login, hvis Mac'en har været slukket søndag 03:00 --> <key>RunAtLoad</key><true/> <!-- Giv andre jobs prioritet først --> <key>Nice</key><integer>10</integer> <key>LowPriorityIO</key><true/> <!-- Log til samme mappe som scriptet --> <key>StandardOutPath</key> <string>$HOME/Library/Logs/weekly-maintenance/run.log</string> <key>StandardErrorPath</key> <string>$HOME/Library/Logs/weekly-maintenance/error.log</string> <!-- Afbryd job, hvis det stadig kører efter 6 timer (i sekunder) --> <key>Timeout</key><integer>21600</integer></dict></plist>- RunAtLoad sørger for, at scriptet også startes umiddelbart efter login, så du ikke misser en kørsel, hvis maskinen var slukket på det planlagte tidspunkt.
- Nice og LowPriorityIO sikrer, at CPU- og diskadgang foregår i baggrunden, så du sjældent mærker jobbet køre.
-
Timeout er valgfrit, men nyttigt, hvis et
brew upgradeeller lignende hænger.
Installation
- Gør scriptet eksekverbart:
chmod +x ~/Scripts/weekly-maintenance.sh - Indlæs agenten:
launchctl load -w ~/Library/LaunchAgents/dk.itforum.weeklymaintenance.plist - Tjek status:
launchctl list | grep weeklymaintenance
Skulle du nogensinde ønske at deaktivere jobbet igen, benytter du blot launchctl unload.
Med denne enkle opsætning får du en automatisk, skånsom og logført vedligeholdelse af din Mac - helt uden tredjeparts “cleanup-apps”.
Reager på netværksskift: flush DNS og netværksprofiler
Når du hopper mellem Wi-Fi på kontoret, mobil hotspot og hjemmenetværket, vil du ofte have forskellige krav til DNS-opsætning, proxy eller VPN. Med et LaunchAgent kan macOS automatisk reagere på hvert netværksskift - helt uden menuklik.
1. Hvorfor keepalive > networkstate?
Nøglen KeepAlive har flere undernøgler; sætter du NetworkState = true genstarter launchd dit job hver gang:
- macOS får ny IP-adresse
- interface går fra offline → online (eller omvendt)
- du skifter SSID
Det er perfekt til at køre et ”on-network-change”-script uden at rode med timers eller polling.
2. Eksempel: Flush-network.profiles.sh
Gem et simpelt shell-script i ~/Scripts/flush-network.profiles.sh og gør det eksekverbart (chmod +x):
#!/bin/zshLOGFILE="$HOME/Library/Logs/flush-network.log"echo "$(date '+%F %T') - netværksskift opdaget" >> "$LOGFILE"# 1) Flush DNSdscacheutil -flushcachesudo killall -HUP mDNSResponder 2>/dev/null# 2) Bestem aktivt SSIDSSID="$(/System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I | \ awk -F': ' '/ SSID/ {print $2}')"case "$SSID" in"HjemmeNet")# Ingen proxy, men kør lokal DNS-resolvernetworksetup -setwebproxy Wi-Fi "" 0networksetup -setdnsservers Wi-Fi 100.100.100.100 1.1.1.1;;"FirmaNet")# Aktivér proxy og split-tunnel VPN-profilnetworksetup -setwebproxy Wi-Fi proxy.firma.dk 8080osascript -e 'tell application "Tunnelblick" to connect "Work VPN"';;*)# Fallbacknetworksetup -setdnsservers Wi-Fi "Empty";;esacecho "$(date '+%F %T') - færdig for SSID: ${SSID}" >> "$LOGFILE"Tip: Skal scriptet udføre sudo-kommandoer (fx kill af mDNSResponder), så giv den specifikke bruger NOPASSWD-rettigheder i /etc/sudoers.d/ på de pågældende kommandoer.
3. Launchagent-plisten
Læg nedenstående i ~/Library/LaunchAgents/dk.itforum.flushnetwork.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>dk.itforum.flushnetwork</string><key>ProgramArguments</key><array><string>/Users/<!-- DIT-BRUGERNAVN -->/Scripts/flush-network.profiles.sh</string></array><key>RunAtLoad</key><true/><key>KeepAlive</key><dict><key>NetworkState</key><true/></dict><key>StandardOutPath</key><string>~/Library/Logs/flush-network.out</string><key>StandardErrorPath</key><string>~/Library/Logs/flush-network.err</string><key>EnvironmentVariables</key><dict><key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string></dict><key>ThrottleInterval</key><integer>30</integer> <!-- min. 30 sek. mellem genstart --></dict></plist>Indlæs med:
launchctl load -w ~/Library/LaunchAgents/dk.itforum.flushnetwork.plist4. Hvornår skal du bruge en launchdaemon?
LaunchAgents kører i brugerens kontekst (efter login) og har de samme rettigheder som brugeren. Det er fint til:
- brugerspecifikke DNS- eller proxyændringer
- VPN-klienter der kører i menulinjen
Skal jobbet derimod:
- ændre system-wide netværksplaceringer
- redigere
/etc/hostseller andre root-krævende filer - starte dæmoner før nogen logger ind (servermiljø)
… så skal du oprette en identisk plist i /Library/LaunchDaemons og sørge for root-ejerskab & rettigheder (644). Husk, at miljøvariabler er mere begrænsede i daemons, så brug fulde stier i scripts.
5. Afprøvning og fejlfinding
-
log stream --predicate 'process == "dk.itforum.flushnetwork"' --infoviser real-time-log. -
launchctl kickstart -k gui/$(id -u)/dk.itforum.flushnetworksimulerer et netværksskift. - Tjek
StandardErrorPath-filen for shell-fejl, især hvissudomislykkes.
Med den opsætning får du en Mac, der automatisk flusher DNS, retter proxy og kobler VPN til det rigtige netværk - helt transparent og uden at spilde din tid i Systemindstillinger.
Synkronisér Git-projekter automatisk i baggrunden
Automatisk synkronisering af dine Git-projekter kan spare dig for både tid og glemte git pulls. Her viser vi, hvordan du med en enkelt .plist og et lille shell-script kan lade macOS gøre arbejdet for dig - uden risiko for overlappende jobs eller ødelagte merge-konflikter.
1. Shell-scriptet: ~/scripts/autogit.sh
#!/usr/bin/env bashset -euo pipefail# --- konfig ---BASE_DIR="$HOME/Projects" # Rodmappen med repoerLOGFILE="$HOME/Library/Logs/autogit-run.log"LOCKFILE="/tmp/autogit.lock"REMOTE="origin"BRANCH="main"TIMEOUT=15 # sekunder til ping# ---------------# simpelt lås - forhindrer overlappende kørselexec 200>"$LOCKFILE"flock -n 200 || { echo "Job overlappede, exit." >> "$LOGFILE"; exit 0; }# er vi online?ping -t "$TIMEOUT" -c 1 github.com >/dev/null 2>&1 \ || { echo "$(date) Offline, springer over." >> "$LOGFILE"; exit 0; }echo "$(date) --- Autogit start ---" >> "$LOGFILE"for repo in "$BASE_DIR"/*/.git; do REPO_DIR="${repo%/.git}" ( cd "$REPO_DIR" # undgå konflikter: kun fast-forward git fetch "$REMOTE" && git merge --ff-only "$REMOTE/$BRANCH" ) >> "$LOGFILE" 2>&1doneecho "$(date) --- Autogit slut ---" >> "$LOGFILE"-
Låsning:
flocksikrer, at næste job ikke starter før det forrige er færdigt. -
Offline-håndtering: Simpelt
pingtjekker forbindelsen; du kan udskifte medscutil --ncfor VPN eller mere avanceret logik. - Konflikt-forebyggelse: Vi henter og laver kun en fast-forward merge. Hvis der er lokale commits, springer scriptet automatisk konflikten over.
2. Launchagent: ~/library/launchagents/dk.itforum.autogit.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>dk.itforum.autogit</string> <key>ProgramArguments</key> <array> <string>/usr/bin/env</string> <string>bash</string> <string>-c</string> <string>$HOME/scripts/autogit.sh</string> </array> <key>WorkingDirectory</key> <string>$HOME/Projects</string> <key>StartInterval</key> <integer>600</integer> <!-- hvert 10. minut --> <key>StandardOutPath</key> <string>$HOME/Library/Logs/autogit.log</string> <key>StandardErrorPath</key> <string>$HOME/Library/Logs/autogit.err</string> <key>Nice</key> <!-- lav CPU-prioritet --> <integer>10</integer> <key>KeepAlive</key><false/> <!-- kun tidsbaseret --></dict></plist>- Kopier filen til
~/Library/LaunchAgents. - Indlæs med
launchctl load ~/Library/LaunchAgents/dk.itforum.autogit.plist. - Se loggen vokse i Console.app ▸ ~/Library/Logs/.
3. Justeringer og tip
-
Interval: Har du store repos, sæt
StartIntervalhøjere (fx 3600 for hver time). -
Flere mapper: Tilføj flere
BASE_DIR-ruter eller lav énautogit.shpr. mappe. -
Credential-løser: Brug
ssh-agenteller Git-credential-helper for at undgå password-prompts. -
Fejlnotifikation: Tilføj
osascript -e 'display notification ...'i scriptet, hvis en merge fejler. - Sovende Mac: macOS køer LaunchAgents, så jobbet kører straks maskinen vækkes, hvis intervallet blev misset.
Med denne opsætning kører dine projekter nu helt automatisk opdatering i baggrunden - uden at du mister kontrol over, hvad der bliver flettet.
Indholdsfortegnelse
- Opryd Downloads-mappen automatisk hver nat
- Tag backup når et eksternt drev monteres
- Start og hold liv i vigtige menulinje-scripts ved login
- Ugentlig vedligeholdelse: opdateringer og oprydning
- Reager på netværksskift: flush DNS og netværksprofiler
- 1. Hvorfor keepalive > networkstate?
- 2. Eksempel: Flush-network.profiles.sh
- 3. Launchagent-plisten
- 4. Hvornår skal du bruge en launchdaemon?
- 5. Afprøvning og fejlfinding
- Synkronisér Git-projekter automatisk i baggrunden