#!/usr/bin/env bash # hermes-wiki-serve.sh — Hermes Wiki via obsidian-web-viewer + Tailscale # # Was dieses Skript tut: # - Klont DanielCheer/obsidian-web-viewer einmalig nach ~/.local/share/hermes-wiki/owv/ # - Startet deren server.py (Python stdlib) auf 127.0.0.1:8765 (Loopback) # - Aktiviert tailscale serve, der den Loopback-Port als https im Tailnet exponen lässt # - Liefert File-Tree, 3D-Graph, Volltext-Suche, WikiLink-Navigation out-of-the-box # # Verwendung: # hermes-wiki-serve.sh start # Server starten (klont obv beim ersten Mal) # hermes-wiki-serve.sh stop # Server stoppen # hermes-wiki-serve.sh restart # stop + start # hermes-wiki-serve.sh status # Process-Status, Health-Check, Tailscale-URL # hermes-wiki-serve.sh logs # tail -f der Log-Files # hermes-wiki-serve.sh install # nur das Clonen/Update, kein Server-Start # hermes-wiki-serve.sh --help # # Umgebungsvariablen: # HERMES_WIKI_PORT=8765 Lokaler HTTP-Port (Loopback) # HERMES_WIKI_DIR=/home/admin/my-karpathy-wiki # HERMES_WIKI_LOG_DIR=$HOME/.local/share/hermes-wiki/log # HERMES_WIKI_TS=auto auto|yes|no — Tailscale serve aktivieren # HERMES_WIKI_TS_PATH= leer=Root-Prefix (default), /wiki=unter-Pfad # HERMES_WIKI_OWV_DIR=$HOME/repos/hermes-wiki-viewer (Lukas-Fork default; upstream obv als Fallback) set -euo pipefail # --------------------------------------------------------------------------- # Defaults # --------------------------------------------------------------------------- readonly HERMES_WIKI_PORT="${HERMES_WIKI_PORT:-8765}" readonly HERMES_WIKI_DIR="${HERMES_WIKI_DIR:-/home/admin/my-karpathy-wiki}" readonly HERMES_WIKI_LOG_DIR="${HERMES_WIKI_LOG_DIR:-$HOME/.local/share/hermes-wiki/log}" readonly HERMES_WIKI_TS="${HERMES_WIKI_TS:-auto}" readonly HERMES_WIKI_TS_PATH="${HERMES_WIKI_TS_PATH:-}" # leer = Root-Prefix (obv nutzt relative URLs!) readonly HERMES_WIKI_OWV_DIR="${HERMES_WIKI_OWV_DIR:-$HOME/repos/hermes-wiki-viewer}" readonly HERMES_WIKI_REPO="${HERMES_WIKI_REPO:-}" # leer = lokales Lukas-Repo nutzen readonly OWV_REPO_UPSTREAM="https://github.com/DanielCheer/obsidian-web-viewer.git" readonly PID_FILE="/tmp/hermes-wiki-serve.pid" readonly HEALTH_URL="http://127.0.0.1:${HERMES_WIKI_PORT}/api/vault/tree" # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*" | tee -a "$HERMES_WIKI_LOG_DIR/server.log" >&2 } die() { log "ERROR: $*" exit 1 } ensure_dirs() { mkdir -p "$HERMES_WIKI_LOG_DIR" || die "Kann Log-Dir nicht erstellen: $HERMES_WIKI_LOG_DIR" } require_tools() { command -v python3 >/dev/null || die "python3 nicht gefunden" command -v git >/dev/null || die "git nicht gefunden" [ -d "$HERMES_WIKI_DIR" ] || die "Wiki-Verzeichnis nicht gefunden: $HERMES_WIKI_DIR" } detect_tailscale() { [ "$HERMES_WIKI_TS" != "no" ] || return 1 command -v tailscale >/dev/null || return 1 tailscale status --json >/dev/null 2>&1 || return 1 return 0 } # --------------------------------------------------------------------------- # Installation: Repo klonen oder updaten # --------------------------------------------------------------------------- install_repo() { # Mode 1: Lokales Lukas-Repo (~/repos/hermes-wiki-viewer/) — default nach Fork if [ -d "$HERMES_WIKI_OWV_DIR/.git" ] && [ -z "$HERMES_WIKI_REPO" ]; then log "Nutze lokales Lukas-Repo: $HERMES_WIKI_OWV_DIR" (cd "$HERMES_WIKI_OWV_DIR" && git pull --ff-only 2>>"$HERMES_WIKI_LOG_DIR/install.log") || \ log "WARN: git pull fehlgeschlagen — nutze Working-Tree-Stand" # Mode 2: Remote Lukas-Repo klonen (z.B. wenn HERMES_WIKI_REPO gesetzt) elif [ -n "$HERMES_WIKI_REPO" ]; then if [ -d "$HERMES_WIKI_OWV_DIR/.git" ]; then log "Update Lukas-Fork in $HERMES_WIKI_OWV_DIR" (cd "$HERMES_WIKI_OWV_DIR" && git pull --ff-only 2>>"$HERMES_WIKI_LOG_DIR/install.log") || \ log "WARN: git pull fehlgeschlagen" else log "Klone Lukas-Fork $HERMES_WIKI_REPO nach $HERMES_WIKI_OWV_DIR" git clone "$HERMES_WIKI_REPO" "$HERMES_WIKI_OWV_DIR" 2>>"$HERMES_WIKI_LOG_DIR/install.log" || \ die "git clone fehlgeschlagen — Repo-URL korrekt?" fi # Mode 3: Fallback — upstream obv direkt (vor Lukas-Fork) else log "Kein Lukas-Repo gefunden, klone upstream obv nach $HERMES_WIKI_OWV_DIR" log "(Für Lukas-Customizations: HERMES_WIKI_OWV_DIR=/path/to/hermes-wiki-viewer setzen)" git clone "$OWV_REPO_UPSTREAM" "$HERMES_WIKI_OWV_DIR" 2>>"$HERMES_WIKI_LOG_DIR/install.log" || \ die "git clone fehlgeschlagen" fi # requirements.txt: nur PyYAML (optional). Stdlib reicht für unsere Zwecke. if ! python3 -c "import yaml" 2>/dev/null; then log "PyYAML nicht installiert (optional). Installiere..." python3 -m pip install --user --quiet PyYAML 2>>"$HERMES_WIKI_LOG_DIR/install.log" || \ log "WARN: PyYAML-Installation fehlgeschlagen — Frontmatter wird rudimentär behandelt" fi log "Wiki-Viewer bereit in $HERMES_WIKI_OWV_DIR" } # --------------------------------------------------------------------------- # Tailscale-Integration # --------------------------------------------------------------------------- start_tailscale() { detect_tailscale || { [ "$HERMES_WIKI_TS" = "no" ] || \ log "Tailscale übersprungen (nicht verfügbar oder HERMES_WIKI_TS=$HERMES_WIKI_TS)" return 0 } if tailscale serve status --bg 2>/dev/null | grep -q "$HERMES_WIKI_PORT"; then log "Tailscale serve bereits aktiv für Port $HERMES_WIKI_PORT" return 0 fi log "Starte tailscale serve auf Root-Prefix -> 127.0.0.1:$HERMES_WIKI_PORT" local ts_path_arg=() if [ -n "$HERMES_WIKI_TS_PATH" ]; then ts_path_arg=(--set-path="$HERMES_WIKI_TS_PATH") log "(Tailscale wird unter /$HERMES_WIKI_TS_PATH/ exposen — Achtung: obv nutzt relative URLs!)" fi if tailscale serve --bg --https=443 "${ts_path_arg[@]}" \ "http://127.0.0.1:$HERMES_WIKI_PORT" 2>>"$HERMES_WIKI_LOG_DIR/tailscale.log"; then local ts_url ts_url=$(tailscale serve status --json 2>/dev/null | python3 -c " import json, sys try: data = json.load(sys.stdin) for entry in data.get('Web', {}).values(): print(f'https://{entry[\"HTTPS\"]}{entry[\"Path\"]}/') break except: pass " 2>/dev/null) log "Tailscale serve aktiv: ${ts_url:-URL konnte nicht ermittelt werden}" return 0 else log "WARN: tailscale serve fehlgeschlagen — siehe $HERMES_WIKI_LOG_DIR/tailscale.log" return 0 fi } stop_tailscale() { detect_tailscale || return 0 if tailscale serve status --bg 2>/dev/null | grep -q "$HERMES_WIKI_PORT"; then log "Stoppe tailscale serve" tailscale serve --bg --https=443 --set-path="$HERMES_WIKI_TS_PATH" off \ "http://127.0.0.1:$HERMES_WIKI_PORT" 2>>"$HERMES_WIKI_LOG_DIR/tailscale.log" || true fi } # --------------------------------------------------------------------------- # Server-Lifecycle # --------------------------------------------------------------------------- is_running() { [ -f "$PID_FILE" ] || return 1 local pid pid=$(cat "$PID_FILE" 2>/dev/null || echo "") [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null } start_server() { ensure_dirs require_tools install_repo if is_running; then log "Server läuft bereits (PID $(cat "$PID_FILE"))" return 0 fi # Port-Konflikt-Check if ss -tln 2>/dev/null | grep -q ":${HERMES_WIKI_PORT} "; then die "Port ${HERMES_WIKI_PORT} ist bereits belegt. HERMES_WIKI_PORT ändern oder Prozess beenden." fi # Check, dass die Wiki-Page gerendert werden kann local md_count md_count=$(find "$HERMES_WIKI_DIR" -name "*.md" -not -path "*/raw/*" 2>/dev/null | wc -l) log "Wiki-Dir: $HERMES_WIKI_DIR ($md_count .md-Dateien, ohne /raw/)" # Server starten — Loopback only, NICHT 0.0.0.0 log "Starte obsidian-web-viewer server.py auf 127.0.0.1:$HERMES_WIKI_PORT" nohup python3 "$HERMES_WIKI_OWV_DIR/server.py" \ --vault "$HERMES_WIKI_DIR" \ --host 127.0.0.1 \ --port "$HERMES_WIKI_PORT" \ > "$HERMES_WIKI_LOG_DIR/renderer.log" 2>&1 & local pid=$! echo "$pid" > "$PID_FILE" # Health-Check (das Tool hat keine /__health, aber /api/vault/tree ist immer da) local i for i in 1 2 3 4 5; do sleep 1 if curl -sf "$HEALTH_URL" >/dev/null 2>&1; then log "Server gestartet (PID $pid), Health-Check OK" start_tailscale return 0 fi done log "ERROR: Health-Check fehlgeschlagen nach 5s" log "--- renderer.log ---" tail -20 "$HERMES_WIKI_LOG_DIR/renderer.log" >&2 rm -f "$PID_FILE" return 1 } stop_server() { if is_running; then local pid pid=$(cat "$PID_FILE") log "Stoppe Server (PID $pid)" kill "$pid" 2>/dev/null || true local i for i in 1 2 3 4 5; do kill -0 "$pid" 2>/dev/null || break sleep 0.5 done if kill -0 "$pid" 2>/dev/null; then kill -9 "$pid" 2>/dev/null || true fi rm -f "$PID_FILE" fi # Tailscale NICHT stoppen — soll laufen bleiben für Re-Start ohne Tailscale-Re-Init # stop_tailscale log "(Tailscale serve bleibt aktiv für Re-Start)" } # --------------------------------------------------------------------------- # Status / Help # --------------------------------------------------------------------------- show_status() { echo "=== Hermes Wiki Server Status (via obsidian-web-viewer) ===" echo "Wiki-Dir: $HERMES_WIKI_DIR" echo "Viewer-Repo: $HERMES_WIKI_OWV_DIR" echo "Port: $HERMES_WIKI_PORT (Loopback only)" echo "Log-Dir: $HERMES_WIKI_LOG_DIR" if is_running; then local pid pid=$(cat "$PID_FILE") echo "Status: RUNNING (PID $pid)" ps -o pid,rss,etime,cmd -p "$pid" 2>/dev/null | tail -1 | \ awk '{printf " Memory: %.1f MB, Uptime: %s\n", $2/1024, $3}' if curl -sf "$HEALTH_URL" >/dev/null 2>&1; then local md_count md_count=$(curl -sf "$HEALTH_URL" | python3 -c " import json, sys try: data = json.load(sys.stdin) count = sum(1 + len(c.get('children',[])) for c in [data]) # grobe zählung print(len(json.dumps(data))) except: print('?') " 2>/dev/null || echo "?") echo "Health: OK (Tree-API reachable, $md_count bytes)" else echo "Health: FAILED" fi if detect_tailscale; then if tailscale serve status --bg 2>&1 | grep -q "$HERMES_WIKI_PORT"; then local ts_url ts_url=$(tailscale serve status --json 2>/dev/null | python3 -c " import json, sys try: data = json.load(sys.stdin) for entry in data.get('Web', {}).values(): print(f'https://{entry[\"HTTPS\"]}{entry[\"Path\"]}/') break except: pass " 2>/dev/null) echo "Tailscale: AKTIV — ${ts_url:-URL nicht extrahierbar}" else echo "Tailscale: nicht aktiv für Port $HERMES_WIKI_PORT (HERMES_WIKI_TS=$HERMES_WIKI_TS)" fi else echo "Tailscale: nicht verfügbar oder deaktiviert" fi else echo "Status: NOT RUNNING" fi } show_help() { cat <..ts.net/wiki/ - API: http://127.0.0.1:8765/api/vault/{tree,graph,search,file/} EOF } # --------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------- case "${1:-}" in start) start_server ;; stop) stop_server ;; restart) stop_server; start_server ;; status) show_status ;; logs) tail -f "$HERMES_WIKI_LOG_DIR"/*.log 2>/dev/null || die "Keine Logs gefunden" ;; install) ensure_dirs; require_tools; install_repo ;; --help|-h|help|"") show_help ;; *) die "Unbekanntes Kommando: $1 (--help für Hilfe)" ;; esac