#!/bin/bash
# =============================================================================
#  Safewave Remote Management - macOS onboarding installer
#
#  Installs the open-source tailscaled system daemon, joins the Safewave tailnet
#  with a short-lived ephemeral key fetched at runtime, and advertises this Mac's
#  local IPv4 subnet so Safewave can perform the engagement assessment/remediation.
#
#  NOT covert: standard, visible install (a system daemon + a device that appears
#  in the Tailscale admin console). Requires ONE admin-password prompt, then runs
#  unattended. No UUID/System-Extension approval is needed (open-source tailscaled
#  uses a utun under root). No reusable tailnet secret is stored in this file.
#
#  Native IPv4: the real subnet (e.g. 192.168.1.0/24) is advertised so LAN devices
#  are reachable by their real IPv4. Run one client at a time (see README).
# =============================================================================
set -euo pipefail

# ---- Engagement config (edit before hosting) --------------------------------
CLIENT_NAME="${SAFEWAVE_CLIENT:-Prospect}"
KEY_ENDPOINT="https://safewave-onboard.pages.dev/api/key"
FETCH_TOKEN="REPLACE-ME-FETCH-TOKEN"
STATUS_ENDPOINT="https://safewave-onboard.pages.dev/api/status"
TAG="tag:safewave-onboard"
AUTO_REMOVE_DAYS=14           # 0 disables scheduled self-removal
# For testing without the Worker: paste an ephemeral tagged key here, or run with
#   sudo SAFEWAVE_AUTHKEY=tskey-... ./install-safewave-remote.command
DIRECT_KEY="${SAFEWAVE_AUTHKEY:-}"
# -----------------------------------------------------------------------------

DATA_DIR="/Library/Application Support/Safewave"
LOG="$DATA_DIR/remote-install.log"
TS_BIN="/usr/local/bin/tailscale"
TSD_BIN="/usr/local/bin/tailscaled"
SELF="$0"

log() { echo "$(date '+%Y-%m-%d %H:%M:%S')  $*"; [ -w "$DATA_DIR" ] 2>/dev/null && echo "$(date '+%Y-%m-%d %H:%M:%S')  $*" >> "$LOG" || true; }

# ---- Elevate once via a single GUI admin prompt -----------------------------
if [ "$(id -u)" -ne 0 ]; then
  echo "Requesting administrator rights (one prompt)..."
  osascript -e "do shell script \"/bin/bash \\\"$SELF\\\" --elevated\" with administrator privileges" || {
    echo "Elevation was declined. Setup cannot continue." >&2; exit 1; }
  exit 0
fi

mkdir -p "$DATA_DIR"
log "=== Safewave remote install starting for '$CLIENT_NAME' on $(hostname -s) ==="

HOST_TAG="$(hostname -s | tr '[:upper:]' '[:lower:]')"
CLEAN_CLIENT="$(echo "$CLIENT_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]\{1,\}/-/g; s/^-//; s/-$//')"
[ -z "$CLEAN_CLIENT" ] && CLEAN_CLIENT="client"
NODE_HOST="safewave-${CLEAN_CLIENT}-${HOST_TAG}"

STATUS_RESULT="started"; STATUS_DETAIL=""; STATUS_SUBNET=""
send_status() {
  local result="$1" detail="$2"
  case "$STATUS_ENDPOINT" in *REPLACE-ME*) log "StatusEndpoint not configured; skipping status POST ($result)."; return 0;; esac
  local body
  body=$(printf '{"client":"%s","host":"%s","nodeName":"%s","os":"macos","subnet":"%s","result":"%s","detail":"%s","ts":"%s"}' \
    "$CLIENT_NAME" "$(hostname -s)" "$NODE_HOST" "$STATUS_SUBNET" "$result" "$detail" "$(date -u +%Y-%m-%dT%H:%M:%SZ)")
  curl -fsS --max-time 20 -X POST "$STATUS_ENDPOINT" \
    -H "Authorization: Bearer $FETCH_TOKEN" -H "Content-Type: application/json" \
    --data "$body" >/dev/null 2>&1 && log "Posted install status: $result" || log "Status POST failed (non-fatal)."
}
fail() { log "FAILED: $1"; send_status "failed" "$1"; exit 1; }

# ---- Detect directly-connected private IPv4 subnets (native IPv4) ------------
mask_to_prefix() { local m=$(( $1 )); local p=0 i; for i in $(seq 0 31); do (( (m>>i)&1 )) && p=$((p+1)); done; echo "$p"; }
ip_and_mask_to_cidr() {
  local ip="$1" maskhex="$2"
  local o1 o2 o3 o4; IFS=. read -r o1 o2 o3 o4 <<< "$ip"
  local ipi=$(( (o1<<24)+(o2<<16)+(o3<<8)+o4 ))
  local mask=$(( maskhex ))
  local net=$(( ipi & mask ))
  local prefix; prefix=$(mask_to_prefix "$mask")
  printf '%d.%d.%d.%d/%d' $(( (net>>24)&255 )) $(( (net>>16)&255 )) $(( (net>>8)&255 )) $(( net&255 )) "$prefix"
}
detect_subnets() {
  local out="" line ip mask cidr
  while read -r ip mask; do
    case "$ip" in
      10.*|192.168.*) : ;;
      172.1[6-9].*|172.2[0-9].*|172.3[0-1].*) : ;;
      *) continue ;;
    esac
    cidr=$(ip_and_mask_to_cidr "$ip" "$mask")
    case ",$out," in *",$cidr,"*) ;; *) out="${out:+$out,}$cidr" ;; esac
  done < <(ifconfig | awk '/inet / && $2 !~ /^127\./ && $2 !~ /^169\.254\./ {print $2, $4}')
  echo "$out"
}

# ---- 1. Obtain auth key: direct DIRECT_KEY, else fetch from endpoint ---------
if [ -n "$DIRECT_KEY" ]; then
  log "Using directly-supplied auth key."
  AUTHKEY="$DIRECT_KEY"
else
  case "$KEY_ENDPOINT$FETCH_TOKEN" in *REPLACE-ME*) fail "No SAFEWAVE_AUTHKEY given and the key endpoint/token is not configured.";; esac
  log "Fetching ephemeral auth key from endpoint..."
  RAW="$(curl -fsS --max-time 30 -H "Authorization: Bearer $FETCH_TOKEN" "$KEY_ENDPOINT" || true)"
  AUTHKEY="$(echo "$RAW" | sed -n 's/.*"\(authkey\|key\)"[[:space:]]*:[[:space:]]*"\(tskey-[^"]*\)".*/\2/p')"
  [ -z "$AUTHKEY" ] && case "$RAW" in tskey-*) AUTHKEY="$(echo "$RAW" | tr -d '[:space:]')";; esac
fi
case "$AUTHKEY" in tskey-*) : ;; *) fail "No valid tskey obtained (need SAFEWAVE_AUTHKEY or a working KeyEndpoint).";; esac
log "Auth key ready (tskey-**** redacted)."

# ---- 2. Install tailscaled (open-source static build) ------------------------
if [ -x "$TSD_BIN" ] && [ -x "$TS_BIN" ]; then
  log "Tailscale already installed: $($TS_BIN version | head -1)"
else
  ARCH="$(uname -m)"; case "$ARCH" in arm64) TARCH="arm64";; x86_64) TARCH="amd64";; *) fail "Unsupported arch: $ARCH";; esac
  log "Resolving latest Tailscale ($TARCH)..."
  TARBALL="$(curl -fsS --max-time 30 'https://pkgs.tailscale.com/stable/?mode=json' \
            | sed -n "s/.*\"$TARCH\"[[:space:]]*:[[:space:]]*\"\(tailscale_[^\"]*\.tgz\)\".*/\1/p" | head -1)"
  [ -z "$TARBALL" ] && fail "Could not resolve the Tailscale tarball name."
  TMP="$(mktemp -d)"; log "Downloading $TARBALL ..."
  curl -fsSL --max-time 300 "https://pkgs.tailscale.com/stable/$TARBALL" -o "$TMP/ts.tgz" || fail "Download failed."
  tar -xzf "$TMP/ts.tgz" -C "$TMP"
  SRC="$(dirname "$(find "$TMP" -name tailscaled -type f | head -1)")"
  install -m 0755 "$SRC/tailscaled" "$TSD_BIN"
  install -m 0755 "$SRC/tailscale"  "$TS_BIN"
  rm -rf "$TMP"
  log "Installing system daemon..."
  "$TSD_BIN" install-system-daemon 2>&1 | while read -r l; do log "  tailscaled: $l"; done || fail "install-system-daemon failed."
  sleep 2
