Created
February 23, 2026 16:23
-
-
Save dzogrim/22a955dc475da57d5ceeace6b1b11c79 to your computer and use it in GitHub Desktop.
MacPorts un/load (launchd) service management
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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) | |
| 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