#!/bin/sh
# Rytiv-CLI-Installer.
#
#   curl -fsSL https://rytiv-lab-downloads.s3.nl-ams.scw.cloud/install.sh | sh
#
# WAS DAS HIER IST, UND WAS ES NICHT IST
#
# Es gibt zwei Familien von curl-Pipe-sh-Installern, und sie loesen verschiedene Probleme:
#
#   Familie A - ein PAKET fuer eine vorhandene Laufzeit. Das Skript prueft, ob die Laufzeit da ist,
#   holt das Paket und uebergibt es dem Paketmanager. Der Nutzer braucht die Laufzeit. Beispiele:
#   npm-basierte Werkzeuge, pip-basierte Werkzeuge.
#
#   Familie B - eine BINAERDATEI ohne Laufzeit. Das Skript erkennt Betriebssystem und Architektur,
#   laedt genau die passende Datei und legt sie in den Pfad. Der Nutzer braucht nichts. Fly.io macht
#   das so (`flyctl` ist eine Go-Binaerdatei), und deshalb funktioniert deren Installer auch auf
#   einem Rechner, auf dem nie eine Laufzeit installiert wurde.
#
# DIESES Skript ist Familie A und sagt das offen: die CLI ist heute TypeScript, gebaut nach
# JavaScript, und braucht Node. Der Entscheid "Go fuer Agent und CLI" steht seit 2026-07-28, aber
# umgesetzt ist bisher nur der Datenpfad. Ein Go-Port der CLI macht daraus Familie B und ist der
# richtige naechste Schritt - er ist nur nicht das, was heute noch fertig wird. Siehe
# docs/BACKLOG.md.
#
# Der Unterschied ist keine Kosmetik. Familie A hat drei Nachteile, die ein Kunde merkt: eine
# Node-Version zu alt, ein `npm install -g`, das ohne sudo an den Rechten scheitert, und ein
# Installationsverzeichnis, das nicht im PATH liegt. Genau diese drei Faelle behandelt das Skript
# unten einzeln und mit einem konkreten naechsten Schritt statt mit einer Fehlermeldung von npm.
#
# Env-Schalter:
#   RYTIV_INSTALL_BASE   Basis-URL des Download-Bereichs (Vorgabe unten)
#   RYTIV_CLI_VERSION    bestimmte Version statt der aktuellen, z.B. 0.1.0

set -eu

BASE="${RYTIV_INSTALL_BASE:-https://rytiv-lab-downloads.s3.nl-ams.scw.cloud}"
VERSION="${RYTIV_CLI_VERSION:-}"

# Die Untergrenze steht in cli/package.json unter "engines". Sie steht hier ein zweites Mal, weil
# das Skript das Paket erst NACH der Pruefung in der Hand hat. Damit die beiden Zahlen nicht
# auseinanderlaufen, vergleicht test/unit/install-sh.test.ts sie gegeneinander.
NODE_MIN_MAJOR=22
NODE_MIN_MINOR=6

rot=""; gruen=""; grau=""; aus=""
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
  rot="$(printf '\033[31m')"; gruen="$(printf '\033[32m')"; grau="$(printf '\033[2m')"; aus="$(printf '\033[0m')"
fi

hinweis() { printf '%s\n' "$*"; }
klein()   { printf '%s%s%s\n' "$grau" "$*" "$aus"; }
gut()     { printf '%s+%s %s\n' "$gruen" "$aus" "$*"; }
# Abbruch mit einem HANDGRIFF, nicht nur mit einer Diagnose. Ein Installer, der sagt "Node zu alt"
# und den Nutzer damit stehen laesst, hat die Haelfte seiner Aufgabe erledigt.
abbruch() {
  printf '%sx%s %s\n' "$rot" "$aus" "$1" >&2
  shift
  for zeile in "$@"; do printf '  %s\n' "$zeile" >&2; done
  exit 1
}

hat() { command -v "$1" >/dev/null 2>&1; }

# Ein Lader, zwei moegliche Werkzeuge. Wer dieses Skript per curl bezieht, hat curl - aber es kann
# auch aus einer Datei laufen.
laden() {
  if hat curl; then curl -fsSL "$1" -o "$2"
  elif hat wget; then wget -qO "$2" "$1"
  else abbruch "weder curl noch wget gefunden." "Eines von beiden installieren und erneut versuchen."
  fi
}

hinweis ""
hinweis "  Rytiv CLI"
klein   "  Installation ueber npm (die CLI braucht Node)"
hinweis ""

# --- 1. Laufzeit vorhanden? ---------------------------------------------------------------------
hat node || abbruch "Node ist nicht installiert." \
  "Die Rytiv-CLI ist ein npm-Paket und braucht Node ${NODE_MIN_MAJOR}.${NODE_MIN_MINOR} oder neuer." \
  "macOS:  brew install node" \
  "Linux:  https://nodejs.org/en/download  (oder nvm, fnm, volta)"

hat npm || abbruch "npm ist nicht installiert." \
  "npm gehoert normalerweise zu Node. Node-Installation pruefen."

# --- 2. Laufzeit neu genug? ---------------------------------------------------------------------
# `node -v` liefert z.B. v22.6.0 oder v25.9.0-nightly. Major und Minor werden einzeln geschnitten,
# damit ein Zusatz hinter dem Patch-Stand nichts kaputt macht.
NODE_ROH="$(node -v 2>/dev/null || echo v0.0.0)"
NODE_NUM="${NODE_ROH#v}"
NODE_MAJOR="${NODE_NUM%%.*}"
NODE_REST="${NODE_NUM#*.}"
NODE_MINOR="${NODE_REST%%.*}"
case "$NODE_MAJOR" in *[!0-9]*|"") NODE_MAJOR=0 ;; esac
case "$NODE_MINOR" in *[!0-9]*|"") NODE_MINOR=0 ;; esac

zu_alt=0
if [ "$NODE_MAJOR" -lt "$NODE_MIN_MAJOR" ]; then zu_alt=1
elif [ "$NODE_MAJOR" -eq "$NODE_MIN_MAJOR" ] && [ "$NODE_MINOR" -lt "$NODE_MIN_MINOR" ]; then zu_alt=1
fi
[ "$zu_alt" -eq 0 ] || abbruch "Node ${NODE_ROH} ist zu alt (gebraucht wird ${NODE_MIN_MAJOR}.${NODE_MIN_MINOR} oder neuer)." \
  "macOS:  brew upgrade node" \
  "nvm:    nvm install ${NODE_MIN_MAJOR} && nvm use ${NODE_MIN_MAJOR}" \
  "sonst:  https://nodejs.org/en/download"
gut "Node ${NODE_ROH}"

# --- 3. Welche Version? -------------------------------------------------------------------------
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT INT TERM

if [ -z "$VERSION" ]; then
  laden "${BASE}/cli/latest.txt" "$TMP/latest.txt" 2>/dev/null \
    || abbruch "Download-Bereich nicht erreichbar (${BASE})." \
       "Netzverbindung pruefen, oder eine Version fest vorgeben:" \
       "RYTIV_CLI_VERSION=0.1.0 sh -c \"\$(curl -fsSL ${BASE}/install.sh)\""
  # tr statt einer Shell-Ersetzung: die Datei kann mit \n oder \r\n enden.
  VERSION="$(tr -d ' \t\r\n' < "$TMP/latest.txt")"
  [ -n "$VERSION" ] || abbruch "latest.txt ist leer - der Download-Bereich ist unvollstaendig."
fi

PAKET="rytiv-cli-${VERSION}.tgz"
TGZ="$TMP/$PAKET"

# --- 4. Holen und pruefen -----------------------------------------------------------------------
laden "${BASE}/cli/${PAKET}" "$TGZ" \
  || abbruch "Version ${VERSION} nicht gefunden unter ${BASE}/cli/${PAKET}."

# Die Pruefsumme ist VERBINDLICH. Ein `curl ... | sh` aus einem oeffentlichen Bucket ist ein Weg
# direkt auf den Rechner eines Kunden, und dort gilt mindestens der Massstab, den
# infra/bootstrap/provision-host.sh fuer die eigenen Hosts anlegt ("da wird kein Tarball ungeprueft
# ausgepackt", sha256 fuer Go und Prometheus).
#
# Bis 2026-07-30 war diese Pruefung weich: fehlte die .sha256-Datei oder das Pruefwerkzeug, lief die
# Installation ohne Pruefung weiter und sagte es nur. Das ist die falsche Richtung - eine Pruefung,
# die sich selbst abschaltet, sobald etwas fehlt, ist bei genau dem Angriff wirkungslos, gegen den
# sie steht (wer das Tarball austauschen kann, kann die .sha256 auch weglassen). Deshalb jetzt
# fail-closed: nicht pruefbar heisst nicht installieren.
#
# Was sie leistet und was nicht: sie faengt einen abgebrochenen oder halb hochgeladenen Upload und
# einen Austausch NUR des Pakets. Gegen jemanden, der den ganzen Bucket schreiben kann, schuetzt sie
# nicht - dafuer stehen TLS und die Bucket-Rechte gerade (oeffentlich ist ausschliesslich
# s3:GetObject). Das ehrlich zu benennen ist Teil der Zusage.
PRUEFER=""
if hat sha256sum; then PRUEFER="sha256sum"
elif hat shasum; then PRUEFER="shasum -a 256"
else
  abbruch "Kein sha256-Werkzeug gefunden (weder sha256sum noch shasum)." \
    "Ohne Pruefsumme wird nicht installiert." \
    "Auf Linux liefert das Paket coreutils sha256sum, auf macOS ist shasum vorhanden."
fi