fi

# ---- 3. Enable IPv4 forwarding (needed to route the LAN) ---------------------
sysctl -w net.inet.ip.forwarding=1 >/dev/null 2>&1 || true

# ---- 4. Detect subnet(s) -----------------------------------------------------
ROUTES="$(detect_subnets)"
[ -z "$ROUTES" ] && fail "Could not detect a local IPv4 subnet to advertise."
STATUS_SUBNET="$ROUTES"
log "Advertising native IPv4 route(s): $ROUTES"

# ---- 5. Bring the node up ----------------------------------------------------
log "Joining tailnet as '$NODE_HOST'..."
"$TS_BIN" up --authkey "$AUTHKEY" --advertise-routes "$ROUTES" \
  --accept-dns=false --hostname "$NODE_HOST" --reset 2>&1 | while read -r l; do log "  tailscale: $l"; done
TSIP="$("$TS_BIN" ip -4 2>/dev/null | head -1 || true)"
log "Joined. Tailnet IP: $TSIP"

# ---- 6. Schedule self-removal (safety net) ----------------------------------
if [ "$AUTO_REMOVE_DAYS" -gt 0 ]; then
  log "Scheduling self-removal in $AUTO_REMOVE_DAYS day(s)..."
  STAGE="$DATA_DIR/self-remove.sh"
  cat > "$STAGE" <<'EOS'
#!/bin/bash
LOG="/Library/Application Support/Safewave/remote-install.log"
echo "$(date '+%F %T')  [REMOVE] self-removal triggered." >> "$LOG"
/usr/local/bin/tailscale logout >/dev/null 2>&1 || true
/usr/local/bin/tailscaled uninstall-system-daemon >/dev/null 2>&1 || true
rm -f /usr/local/bin/tailscale /usr/local/bin/tailscaled
launchctl bootout system/com.safewave.selfremove >/dev/null 2>&1 || true
rm -f /Library/LaunchDaemons/com.safewave.selfremove.plist
echo "$(date '+%F %T')  [REMOVE] complete." >> "$LOG"
EOS
  chmod 0755 "$STAGE"
  # Fire once at a specific calendar date N days out.
  RM_MONTH=$(date -v+"${AUTO_REMOVE_DAYS}"d +%m 2>/dev/null || date -d "+${AUTO_REMOVE_DAYS} days" +%m)
  RM_DAY=$(date -v+"${AUTO_REMOVE_DAYS}"d +%d 2>/dev/null || date -d "+${AUTO_REMOVE_DAYS} days" +%d)
  cat > /Library/LaunchDaemons/com.safewave.selfremove.plist <<EOP
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>com.safewave.selfremove</string>
  <key>ProgramArguments</key><array><string>/bin/bash</string><string>$STAGE</string></array>
  <key>StartCalendarInterval</key><dict>
    <key>Month</key><integer>$((10#$RM_MONTH))</integer>
    <key>Day</key><integer>$((10#$RM_DAY))</integer>
    <key>Hour</key><integer>3</integer>
    <key>Minute</key><integer>0</integer>
  </dict>
</dict></plist>
EOP
  launchctl bootstrap system /Library/LaunchDaemons/com.safewave.selfremove.plist 2>/dev/null || \
    launchctl load /Library/LaunchDaemons/com.safewave.selfremove.plist 2>/dev/null || true
else
  log "Self-removal disabled (AUTO_REMOVE_DAYS=0)."
fi

send_status "success" "Joined as $NODE_HOST ($TSIP); routes $ROUTES"
log "=== Safewave remote install complete ==="
