Skip to content

Instantly share code, notes, and snippets.

@dzogrim
Created February 23, 2026 16:23
Show Gist options
  • Select an option

  • Save dzogrim/22a955dc475da57d5ceeace6b1b11c79 to your computer and use it in GitHub Desktop.

Select an option

Save dzogrim/22a955dc475da57d5ceeace6b1b11c79 to your computer and use it in GitHub Desktop.
MacPorts un/load (launchd) service management
#!/usr/bin/env bash
# macports-services-auto.sh
#
# MacPorts (launchd) service management with:
# - Two inventory modes:
# 1) 'present':
# services actually loadable now (plists present on disk)
# 2) 'known':
# services known to launchd (may include stale/residual entries)
# - An exhaustive union mode (present + known)
# - Auto ON/OFF decision per service + overrides
# - help <name> with:
# - plist present: yes/no
# - port active: yes/no
# - launchd known: yes/no + enabled/disabled
# - reason for the auto decision
#
# IMPORTANT NOTES:
# - `launchd` labels look like: org.macports.<Service>
# - MacPorts port names often differ (e.g. rsyncd -> rsync,
# slapd -> openldap, smartd -> smartmontools)
# - This script applies via "port load/unload" when possible
# (using a mapping), otherwise it guides via overrides.
#
# Safety:
# - Stable output, parses "print-disabled" rather than "print system"
# - Does not modify PATH
# - Does not auto-load risky services (OpenSSH) by default
set -euo pipefail
# --------------------------------------------------------------------
# User configuration
# --------------------------------------------------------------------
# Overrides file (optional). Format: <service>=0|1
# Example:
# OpenSSH=0
# rsyncd=1
OVERRIDES_FILE="${OVERRIDES_FILE:-$HOME/.config/macports/services.conf}"
# Conservative policy: never auto-ON these services (possible conflicts)
DEFAULT_OFF_ALWAYS=("OpenSSH")
# --------------------------------------------------------------------
# Utilities
# --------------------------------------------------------------------
usage() {
cat <<'EOF'
Usage:
macports-services-auto.sh list [present|known|all]
macports-services-auto.sh scan [present|known|all]
macports-services-auto.sh plan [present|known|all]
macports-services-auto.sh apply [present|known|all]
macports-services-auto.sh help <name>
macports-services-auto.sh load <name>
macports-services-auto.sh unload <name>
Modes:
present: services with a plist present in /Library/LaunchDaemons or /Library/LaunchAgents
known : services known to launchd (launchctl print-disabled system)
all : union of present + known (exhaustive)
Commands:
list : list services for the chosen mode
scan : print the ON/OFF decision (auto + overrides) for the chosen mode
help : per-service details (plist present, port active, launchd state, reason)
plan : print LOAD/UNLOAD actions for the decision
apply: perform LOAD/UNLOAD actions for the decision
EOF
}
# Remove NULs and non-printables often seen in some launchctl outputs
strip_nuls() {
# This is usually sufficient:
tr -d '\000'
}
# Convert a launchd label org.macports.X into "X"
label_to_service() {
local label="$1"
echo "$label" | sed -E 's/^org\.macports\.//'
}
# Convert "X" into launchd label org.macports.X
service_to_label() {
local s="$1"
echo "org.macports.$s"
}
# --------------------------------------------------------------------
# Inventories (present/known/all)
# --------------------------------------------------------------------
# List services whose plist exists (actually loadable now)
# Returns "Service" names (without org.macports.)
present_services() {
{
ls -1 /Library/LaunchDaemons/org.macports.*.plist 2>/dev/null \
| sed -E 's|.*/org\.macports\.([^.]+)\.plist|\1|'
ls -1 /Library/LaunchAgents/org.macports.*.plist 2>/dev/null \
| sed -E 's|.*/org\.macports\.([^.]+)\.plist|\1|'
} | sort -u
}
# List org.macports.* labels known to launchd, with enabled/disabled state
# Format: <label>\t<enabled|disabled>
known_labels_with_state() {
sudo launchctl print-disabled system 2>/dev/null \
| strip_nuls \
| sed -n 's/.*"\(org\.macports\.[^"]*\)".*=> *\(enabled\|disabled\).*/\1\t\2/p' \
| sort -u
}
# List services known to launchd (may include stale entries)
known_services() {
known_labels_with_state | cut -f1 | while IFS= read -r label; do
label_to_service "$label"
done | sort -u
}
# Union (exhaustive)
all_services() {
{ present_services; known_services; } | sort -u
}
# Select inventory by mode
services_for_mode() {
local mode="${1:-present}"
case "$mode" in
present) present_services ;;
known) known_services ;;
all) all_services ;;
*)
echo "Unknown mode: $mode (expected: present|known|all)" >&2
exit 2
;;
esac
}
# --------------------------------------------------------------------
# Detection: plist present? / port active? / launchd state?
# --------------------------------------------------------------------
plist_present() {
local s="$1"
[[ -e "/Library/LaunchDaemons/org.macports.${s}.plist" ]] && return 0
[[ -e "/Library/LaunchAgents/org.macports.${s}.plist" ]] && return 0
return 1
}
# launchd known + enabled/disabled
# Output: "yes enabled" / "yes disabled" / "no -"
launchd_state() {
local s="$1"
local label
label="$(service_to_label "$s")"
local line
line="$(known_labels_with_state | awk -F'\t' -v l="$label" '$1==l {print $0}' || true)"
if [[ -n "$line" ]]; then
echo "yes $(echo "$line" | cut -f2)"
else
echo "no -"
fi
}
# Mapping service -> MacPorts port name (when service name differs from port name)
# Adjusted for the observed cases:
# - Anacron -> anacron
# - OpenSSH -> openssh
# - rsyncd -> rsync
# - slapd -> openldap
# - smartd -> smartmontools
# - shared-mime-info-updater -> shared-mime-info
service_to_portname() {
local s="$1"
case "$s" in
Anacron) echo "anacron" ;;
OpenSSH) echo "openssh" ;;
rsyncd) echo "rsync" ;;
slapd) echo "openldap" ;;
smartd) echo "smartmontools" ;;
shared-mime-info-updater) echo "shared-mime-info" ;;
*)
# Default: attempt same name
echo "$s"
;;
esac
}
# True if a port is installed and active
port_active() {
local p="$1"
port installed "$p" 2>/dev/null | grep -qE '\(active\)'
}
# True if the port corresponding to a service is active (via mapping)
service_port_active() {
local s="$1"
local p
p="$(service_to_portname "$s")"
port_active "$p"
}
# --------------------------------------------------------------------
# Overrides (force ON/OFF)
# --------------------------------------------------------------------
declare -A OVERRIDE=()
load_overrides() {
[[ -r "$OVERRIDES_FILE" ]] || return 0
while IFS= read -r line; do
[[ -z "$line" ]] && continue
[[ "$line" =~ ^[[:space:]]*# ]] && continue
if [[ "$line" =~ ^([a-zA-Z0-9._+-]+)=(0|1)[[:space:]]*$ ]]; then
OVERRIDE["${BASH_REMATCH[1]}"]="${BASH_REMATCH[2]}"
fi
done < "$OVERRIDES_FILE"
}
# --------------------------------------------------------------------
# Auto heuristics
# --------------------------------------------------------------------
# Auto decision: returns 0/1
auto_want() {
local s="$1"
# Always-off
for x in "${DEFAULT_OFF_ALWAYS[@]}"; do
[[ "$s" == "$x" ]] && { echo 0; return; }
done
case "$s" in
# dbus (if ever present): useful if virt-manager/libvirt are active
dbus)
if port_active virt-manager || port_active libvirt; then echo 1; else echo 0; fi
;;
# avahi (if ever present): only if avahi port is active
avahi)
if port_active avahi; then echo 1; else echo 0; fi
;;
# rsyncd: ON only if rsync is active + rsyncd config exists
rsyncd)
if port_active rsync && [[ -f /opt/local/etc/rsyncd.conf ]]; then echo 1; else echo 0; fi
;;
# smartd: ON only if smartmontools is active + smartd config exists
smartd)
if port_active smartmontools && [[ -f /opt/local/etc/smartd.conf ]]; then echo 1; else echo 0; fi
;;
# Anacron: ON if anacron active + spool present (simple signal)
Anacron)
if port_active anacron && [[ -d /opt/local/var/spool/anacron ]]; then echo 1; else echo 0; fi
;;
# slapd: ON if openldap is active (daemon decision remains policy-driven)
slapd)
if port_active openldap; then echo 1; else echo 0; fi
;;
# shared-mime-info-updater: often auto-managed by MacPorts
# Keep ON if the port is active
shared-mime-info-updater)
if port_active shared-mime-info; then echo 1; else echo 0; fi
;;
*)
# Default: ON if mapped port is active, else OFF
if service_port_active "$s"; then echo 1; else echo 0; fi
;;
esac
}
decision_for() {
local s="$1"
if [[ -n "${OVERRIDE[$s]+x}" ]]; then
echo "${OVERRIDE[$s]}"
else
auto_want "$s"
fi
}
# Explain the decision (help)
explain_want() {
local s="$1"
if [[ -n "${OVERRIDE[$s]+x}" ]]; then
echo "$s=${OVERRIDE[$s]} (override via $OVERRIDES_FILE)"
return 0
fi
for x in "${DEFAULT_OFF_ALWAYS[@]}"; do
if [[ "$s" == "$x" ]]; then
echo "$s=0 (policy: never auto-load by default, potential macOS conflict)"
return 0
fi
done
case "$s" in
rsyncd)
if port_active rsync && [[ -f /opt/local/etc/rsyncd.conf ]]; then
echo "$s=1 (rsync active + /opt/local/etc/rsyncd.conf present)"
elif port_active rsync; then
echo "$s=0 (rsync active but /opt/local/etc/rsyncd.conf missing)"
else
echo "$s=0 (rsync port not active)"
fi
;;
smartd)
if port_active smartmontools && [[ -f /opt/local/etc/smartd.conf ]]; then
echo "$s=1 (smartmontools active + /opt/local/etc/smartd.conf present)"
elif port_active smartmontools; then
echo "$s=0 (smartmontools active but /opt/local/etc/smartd.conf missing)"
else
echo "$s=0 (smartmontools port not active)"
fi
;;
Anacron)
if port_active anacron && [[ -d /opt/local/var/spool/anacron ]]; then
echo "$s=1 (anacron active + spool present)"
elif port_active anacron; then
echo "$s=0 (anacron active but spool missing)"
else
echo "$s=0 (anacron port not active)"
fi
;;
slapd)
if port_active openldap; then
echo "$s=1 (openldap active)"
else
echo "$s=0 (openldap port not active)"
fi
;;
shared-mime-info-updater)
if port_active shared-mime-info; then
echo "$s=1 (shared-mime-info active, often auto-managed)"
else
echo "$s=0 (shared-mime-info port not active)"
fi
;;
*)
if service_port_active "$s"; then
echo "$s=1 (default rule: port '$(service_to_portname "$s")' active)"
else
echo "$s=0 (default rule: port '$(service_to_portname "$s")' not active)"
fi
;;
esac
}
# --------------------------------------------------------------------
# Actions load/unload
# --------------------------------------------------------------------
do_load() {
local s="$1"
local p
p="$(service_to_portname "$s")"
echo "+ sudo port load $p"
sudo port load "$p"
}
do_unload() {
local s="$1"
local p
p="$(service_to_portname "$s")"
echo "+ sudo port unload $p"
sudo port unload "$p" || true
}
# --------------------------------------------------------------------
# help <name>
# --------------------------------------------------------------------
port_info_field() {
# Extract a single field from `port info <port>`.
#
# Works with both:
# - `port info <port>` (pretty, multi-line)
# - `port info --line <port>` (tab-separated, may contain NUL bytes)
#
# Example:
# port_info_field openssh "Description"
local portname="$1"
local field="$2"
# Prefer --pretty output because it is stable and does not contain NULs.
# It also has explicit "Field:" prefixes that are easy to parse.
port info --pretty --index "$portname" 2>/dev/null \
| awk -v f="$field" '
BEGIN {found=0}
$0 ~ ("^"f":[[:space:]]*") {
sub("^"f":[[:space:]]*", "", $0)
print
found=1
exit
}
END { if (!found) exit 1 }
'
}
help_one() {
local s="${1:-}"
[[ -n "$s" ]] || { echo "Usage: $0 help <name>" >&2; return 2; }
local label portname
label="$(service_to_label "$s")"
portname="$(service_to_portname "$s")"
echo "Service: $s"
echo "launchd label: $label"
echo "MacPorts port (mapping): $portname"
echo
if plist_present "$s"; then
echo "plist present: yes"
[[ -e "/Library/LaunchDaemons/org.macports.${s}.plist" ]] && echo " - /Library/LaunchDaemons/org.macports.${s}.plist"
[[ -e "/Library/LaunchAgents/org.macports.${s}.plist" ]] && echo " - /Library/LaunchAgents/org.macports.${s}.plist"
else
echo "plist present: no"
fi
if port_active "$portname"; then
echo "port active: yes"
else
echo "port active: no"
fi
echo
echo "Port info:"
if port info "$portname" >/dev/null 2>&1; then
# Single-line description
desc="$(port_info_field "$portname" "Description" || true)"
[[ -n "${desc:-}" ]] && echo "Description: $desc"
# Optional: useful extra fields
home="$(port_info_field "$portname" "Homepage" || true)"
[[ -n "${home:-}" ]] && echo "Homepage: $home"
vers="$(port_info_field "$portname" "$portname" || true)"
# (Not always present as a field; typically the first line includes name + version)
first_line="$(port info "$portname" 2>/dev/null | head -n 1 || true)"
[[ -n "${first_line:-}" ]] && echo "Summary: $first_line"
variants="$(port_info_field "$portname" "Variants" || true)"
[[ -n "${variants:-}" ]] && echo "Variants: $variants"
subports="$(port_info_field "$portname" "Sub-ports" || true)"
[[ -n "${subports:-}" ]] && echo "Sub-ports: $subports"
else
echo "Port info: unavailable (port not found in current MacPorts tree)"
fi
local ls_state
ls_state="$(launchd_state "$s")"
echo "launchd known: $(echo "$ls_state" | awk '{print $1}')"
echo "launchd state: $(echo "$ls_state" | awk '{print $2}')"
echo
echo "Decision:"
explain_want "$s"
echo
echo "Force ON/OFF (override):"
echo " mkdir -p \"$(dirname "$OVERRIDES_FILE")\""
echo " echo \"$s=1\" >> \"$OVERRIDES_FILE\" # force ON"
echo " echo \"$s=0\" >> \"$OVERRIDES_FILE\" # force OFF"
}
# --------------------------------------------------------------------
# Main
# --------------------------------------------------------------------
load_overrides
cmd="${1:-}"; shift || true
mode="present"
if [[ "${1:-}" =~ ^(present|known|all)$ ]]; then
mode="$1"
shift || true
fi
case "$cmd" in
list)
services_for_mode "$mode"
;;
scan)
while IFS= read -r s; do
printf '%s=%s\n' "$s" "$(decision_for "$s")"
done < <(services_for_mode "$mode")
[[ -r "$OVERRIDES_FILE" ]] && echo "# overrides: $OVERRIDES_FILE" >&2
;;
plan)
while IFS= read -r s; do
if [[ "$(decision_for "$s")" == "1" ]]; then
echo "LOAD $s"
else
echo "UNLOAD $s"
fi
done < <(services_for_mode "$mode")
;;
apply)
while IFS= read -r s; do
if [[ "$(decision_for "$s")" == "1" ]]; then
do_load "$s"
else
do_unload "$s"
fi
done < <(services_for_mode "$mode")
;;
help)
help_one "${1:?missing name}"
;;
load)
do_load "${1:?missing name}"
;;
unload)
do_unload "${1:?missing name}" || true
;;
""|-h|--help|help-all)
usage
;;
*)
echo "Unknown command: $cmd" >&2
usage
exit 2
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment