#!/bin/bash # Developer Platform: Mac in einem Schritt verbinden (VPN-Profil + Root-CA + Test). # Aufruf: curl -fsSL https://zugang.demo.oliverkoehn.com/mac-setup.sh -o mac-setup.sh && bash mac-setup.sh # Das Skript speichert keine Passwoerter. sudo wird nur fuer das Eintragen der Root-CA gebraucht. set -u LOGIN_SERVER="https://headscale.demo.oliverkoehn.com" SITE="https://zugang.demo.oliverkoehn.com" CA_FP="35:0C:75:C5:F5:64:91:00:F2:0E:2F:8A:BF:1D:98:F9:C1:70:F0:14:83:10:A0:03:94:25:07:7B:71:21:1E:1F" CA_NAME="devplatform Internal Root CA (demo.internal)" GATEWAY="192.168.122.210" TEST_HOST="portal.demo.internal" WAIT_MINUTES=30 B=$'\033[1m'; G=$'\033[32m'; Y=$'\033[33m'; R=$'\033[31m'; N=$'\033[0m' step() { printf '\n%s==> %s%s\n' "$B" "$1" "$N"; } ok() { printf ' %s✓%s %s\n' "$G" "$N" "$1"; } warn() { printf ' %s!%s %s\n' "$Y" "$N" "$1"; } fail() { printf ' %s✗ %s%s\n' "$R" "$1" "$N"; exit 1; } [ "$(uname -s)" = "Darwin" ] || fail "Dieses Skript ist nur fuer macOS." WORK=$(mktemp -d); trap 'rm -rf "$WORK"' EXIT # JSON-Feld aus "tailscale status --json" lesen (plutil ist auf jedem Mac vorhanden) json() { plutil -extract "$1" raw -o - "$WORK/status.json" 2>/dev/null; } status() { "$TS" status --json >"$WORK/status.json" 2>/dev/null; json BackendState; } # --------------------------------------------------------------------------------------------- step "1/5 System pruefen" ok "macOS $(sw_vers -productVersion), Rechner: $(scutil --get ComputerName 2>/dev/null || hostname)" TS="" for c in /Applications/Tailscale.app/Contents/MacOS/Tailscale "$(command -v tailscale 2>/dev/null)" /usr/local/bin/tailscale /opt/homebrew/bin/tailscale; do [ -n "$c" ] && [ -x "$c" ] && { TS="$c"; break; } done if [ -z "$TS" ]; then if command -v brew >/dev/null 2>&1; then warn "Tailscale fehlt - installiere per Homebrew (brew install --cask tailscale) ..." brew install --cask tailscale || fail "Installation fehlgeschlagen." TS=/Applications/Tailscale.app/Contents/MacOS/Tailscale else fail "Tailscale ist nicht installiert. Bitte von https://tailscale.com/download/mac installieren, einmal starten, VPN erlauben und das Skript erneut ausfuehren." fi fi ok "Tailscale: $("$TS" version 2>/dev/null | head -1) ($TS)" if [ -z "$(status)" ]; then warn "Tailscale-App laeuft nicht - starte sie ..." open -a Tailscale 2>/dev/null; sleep 5 fi st=$(status) [ -n "$st" ] || fail "Tailscale antwortet nicht. App starten, ggf. VPN-Konfiguration in den Systemeinstellungen erlauben, dann erneut ausfuehren." ok "Tailscale-Dienst laeuft (Zustand: $st)" # --------------------------------------------------------------------------------------------- step "2/5 Root-Zertifikat der Plattform" curl -fsS "$SITE/platform-root-ca.crt" -o "$WORK/ca.crt" || fail "Download der Root-CA fehlgeschlagen." fp=$(openssl x509 -in "$WORK/ca.crt" -noout -fingerprint -sha256 | cut -d= -f2) [ "$fp" = "$CA_FP" ] || fail "Fingerabdruck stimmt NICHT (erhalten: $fp). Abbruch, bitte Admin informieren." ok "Fingerabdruck geprueft: ${CA_FP:0:23}..." if security find-certificate -c "$CA_NAME" /Library/Keychains/System.keychain >/dev/null 2>&1; then ok "Bereits im System-Schluesselbund vorhanden." else echo " Fuer das Eintragen in den System-Schluesselbund wird dein Mac-Passwort benoetigt:" sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$WORK/ca.crt" \ && ok "Root-CA eingetragen (Safari/Chrome). Firefox: siehe $SITE/#zertifikat" \ || warn "Eintragen fehlgeschlagen - manuell nachholen, siehe $SITE/#zertifikat" fi # --------------------------------------------------------------------------------------------- step "3/5 VPN-Profil anmelden" connected_here() { [ "$(status)" = "Running" ] && "$TS" status 2>/dev/null | grep -q "devplatform-router" } if connected_here; then ok "Bereits mit der Developer Platform verbunden." else # Legt bei bestehendem Tailscale-Konto ein zusaetzliches Profil an; das alte Profil bleibt erhalten. ( "$TS" login --login-server "$LOGIN_SERVER" --accept-routes --accept-dns >"$WORK/login.log" 2>&1 & ) find_url() { url=$(grep -Eo "https://[^ ]*/register/[A-Za-z0-9_-]+" "$WORK/login.log" | head -1) [ -z "$url" ] && { status >/dev/null; url=$(json AuthURL | grep "$LOGIN_SERVER"); } [ -n "$url" ] } url="" for _ in $(seq 1 20); do sleep 1; find_url && break; connected_here && break; done if [ -z "$url" ] && ! connected_here; then # Fallback: Anmeldung ueber das Menue der App (offizieller Weg fuer zusaetzliche Login-Server) [ -s "$WORK/login.log" ] && warn "tailscale login: $(head -1 "$WORK/login.log")" printf '\n %sBitte jetzt in der Menueleiste:%s\n' "$B" "$N" echo " 1. Option-Taste gedrueckt halten und auf das Tailscale-Symbol klicken" echo " 2. Debug -> Custom Login Server -> Add Account..." echo " 3. Eintragen: $LOGIN_SERVER -> Add Account" echo " (Das Browserfenster zeigt den Code ebenfalls. Das Skript wartet hier ...)" for _ in $(seq 1 120); do sleep 2; find_url && break; connected_here && break; printf '.'; done echo fi if ! connected_here; then [ -n "$url" ] || fail "Keine Anmelde-Adresse erhalten. Bitte die Ausgabe von \"$TS switch --list\" an den Admin schicken." code=${url##*/} printf '\n %s┌──────────────────────────────────────────────────────────────────┐%s\n' "$B" "$N" printf ' %s│ Bitte an den Plattform-Admin schicken: │%s\n' "$B" "$N" printf ' %s└──────────────────────────────────────────────────────────────────┘%s\n' "$B" "$N" printf ' Code: %s%s%s\n' "$G" "$code" "$N" printf ' Rechner: %s\n' "$(scutil --get ComputerName 2>/dev/null || hostname)" printf ' Benutzer: %s\n\n' "${USER}" echo " Das Skript wartet bis zu $WAIT_MINUTES Minuten auf die Freischaltung (Abbruch mit Ctrl+C," echo " danach einfach neu starten)." deadline=$(( $(date +%s) + WAIT_MINUTES * 60 )); upped="" while ! connected_here; do [ "$(date +%s)" -lt "$deadline" ] || fail "Keine Freischaltung innerhalb von $WAIT_MINUTES Minuten." st=$(status) # Nach der Freischaltung ggf. Profil einmal aktiv schalten (Fall "unexpected EOF") if [ "$st" = "Stopped" ] && [ -z "$upped" ]; then upped=1 "$TS" up --login-server "$LOGIN_SERVER" --accept-routes --accept-dns >/dev/null 2>&1 & fi printf '.'; sleep 5 done echo fi ok "Freigeschaltet und verbunden." fi "$TS" set --accept-routes --accept-dns >/dev/null 2>&1 && ok "Routen und DNS der Plattform aktiv." # --------------------------------------------------------------------------------------------- step "4/5 Verbindung testen" sleep 2 ip=$(dscacheutil -q host -a name "$TEST_HOST" 2>/dev/null | awk '/ip_address/ {print $2; exit}') [ "$ip" = "$GATEWAY" ] && ok "DNS: $TEST_HOST -> $ip" || warn "DNS: $TEST_HOST -> '${ip:-keine Antwort}' (erwartet $GATEWAY)" if "$TS" ping -c 3 --timeout 5s devplatform-router >/dev/null 2>&1; then ok "VPN-Router erreichbar"; else warn "VPN-Router antwortet nicht auf tailscale ping"; fi code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 --cacert "$WORK/ca.crt" --resolve "$TEST_HOST:443:$GATEWAY" "https://$TEST_HOST/") case "$code" in 2??|3??) ok "HTTPS: https://$TEST_HOST antwortet ($code), Zertifikat gueltig";; *) warn "HTTPS: https://$TEST_HOST -> ${code}";; esac # --------------------------------------------------------------------------------------------- step "5/5 Zusammenfassung (diese Ausgabe kannst du dem Admin schicken)" "$TS" status --json >"$WORK/status.json" 2>/dev/null echo " Rechner: $(scutil --get ComputerName 2>/dev/null || hostname), macOS $(sw_vers -productVersion)" echo " Tailscale: $("$TS" version 2>/dev/null | head -1)" echo " Zustand: $(json BackendState)" echo " VPN-Adresse: $(json Self.TailscaleIPs.0)" echo " DNS-Test: ${ip:-fehlgeschlagen}" echo " HTTPS-Test: ${code}" echo echo " Fertig. Start: https://$TEST_HOST (Anmeldung mit deinem Plattform-Konto)" echo " Zurueck zu einem anderen Tailscale-Konto: $TS switch --list"