Skip to content

Instantly share code, notes, and snippets.

@dzogrim
Last active September 26, 2026 08:37
Show Gist options
  • Select an option

  • Save dzogrim/4a5eaac370bdae8fca30f690e5d947ad to your computer and use it in GitHub Desktop.

Select an option

Save dzogrim/4a5eaac370bdae8fca30f690e5d947ad to your computer and use it in GitHub Desktop.
Standalone macOS settings helper for nix-plist-manager
#!/usr/bin/env bash
# shellcheck shell=bash
#
# ==============================================================================
# nix-plist-manager-mac
# ==============================================================================
#
# Description: Standalone macOS settings helper for nix-plist-manager.
#
# Provides a simple operational interface to:
#
# capture - Capture current macOS settings into mac.nix
# compare - Compare current macOS settings against stored mac.nix
# apply - Apply the stored configuration through Home Manager
# status - Check environment and configuration status
#
# Configuration:
# Nix directory : $HOME/.config/nix
# Settings file : $HOME/.config/nix/mac.nix
#
# Safety:
# - macOS-only execution
# - Checks Nix availability
# - Validates required configuration files
# - Creates timestamped backups before replacing mac.nix
# - Cleans temporary resources automatically
# - Uses explicit exit codes
# - Disables colours automatically when stdout is not a TTY
#
# Usage:
# nix-plist-manager-mac capture
# nix-plist-manager-mac compare
# nix-plist-manager-mac apply
# nix-plist-manager-mac status
# nix-plist-manager-mac help
#
# ==============================================================================
set -uo pipefail
readonly VERSION="0.1.1"
readonly NIX_DIR="${HOME}/.config/nix"
readonly MAC_NIX="${NIX_DIR}/mac.nix"
readonly NPM_FLAKE="github:SushyDev/nix-plist-manager#current"
# Temporary workspace used by commands requiring transient files.
# Kept global so the EXIT trap can safely access it with nounset enabled.
TEMP_DIR=""
# ==============================================================================
# Terminal formatting
# ==============================================================================
RESET=""
BOLD=""
DIM=""
RED=""
GREEN=""
YELLOW=""
BLUE=""
CYAN=""
init_terminal()
{
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
RESET="$(tput sgr0 2>/dev/null || true)"
BOLD="$(tput bold 2>/dev/null || true)"
DIM="$(tput dim 2>/dev/null || true)"
RED="$(tput setaf 1 2>/dev/null || true)"
GREEN="$(tput setaf 2 2>/dev/null || true)"
YELLOW="$(tput setaf 3 2>/dev/null || true)"
BLUE="$(tput setaf 4 2>/dev/null || true)"
CYAN="$(tput setaf 6 2>/dev/null || true)"
fi
}
# ==============================================================================
# Output helpers
# ==============================================================================
info()
{
printf '%s::%s %s\n' \
"${BLUE}${BOLD}" "${RESET}" "$*"
}
success()
{
printf '%s✔%s %s\n' \
"${GREEN}${BOLD}" "${RESET}" "$*"
}
warning()
{
printf '%s!%s %s\n' \
"${YELLOW}${BOLD}" "${RESET}" "$*" >&2
}
error()
{
printf '%s✘%s %s\n' \
"${RED}${BOLD}" "${RESET}" "$*" >&2
}
header()
{
printf '\n%s%s%s %sv%s%s\n' \
"${BOLD}${CYAN}" \
"nix-plist-manager-mac" \
"${RESET}" \
"${DIM}" \
"${VERSION}" \
"${RESET}"
printf '%s%s%s\n\n' \
"${DIM}" \
"Capture, compare and restore declarative macOS preferences." \
"${RESET}"
}
# ==============================================================================
# Cleanup
# ==============================================================================
cleanup()
{
# TEMP_DIR is intentionally global so this function remains safe when
# invoked by the EXIT trap after command-local variables are out of scope.
if [[ -n "${TEMP_DIR:-}" && -d "${TEMP_DIR}" ]]; then
rm -rf -- "${TEMP_DIR}"
fi
TEMP_DIR=""
}
# Static trap avoids variable expansion at registration time and therefore
# remains ShellCheck-safe (SC2064).
trap cleanup EXIT
# ==============================================================================
# Environment validation
# ==============================================================================
check_macos()
{
local os
os="$(uname -s 2>/dev/null || true)"
if [[ "${os}" != "Darwin" ]]; then
error "Unsupported operating system: ${os:-unknown}"
printf 'This utility can only run on macOS.\n' >&2
return 1
fi
}
check_nix()
{
if ! command -v nix >/dev/null 2>&1; then
error "Nix is not available in PATH."
printf '\n'
printf 'A working Nix installation is required.\n'
printf 'Check your Nix installation and shell environment.\n'
return 1
fi
}
check_mac_nix()
{
if [[ ! -f "${MAC_NIX}" ]]; then
error "Configuration file not found:"
printf ' %s\n\n' "${MAC_NIX}"
printf 'Create it first with:\n'
printf ' nix-plist-manager-mac capture\n'
return 1
fi
}
preflight()
{
check_macos || return 1
check_nix || return 1
}
# ==============================================================================
# Capture
# ==============================================================================
cmd_capture()
{
local backup
preflight || return 1
header
info "Capturing current macOS settings"
printf 'Destination: %s\n\n' "${MAC_NIX}"
if ! mkdir -p "${NIX_DIR}"; then
error "Unable to create configuration directory:"
printf ' %s\n' "${NIX_DIR}" >&2
return 1
fi
# Preserve the previous known-good configuration before overwriting it.
if [[ -f "${MAC_NIX}" ]]; then
backup="${MAC_NIX}.$(date '+%Y%m%d-%H%M%S').bak"
if ! cp -p "${MAC_NIX}" "${backup}"; then
error "Unable to create backup. Capture aborted."
return 1
fi
success "Previous configuration backed up"
printf ' %s\n\n' "${backup}"
fi
if nix run "${NPM_FLAKE}" -- \
"${MAC_NIX}" \
--scope user
then
printf '\n'
success "Capture completed"
printf ' %s\n' "${MAC_NIX}"
else
printf '\n'
error "Capture failed."
return 1
fi
}
# ==============================================================================
# Compare
# ==============================================================================
cmd_compare()
{
local current_nix
local diff_file
local rc
preflight || return 1
check_mac_nix || return 1
header
info "Comparing stored configuration against current macOS settings"
printf 'Stored configuration:\n'
printf ' %s\n\n' "${MAC_NIX}"
# --------------------------------------------------------------------------
# Create an isolated temporary workspace.
#
# We deliberately do NOT use nix-plist-manager --against here.
# Instead, we capture the current machine state into a temporary mac.nix
# and perform a standard local diff against the stored configuration.
#
# This makes the comparison deterministic and completely transparent:
#
# stored mac.nix <── diff ──> freshly captured mac.nix
# --------------------------------------------------------------------------
if ! TEMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/nix-plist-manager.XXXXXX")"; then
error "Unable to create temporary directory."
return 1
fi
current_nix="${TEMP_DIR}/mac-current.nix"
diff_file="${TEMP_DIR}/mac.diff"
info "Capturing current macOS settings into temporary workspace"
if ! nix run "${NPM_FLAKE}" -- \
"${current_nix}" \
--scope user \
>/dev/null
then
error "Unable to capture current macOS settings."
return 1
fi
success "Current settings captured"
printf '\n'
# --------------------------------------------------------------------------
# Compare both local files.
#
# diff exit codes:
# 0 = files are identical
# 1 = files differ
# >1 = operational error
# --------------------------------------------------------------------------
diff -u \
--label "stored: ${MAC_NIX}" \
--label "current: macOS" \
"${MAC_NIX}" \
"${current_nix}" \
>"${diff_file}"
rc=$?
case "${rc}" in
0)
success "No differences found"
printf '\n'
printf '%sStored configuration matches the current Mac.%s\n' \
"${DIM}" "${RESET}"
;;
1)
warning "Differences found"
printf '\n'
# Use a colour-aware pager when available, while remaining
# perfectly usable on a minimal macOS installation.
if command -v delta >/dev/null 2>&1; then
delta <"${diff_file}"
elif command -v colordiff >/dev/null 2>&1; then
colordiff <"${diff_file}"
elif command -v less >/dev/null 2>&1 && [[ -t 1 ]]; then
less -R "${diff_file}"
else
cat "${diff_file}"
fi
printf '\n'
warning "Stored configuration differs from the current Mac."
;;
*)
error "diff failed (exit ${rc})."
return "${rc}"
;;
esac
# Release temporary files immediately. The EXIT trap remains as a safety
# net for errors, interruptions and early returns.
cleanup
return 0
}
# ==============================================================================
# Apply
# ==============================================================================
cmd_apply()
{
local rc
preflight || return 1
check_mac_nix || return 1
header
info "Applying stored macOS settings"
printf 'Source:\n'
printf ' %s\n\n' "${MAC_NIX}"
if [[ ! -f "${NIX_DIR}/flake.nix" ]]; then
error "flake.nix not found:"
printf ' %s\n' "${NIX_DIR}/flake.nix"
return 1
fi
if ! command -v home-manager >/dev/null 2>&1; then
error "home-manager is not available in PATH."
printf '\n'
printf 'The apply operation currently requires Home Manager.\n'
return 1
fi
(
cd "${NIX_DIR}" || exit 1
home-manager switch --flake .
)
rc=$?
if (( rc == 0 )); then
printf '\n'
success "Stored settings applied successfully"
else
printf '\n'
error "Home Manager rebuild failed (exit ${rc})."
return "${rc}"
fi
}
# ==============================================================================
# Status
# ==============================================================================
cmd_status()
{
local os
local arch
header
os="$(uname -s 2>/dev/null || printf 'unknown')"
arch="$(uname -m 2>/dev/null || printf 'unknown')"
printf '%-20s %s (%s)\n' \
"Platform:" "${os}" "${arch}"
if command -v nix >/dev/null 2>&1; then
printf '%-20s %s%s%s\n' \
"Nix:" "${GREEN}" "available" "${RESET}"
printf '%-20s %s\n' \
"Nix version:" "$(nix --version 2>/dev/null || printf 'unknown')"
else
printf '%-20s %s%s%s\n' \
"Nix:" "${RED}" "not found" "${RESET}"
fi
if command -v home-manager >/dev/null 2>&1; then
printf '%-20s %s%s%s\n' \
"Home Manager:" "${GREEN}" "available" "${RESET}"
else
printf '%-20s %s%s%s\n' \
"Home Manager:" "${YELLOW}" "not found" "${RESET}"
fi
if [[ -f "${MAC_NIX}" ]]; then
printf '%-20s %s%s%s\n' \
"mac.nix:" "${GREEN}" "present" "${RESET}"
printf '%-20s %s\n' \
"Last modified:" \
"$(stat -f '%Sm' -t '%Y-%m-%d %H:%M:%S' "${MAC_NIX}" 2>/dev/null || printf 'unknown')"
else
printf '%-20s %s%s%s\n' \
"mac.nix:" "${YELLOW}" "missing" "${RESET}"
fi
printf '\n%-20s %s\n' \
"Configuration:" "${MAC_NIX}"
}
# ==============================================================================
# Help
# ==============================================================================
usage()
{
header
cat <<EOF
${BOLD}Usage:${RESET}
nix-plist-manager-mac <command>
${BOLD}Commands:${RESET}
${GREEN}capture${RESET} Capture current macOS settings into mac.nix
${CYAN}compare${RESET} Compare current macOS settings against mac.nix
${YELLOW}apply${RESET} Apply mac.nix through Home Manager
status Show environment and configuration status
version Show program version
help Show this help
${BOLD}Aliases:${RESET}
diff → compare
restore → apply
check → status
${BOLD}Data flow:${RESET}
capture macOS ─────────────────────────► mac.nix
compare macOS ─► temporary snapshot ─┐
├─► local diff
mac.nix ─────────────────────┘
apply macOS ◄──────────────────────── mac.nix
${BOLD}Configuration:${RESET}
${MAC_NIX}
EOF
}
# ==============================================================================
# Main
# ==============================================================================
main()
{
init_terminal
case "${1:-help}" in
capture)
cmd_capture
;;
compare|diff)
cmd_compare
;;
apply|restore)
cmd_apply
;;
status|check)
cmd_status
;;
version|-V|--version)
printf 'nix-plist-manager-mac %s\n' "${VERSION}"
;;
help|-h|--help)
usage
;;
*)
error "Unknown command: ${1}"
printf '\n'
usage
return 2
;;
esac
}
# ==============================================================================
# Standalone entry point
# ==============================================================================
main "$@"
exit $?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment