#!/usr/bin/env bash
# ssh-mesh.sh — Bootstrap SSH mesh for Henriks Mac-maskiner.
#
# Usage:
#   curl -fsSL https://setup.henrik-bondtofte.dk | bash
#   bash ssh-mesh.sh                # local
#   bash ssh-mesh.sh --check        # kun verificer, ingen ændringer
#   bash ssh-mesh.sh --pull-only    # kun hent andres pubkeys, ikke push
#   bash ssh-mesh.sh --verify       # kun kør verifikation (test SSH til alle)
#
# Override config-URL:
#   MESH_CONFIG_URL=file:///path/to/local.json bash ssh-mesh.sh
#
# Idempotent: kan køres flere gange uden bivirkninger.
# Public-readable: indeholder ingen keys eller secrets.
#
# Mesh-config er EMBEDDED i scriptet (se EMBEDDED_CONFIG nedenfor) for at
# undgå Vercel bot-protection challenges på sekundær fetch. Override med
# MESH_CONFIG_URL hvis du vil teste mod en anden config.

set -euo pipefail

# Hvis sat, henter scriptet config fra URL i stedet for embedded version
MESH_CONFIG_URL="${MESH_CONFIG_URL:-}"
SSH_DIR="$HOME/.ssh"
KEY_FILE="$SSH_DIR/id_ed25519"
AUTH_FILE="$SSH_DIR/authorized_keys"
CONFIG_FILE="$SSH_DIR/config"
TS=$(date +%Y%m%d-%H%M%S)
MESH_JSON_FILE="/tmp/ssh-mesh-config-${TS}.json"

MODE="${1:-full}"

# --- EMBEDDED CONFIG ---------------------------------------------------------
# REDIGÉR HER for at tilføje/ændre maskiner. Dette er single source of truth.
# (Den separate ssh-mesh.json er en kopi for læsbarhed - dette script er kanonisk.)

read -r -d '' EMBEDDED_CONFIG <<'EOF_CONFIG' || true
{
  "_comment": "Central SSH mesh config for Henriks Mac-maskiner. Public - no secrets.",
  "_updated": "2026-05-05",
  "machines": [
    {
      "alias": "studio",
      "host": "100.68.80.125",
      "user": "henrikbondtofte",
      "hostname_match": "MacStuddeHenrik",
      "role": "main",
      "notes": "Mac Studio - HQ hovedmaskine"
    },
    {
      "alias": "mini1",
      "host": "100.99.47.6",
      "user": "henrikbondtofte",
      "hostname_match": "Mac-mini-tilhrende-Henrik",
      "role": "secondary",
      "notes": "Original Mac Mini - sekundær efter Studio-skift maj 2026"
    },
    {
      "alias": "monster",
      "host": "100.99.47.6",
      "user": "monster",
      "role": "service",
      "notes": "Monster service-bruger på Mac Mini 1 (uid 503)"
    },
    {
      "alias": "mini2",
      "host": "100.113.20.106",
      "user": "macmini2",
      "hostname_match": "Mac-mini-tilhrende-Henrik-2",
      "role": "kunde-agenter",
      "notes": "Mac Mini 2 - kunde-agenter"
    },
    {
      "alias": "macbook",
      "host": "100.113.106.28",
      "user": "henrikbondtofte",
      "hostname_match": "MacBook-Pro",
      "role": "mobile",
      "notes": "MacBook Pro - mobil"
    },
    {
      "alias": "jytte",
      "host": "100.107.228.21",
      "user": "jytte",
      "role": "kunde",
      "notes": "EarlyBird/Jytte kunde-mac"
    }
  ],
  "external": [
    {
      "alias": "hetzner",
      "host": "46.224.150.176",
      "user": "root",
      "identity": "hetzner_henrik",
      "notes": "Hetzner server - separat key"
    }
  ]
}
EOF_CONFIG

# --- helpers -----------------------------------------------------------------

c_blue()  { printf "\033[1;34m%s\033[0m\n" "$*"; }
c_green() { printf "\033[1;32m%s\033[0m\n" "$*"; }
c_red()   { printf "\033[1;31m%s\033[0m\n" "$*"; }
c_yel()   { printf "\033[1;33m%s\033[0m\n" "$*"; }
hdr()     { printf "\n\033[1;36m=== %s ===\033[0m\n" "$*"; }

die() { c_red "ERROR: $*"; exit 1; }

require_cmd() {
  command -v "$1" >/dev/null 2>&1 || die "kræver $1 — installer det først"
}

cleanup() { rm -f "$MESH_JSON_FILE"; }
trap cleanup EXIT

# --- pre-flight --------------------------------------------------------------

hdr "ssh-mesh bootstrap"
echo "host:    $(hostname)"
echo "user:    $(whoami)"
echo "uid:     $(id -u)"
echo "mode:    $MODE"
echo "config:  $MESH_CONFIG_URL"

require_cmd curl
require_cmd ssh
require_cmd ssh-keygen
require_cmd python3

if [[ "$(uname -s)" != "Darwin" ]]; then
  c_yel "ADVARSEL: kører ikke på macOS — Remote Login tjek springes over"
fi

# --- 1. macOS Remote Login ---------------------------------------------------

hdr "1. macOS Remote Login (sshd)"
if [[ "$(uname -s)" == "Darwin" ]]; then
  RL_STATUS=$(sudo -n systemsetup -getremotelogin 2>/dev/null || echo "unknown")
  if echo "$RL_STATUS" | grep -qi "On"; then
    c_green "✅ Remote Login er ON"
  elif echo "$RL_STATUS" | grep -qi "Off"; then
    c_yel "⚠️  Remote Login er OFF — kør manuelt:"
    echo "   sudo systemsetup -setremotelogin on"
  else
    c_yel "⚠️  Kan ikke tjekke Remote Login uden sudo. Kør manuelt:"
    echo "   sudo systemsetup -getremotelogin"
    echo "   sudo systemsetup -setremotelogin on   # hvis OFF"
  fi
fi

# --- 2. SSH key -------------------------------------------------------------

hdr "2. SSH key"
mkdir -p "$SSH_DIR"
chmod 700 "$SSH_DIR"