laden "${BASE}/cli/${PAKET}.sha256" "$TMP/soll" \
  || abbruch "Keine Pruefsumme zu ${PAKET} gefunden." \
     "Ohne Pruefsumme wird nicht installiert. Das ist gewollt: ein Paket, dessen Pruefsumme fehlt," \
     "ist von einem ausgetauschten Paket nicht zu unterscheiden."

SOLL="$(tr -d ' \t\r\n' < "$TMP/soll" | cut -d'*' -f1 | cut -c1-64)"
IST="$($PRUEFER "$TGZ" | cut -d' ' -f1)"
[ -n "$SOLL" ] || abbruch "Die Pruefsummen-Datei ist leer - der Download-Bereich ist unvollstaendig."
[ "$SOLL" = "$IST" ] || abbruch "Pruefsumme stimmt NICHT - das Paket ist nicht das erwartete." \
  "erwartet: $SOLL" "bekommen: $IST" \
  "Nicht installiert. Bei wiederholtem Auftreten bitte melden, statt es zu umgehen."
gut "Paket ${VERSION} geladen und geprueft (sha256)"

# --- 5. Installieren ----------------------------------------------------------------------------
PREFIX="$(npm prefix -g 2>/dev/null || echo "")"

if ! npm install -g "$TGZ" >"$TMP/npm.log" 2>&1; then
  # Der mit Abstand haeufigste Grund ist ein Prefix, das dem Nutzer nicht gehoert (Node aus einem
  # Systempaket). Dann ist die Antwort NICHT "das Skript macht heimlich sudo", sondern der genaue
  # Befehl zum Nachfahren - wer eine Pipe aus dem Netz ausfuehrt, soll nicht zusaetzlich
  # unbemerkt Rechte abgeben.
  if grep -qi "EACCES\|permission denied" "$TMP/npm.log"; then
    abbruch "Keine Schreibrechte im globalen npm-Verzeichnis (${PREFIX:-unbekannt})." \
      "Entweder mit erhoehten Rechten wiederholen:" \
      "  sudo npm install -g ${BASE}/cli/${PAKET}" \
      "oder ein eigenes Prefix einrichten (dauerhaft besser, kein sudo noetig):" \
      "  npm config set prefix ~/.npm-global" \
      "  echo 'export PATH=\$HOME/.npm-global/bin:\$PATH' >> ~/.profile && . ~/.profile" \
      "  dann diesen Installer erneut ausfuehren"
  fi
  hinweis ""
  sed 's/^/  /' "$TMP/npm.log" >&2
  abbruch "npm install -g ist fehlgeschlagen (Ausgabe oben)."
fi

# --- 6. Laeuft es wirklich? ---------------------------------------------------------------------
# Die Lehre vom 2026-07-30, fuenfmal bestaetigt: gebaut heisst nicht, dass es wirkt. Ein gruenes
# `npm install` beweist, dass Dateien liegen - nicht, dass ein Aufruf funktioniert. Genau daran ist
# die erste paketierte Fassung der CLI gescheitert (Node lehnt Type-Stripping unter node_modules
# ab; `npm pack` war gruen, der erste Aufruf fiel um). Deshalb wird hier AUFGERUFEN.
RY=""
if [ -n "$PREFIX" ] && [ -x "$PREFIX/bin/ry" ]; then RY="$PREFIX/bin/ry"
elif hat ry; then RY="$(command -v ry)"
else
  abbruch "Installation gemeldet, aber es liegt kein ausfuehrbares 'ry' vor." \
    "Bitte melden, zusammen mit: npm prefix -g && npm ls -g --depth 0"
fi

INSTALLIERT="$("$RY" --version 2>/dev/null || echo "")"
[ -n "$INSTALLIERT" ] || abbruch "'ry' ist installiert, laesst sich aber nicht ausfuehren." \
  "Zum Melden:  $RY --version"
[ "$INSTALLIERT" = "$VERSION" ] || klein "  Hinweis: installiert ist ${INSTALLIERT}, erwartet war ${VERSION}."

# --- 7. Ist es auch AUFRUFBAR? ------------------------------------------------------------------
# Eine erfolgreiche Installation in ein Verzeichnis ausserhalb des PATH sieht fuer den Nutzer
# genauso aus wie eine fehlgeschlagene: "ry: command not found".
hinweis ""
if hat ry; then
  gut "ry ${INSTALLIERT} installiert"
else
  gut "ry ${INSTALLIERT} installiert nach ${RY}"
  hinweis ""
  hinweis "  ${rot}Aber:${aus} ${PREFIX}/bin liegt nicht im PATH - 'ry' ist so noch nicht aufrufbar."
  hinweis "  In die Shell-Konfiguration aufnehmen (~/.zshrc oder ~/.bashrc):"
  hinweis "    export PATH=\"${PREFIX}/bin:\$PATH\""
fi

hinweis ""
klein "  Weiter:"
hinweis "    ry login --token <token>     Token aus der Console"
hinweis "    ry status                    Ueberblick"
hinweis ""
