#!/bin/bash # 07-csv-audit-log.sh — append a row to a CSV every time a session starts or ends. # # Why you might want this: # Compliance, accounting, or your own curiosity — every connect and # disconnect lands as one row in a CSV under your user Logs directory. # The file opens in Numbers, Excel, or any text editor; spreadsheet # tools can graph it, filter it, sum the durations. # # What the CSV looks like: # timestamp,event,hostname,user,session_seconds # 2026-06-09T19:45:12+03:00,on-connect,murats-mbp,murat, # 2026-06-09T20:13:48+03:00,on-disconnect,murats-mbp,murat,1716 # # Setup: # Make the script executable, then add it from HearthGate's Hooks tab. # Full guide: https://codnamacs.com/hearthgate/help/features/hooks # # Where the log lives: # ~/Library/Logs/HearthGate-Access.csv # The directory is created on demand. Permissions stay default user-only. # # Rotation: # Apple's log roll-up does not touch this file. If you run this for years # it can grow — consider rotating with a cron / launchd job, e.g. monthly # `mv HearthGate-Access.csv HearthGate-Access-$(date +%Y%m).csv`. # # Environment HearthGate provides: # HEARTHGATE_EVENT # HEARTHGATE_SESSION_DURATION_SEC (disconnect only) # # Test it: # HEARTHGATE_EVENT=on-connect ./07-csv-audit-log.sh # HEARTHGATE_EVENT=on-disconnect HEARTHGATE_SESSION_DURATION_SEC=1716 ./07-csv-audit-log.sh # cat ~/Library/Logs/HearthGate-Access.csv set -euo pipefail EVENT="${HEARTHGATE_EVENT:-${1:-unknown}}" DURATION_SEC="${HEARTHGATE_SESSION_DURATION_SEC:-}" LOG_DIR="$HOME/Library/Logs" LOG_FILE="$LOG_DIR/HearthGate-Access.csv" HEADER="timestamp,event,hostname,user,session_seconds" mkdir -p "$LOG_DIR" # ISO-8601 timestamp with local timezone offset — readable for humans, easy # to sort and slice in a spreadsheet. TIMESTAMP="$(date "+%Y-%m-%dT%H:%M:%S%z" | sed 's/\(..\)$/:\1/')" HOSTNAME_SHORT="$(hostname -s)" USER_NAME="${USER:-$(id -un)}" # CSV-quote anything that might contain a comma. Hostname/user normally # don't, but defence in depth — Apple lets you set quite weird hostnames. csv_quote() { local val="$1" if [[ "$val" == *,* || "$val" == *\"* ]]; then # double the inner quotes then wrap. val="${val//\"/\"\"}" echo "\"$val\"" else echo "$val" fi } # Add header if the file is brand new — exactly once. if [ ! -f "$LOG_FILE" ]; then echo "$HEADER" > "$LOG_FILE" fi # Build the row. session_seconds is empty for connect events. DURATION_FIELD="" if [ -n "$DURATION_SEC" ]; then DURATION_FIELD="$DURATION_SEC" fi row="$TIMESTAMP,$(csv_quote "$EVENT"),$(csv_quote "$HOSTNAME_SHORT"),$(csv_quote "$USER_NAME"),$DURATION_FIELD" echo "$row" >> "$LOG_FILE"