if [[ ! -f "$KEY_FILE" ]]; then
  # Hvis der findes en eksisterende non-standard key (mini2_id_ed25519, etc.)
  # så bevar trust-chain ved at genbruge den i stedet for at lave en ny.
  EXISTING_KEY=$(ls -t "$SSH_DIR"/*_id_ed25519 2>/dev/null | grep -v ".pub$" | head -1 || true)
  if [[ -n "$EXISTING_KEY" && -f "$EXISTING_KEY.pub" ]]; then
    c_yel "Genbruger eksisterende key $EXISTING_KEY (bevarer trust med peers)"
    cp "$EXISTING_KEY" "$KEY_FILE"
    cp "$EXISTING_KEY.pub" "$KEY_FILE.pub"
  else
    c_blue "Genererer ny SSH key på $KEY_FILE"
    ssh-keygen -t ed25519 -f "$KEY_FILE" -N "" -C "$(whoami)@$(hostname | cut -d. -f1)-$(date +%Y%m%d)"
  fi
else
  c_green "✅ Key findes: $KEY_FILE"
  echo "   $(cat "$KEY_FILE.pub")"
fi
chmod 600 "$KEY_FILE"
chmod 644 "$KEY_FILE.pub"

MY_PUB=$(cat "$KEY_FILE.pub")

# --- 3. Hent mesh config -----------------------------------------------------

hdr "3. Mesh config"
if [[ -n "$MESH_CONFIG_URL" ]]; then
  c_blue "Henter fra URL: $MESH_CONFIG_URL"
  if curl -fsSL "$MESH_CONFIG_URL" > "$MESH_JSON_FILE" 2>/dev/null; then
    c_green "✅ Hentet fra URL"
  else
    c_yel "⚠️  Kunne ikke hente fra URL - bruger embedded config"
    printf '%s' "$EMBEDDED_CONFIG" > "$MESH_JSON_FILE"
  fi
else
  printf '%s' "$EMBEDDED_CONFIG" > "$MESH_JSON_FILE"
  c_green "✅ Bruger embedded config"
fi
MACHINE_COUNT=$(python3 -c "import json; print(len(json.load(open('$MESH_JSON_FILE')).get('machines',[])))")
echo "  $MACHINE_COUNT maskiner i mesh"

# --- 4. Detect this machine -------------------------------------------------

hdr "4. Identificer denne maskine"
MY_HOSTNAME=$(hostname)
MY_TS_IP=""
if [[ -x /Applications/Tailscale.app/Contents/MacOS/Tailscale ]]; then
  MY_TS_IP=$(/Applications/Tailscale.app/Contents/MacOS/Tailscale ip --4 2>/dev/null | head -1 || true)
fi
echo "hostname:    $MY_HOSTNAME"
echo "tailscale:   ${MY_TS_IP:-<ikke fundet>}"
echo "user:        $(whoami)"

MY_ALIAS=$(MESH_JSON_FILE="$MESH_JSON_FILE" MY_HOSTNAME="$MY_HOSTNAME" MY_TS_IP="$MY_TS_IP" MY_USER="$(whoami)" python3 <<'PY'
import json, os
data = json.load(open(os.environ['MESH_JSON_FILE']))
my_h = os.environ.get('MY_HOSTNAME','').lower()
my_ip = os.environ.get('MY_TS_IP','')
my_u = os.environ.get('MY_USER','')
for m in data.get('machines', []):
    if my_ip and m.get('host') == my_ip and m.get('user') == my_u:
        print(m['alias']); break
    hm = (m.get('hostname_match') or '').lower()
    if hm and hm in my_h and m.get('user') == my_u:
        print(m['alias']); break
PY
)

if [[ -n "$MY_ALIAS" ]]; then
  c_green "✅ Genkendt som: $MY_ALIAS"
else
  c_yel "⚠️  Denne maskine er ikke i mesh-config (tilføj til ssh-mesh.json)."
  c_yel "    Kører videre — distribuerer pubkey til andre, men de pusher ikke deres til mig."
fi

# --- 5. Backup ---------------------------------------------------------------

hdr "5. Backup eksisterende konfiguration"
[[ -f "$CONFIG_FILE" ]] && cp -f "$CONFIG_FILE" "$CONFIG_FILE.backup-$TS" && echo "   $CONFIG_FILE.backup-$TS"
[[ -f "$AUTH_FILE" ]]   && cp -f "$AUTH_FILE"   "$AUTH_FILE.backup-$TS"   && echo "   $AUTH_FILE.backup-$TS"

# --- 6. Generér ~/.ssh/config -----------------------------------------------

hdr "6. Opdater ~/.ssh/config"
TMP_CFG=$(mktemp)
cat > "$TMP_CFG" <<HEAD
# === ssh-mesh auto-generated $TS ===
# Genereret af ssh-mesh.sh — REDIGÉR IKKE indenfor denne blok.
# Tilføj custom Hosts EFTER 'END ssh-mesh' markøren.

HEAD

MESH_JSON_FILE="$MESH_JSON_FILE" MY_ALIAS="$MY_ALIAS" python3 <<'PY' >> "$TMP_CFG"
import json, os
data = json.load(open(os.environ['MESH_JSON_FILE']))
my_alias = os.environ.get('MY_ALIAS','')
for m in data.get('machines', []):
    if m['alias'] == my_alias:
        continue
    print(f"Host {m['alias']}")
    print(f"    HostName {m['host']}")
    print(f"    User {m['user']}")
    print(f"    IdentityFile ~/.ssh/id_ed25519")
    print(f"    IdentitiesOnly yes")
    print(f"    ServerAliveInterval 60")
    print(f"    ServerAliveCountMax 3")
    print(f"    StrictHostKeyChecking accept-new")
    print()
for e in data.get('external', []):
    print(f"Host {e['alias']}")
    print(f"    HostName {e['host']}")
    print(f"    User {e['user']}")
    if e.get('identity'):
        print(f"    IdentityFile ~/.ssh/{e['identity']}")
        print(f"    IdentitiesOnly yes")
    print(f"    ServerAliveInterval 60")
    print(f"    ServerAliveCountMax 3")
    print()
PY

echo "# === END ssh-mesh ===" >> "$TMP_CFG"

if [[ -f "$CONFIG_FILE" ]]; then
  CUSTOM=$(awk '/=== END ssh-mesh ===/{flag=1; next} flag' "$CONFIG_FILE" 2>/dev/null || true)
  if [[ -n "$CUSTOM" ]]; then
    echo "" >> "$TMP_CFG"
    echo "# === custom entries (preserved) ===" >> "$TMP_CFG"
    echo "$CUSTOM" >> "$TMP_CFG"
  fi
fi

if [[ "$MODE" == "--check" ]]; then
  c_yel "[--check] springer config-skrivning over"
  diff "$CONFIG_FILE" "$TMP_CFG" 2>&1 | head -40 || true
  rm -f "$TMP_CFG"
elif [[ "$MODE" == "--verify" ]]; then
  c_yel "[--verify] springer config-skrivning over"
  rm -f "$TMP_CFG"
else
  mv "$TMP_CFG" "$CONFIG_FILE"
  chmod 600 "$CONFIG_FILE"
  c_green "✅ ~/.ssh/config opdateret"
fi

# --- 7. Push pubkey til alle andre maskiner ---------------------------------

if [[ "$MODE" != "--pull-only" && "$MODE" != "--check" && "$MODE" != "--verify" ]]; then
  hdr "7. Distribuer pubkey til andre maskiner"
  MESH_JSON_FILE="$MESH_JSON_FILE" MY_ALIAS="$MY_ALIAS" MY_PUB="$MY_PUB" python3 <<'PY'
import json, os, subprocess
data = json.load(open(os.environ['MESH_JSON_FILE']))
my_alias = os.environ.get('MY_ALIAS','')
my_pub = os.environ.get('MY_PUB','').strip()
key = os.path.expanduser('~/.ssh/id_ed25519')
for m in data.get('machines', []):
    if m['alias'] == my_alias:
        continue
    target = f"{m['user']}@{m['host']}"
    remote_cmd = (
        'mkdir -p ~/.ssh && chmod 700 ~/.ssh && '
        'touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && '
        f'(grep -qxF "{my_pub}" ~/.ssh/authorized_keys || echo "{my_pub}" >> ~/.ssh/authorized_keys)'
    )
    cmd = ['ssh','-o','ConnectTimeout=8','-o','StrictHostKeyChecking=accept-new',
           '-o','BatchMode=yes','-i', key, target, remote_cmd]
    label = f"{m['alias']:<10} {target}"
    try:
        r = subprocess.run(cmd, capture_output=True, timeout=20, text=True)
        if r.returncode == 0:
            print(f"  ✅ PUSH  {label}")
        else:
            err = (r.stderr or r.stdout or '').strip().replace('\n',' | ')[:100]
            print(f"  ❌ FAIL  {label}  {err}")
    except subprocess.TimeoutExpired:
        print(f"  ⏱  TIMEOUT  {label}  (offline?)")
    except Exception as e:
        print(f"  ❌ ERROR  {label}  {e}")
PY
fi

# --- 8. Pull pubkeys fra alle andre maskiner --------------------------------

if [[ "$MODE" != "--check" && "$MODE" != "--verify" ]]; then
  hdr "8. Hent pubkeys fra andre maskiner"
  touch "$AUTH_FILE"
  chmod 600 "$AUTH_FILE"
  MESH_JSON_FILE="$MESH_JSON_FILE" MY_ALIAS="$MY_ALIAS" python3 <<'PY'
import json, os, subprocess
data = json.load(open(os.environ['MESH_JSON_FILE']))
my_alias = os.environ.get('MY_ALIAS','')
auth = os.path.expanduser('~/.ssh/authorized_keys')
key = os.path.expanduser('~/.ssh/id_ed25519')
existing = set()
try:
    with open(auth) as f:
        for line in f:
            line = line.strip()
            if line and not line.startswith('#'):
                existing.add(line)
except FileNotFoundError:
    pass
for m in data.get('machines', []):
    if m['alias'] == my_alias:
        continue
    target = f"{m['user']}@{m['host']}"
    # Wrap i bash for at undgå zsh nomatch fejl ved tomme glob-matches
    remote_cmd = 'bash -c \'shopt -s nullglob; for k in ~/.ssh/id_ed25519.pub ~/.ssh/*_id_ed25519.pub ~/.ssh/id_rsa.pub; do [ -f "$k" ] && cat "$k" && break; done\''
    cmd = ['ssh','-o','ConnectTimeout=8','-o','StrictHostKeyChecking=accept-new',
           '-o','BatchMode=yes','-i', key, target, remote_cmd]
    label = f"{m['alias']:<10} {target}"
    try:
        r = subprocess.run(cmd, capture_output=True, timeout=15, text=True)
        if r.returncode == 0 and r.stdout.strip():
            pub = r.stdout.strip().splitlines()[0]
            if pub in existing:
                print(f"  ◯  HAVE  {label}")
            else:
                with open(auth, 'a') as f:
                    f.write(pub + '\n')
                existing.add(pub)
                comment = pub.split()[-1] if len(pub.split()) > 2 else ''
                print(f"  ✅ NEW   {label}  ({comment})")
        else:
            err = (r.stderr or r.stdout or '').strip().replace('\n',' | ')[:100]
            print(f"  ❌ FAIL  {label}  {err}")
    except subprocess.TimeoutExpired:
        print(f"  ⏱  TIMEOUT  {label}  (offline?)")
    except Exception as e:
        print(f"  ❌ ERROR  {label}  {e}")
PY
fi

# --- 9. Verifikation --------------------------------------------------------

hdr "9. Verifikation - test SSH til alle maskiner"
MESH_JSON_FILE="$MESH_JSON_FILE" MY_ALIAS="$MY_ALIAS" python3 <<'PY'
import json, os, subprocess
data = json.load(open(os.environ['MESH_JSON_FILE']))
my_alias = os.environ.get('MY_ALIAS','')
print(f"{'alias':<12} {'target':<35} status")
print("-" * 75)
all_machines = data.get('machines', []) + data.get('external', [])
for m in all_machines:
    if m.get('alias') == my_alias:
        print(f"{m['alias']:<12} {'(dette er mig)':<35} -")
        continue
    target = f"{m['user']}@{m['host']}"
    identity = m.get('identity','id_ed25519')
    cmd = ['ssh','-o','ConnectTimeout=5','-o','BatchMode=yes',
           '-o','StrictHostKeyChecking=accept-new',
           '-i', os.path.expanduser(f'~/.ssh/{identity}'),
           target, 'whoami && hostname']
    try:
        r = subprocess.run(cmd, capture_output=True, timeout=10, text=True)
        if r.returncode == 0:
            who = r.stdout.strip().splitlines()[0]
            print(f"{m['alias']:<12} {target:<35} ✅ OK ({who})")
        else:
            err = (r.stderr or '').strip().replace('\n',' | ')[:50]
            print(f"{m['alias']:<12} {target:<35} ❌ {err}")
    except subprocess.TimeoutExpired:
        print(f"{m['alias']:<12} {target:<35} ⏱  timeout (offline?)")
    except Exception as e:
        print(f"{m['alias']:<12} {target:<35} ❌ {e}")
PY

hdr "Færdig"
echo "Backup-filer:"
ls -1 "$SSH_DIR"/*.backup-* 2>/dev/null | tail -5 || true
echo
echo "Tip: Tilføj din alias til ssh-mesh.json hvis 'denne maskine' ikke blev genkendt."
