#!/bin/bash # 05-caffeinate-guard.sh — keep the Mac awake while a HearthGate session is open. # # Why you might want this: # You SSH in to your Mac mini from your laptop, start a long-running task, # close the laptop, and an hour later the connection dies because the Mac # went to sleep. This hook starts `caffeinate` the moment a remote session # begins, and stops it the moment the session ends — your Mac stays awake # while someone is using it remotely and goes back to its normal sleep # schedule when they leave. # # How it stays clean: # We write the caffeinate PID to a tiny pidfile when the session starts; # when the session ends we kill exactly that PID. No orphan caffeinate # processes if the Mac reboots between events — caffeinate dies with the # shell that spawned it, and a stale pidfile is harmless because kill on # a missing PID just fails silently. # # Scope note: # This is a simple one-guard example. If you regularly allow multiple # simultaneous remote sessions, adapt the PIDFILE to include a session # identifier from your own wrapper so one disconnect does not release the # guard for another active session. # # Setup: # Make the script executable, then add it from HearthGate's Hooks tab. # Full guide: https://codnamacs.com/hearthgate/help/features/hooks # # Variants you can flip: # The flags below default to keeping the *display* awake too (-d). If you # only want the CPU awake but the screen allowed to sleep (so you can # close the laptop lid and let it run remotely), remove the `d` from # CAFFEINATE_FLAGS. # # Environment HearthGate provides: # HEARTHGATE_EVENT # # Test it: # HEARTHGATE_EVENT=on-connect ./05-caffeinate-guard.sh # ps -p $(cat /tmp/hearthgate-caffeinate.pid) # should show caffeinate # HEARTHGATE_EVENT=after-disconnect ./05-caffeinate-guard.sh # ps -p $(cat /tmp/hearthgate-caffeinate.pid) # should be gone set -euo pipefail EVENT="${HEARTHGATE_EVENT:-${1:-unknown}}" PIDFILE="/tmp/hearthgate-caffeinate.pid" # -d keeps display awake, -i prevents idle sleep, -m disk sleep, -s system sleep. # Drop the d to allow the screen to sleep while the SSH tunnel stays alive. CAFFEINATE_FLAGS="-disu" start_caffeinate() { # If a previous run left a live caffeinate, do not stack a second one. if [ -f "$PIDFILE" ]; then local old old="$(cat "$PIDFILE" 2>/dev/null || true)" if [ -n "$old" ] && kill -0 "$old" 2>/dev/null; then return 0 fi fi # `disown` so caffeinate keeps living after this script exits. caffeinate $CAFFEINATE_FLAGS & local pid=$! disown "$pid" 2>/dev/null || true echo "$pid" > "$PIDFILE" } stop_caffeinate() { if [ ! -f "$PIDFILE" ]; then return 0 fi local pid pid="$(cat "$PIDFILE" 2>/dev/null || true)" if [ -n "$pid" ]; then kill "$pid" 2>/dev/null || true fi rm -f "$PIDFILE" } case "$EVENT" in on-connect|after-connect) start_caffeinate ;; on-disconnect|after-disconnect) stop_caffeinate ;; *) echo "caffeinate-guard: unrecognised event '$EVENT'" >&2 ;; esac