#!/usr/bin/env bash
# ============================================================================
# Erdacht und programmiert von Fabian Hammer, Spessartweg 10, 84048 Mainburg, Deutschland.
# (c) 2026 Fabian Hammer - Alle Rechte vorbehalten. Geistiges Eigentum.
# Unbefugte Vervielfältigung, Weitergabe oder Verwertung dieses Quellcodes ohne
# schriftliche Genehmigung des Urhebers ist untersagt.
# ============================================================================

# localeye-install.sh – Installationshelfer für LocalEye / AllInOne-Suite
# Ubuntu / Debian (geprüft mit Ubuntu 26.04 LTS Desktop) und Arch Linux (auch Manjaro, EndeavourOS).
#
#   1. entfernt nach Rückfrage eine Snap-Installation von Docker und kollidierende Distributionspakete
#   2. installiert Docker Engine + Compose-Plugin (Ubuntu/Debian: offizielle Docker-Paketquelle,
#      Arch: pacman mit vollständigem Systemupdate, wie Arch es verlangt)
#   3. nimmt den eigenen Benutzer in die Gruppe docker auf
#   4. legt den Installationsordner an (Standard /opt/allinone) und kopiert allinone_release.zip
#      + manage.sh aus dem Download-Ordner hinein (manage.sh notfalls aus der ZIP)
#   5. startet auf Wunsch ./manage.sh update – danach geht es im Browser weiter (/anlage/)
#
# Aufruf:  bash localeye-install.sh [--ordner PFAD] [--ja]
#   --ordner PFAD   anderer Installationsordner
#   --ja            alle Rückfragen mit Ja beantworten (auch das Entfernen der Snap-Fassung)
# Erneut ausführen ist gefahrlos: Erledigtes wird erkannt und übersprungen.

# Mit „sh datei“ gestartet (dash)? Dann selbst mit bash neu starten – der Rest braucht bash.
if [ -z "${BASH_VERSION:-}" ]; then
    if [ -r "$0" ]; then exec bash "$0" "$@"; fi
    echo "Bitte mit bash starten: bash ~/Downloads/localeye-install.sh" >&2; exit 2
fi

set -euo pipefail

ORDNER=/opt/allinone
ALLE_JA=0
while [ $# -gt 0 ]; do
    case "$1" in
        --ordner|--folder) ORDNER="${2:?--ordner braucht einen Pfad}"; shift 2 ;;
        --ordner=*|--folder=*) ORDNER="${1#*=}"; shift ;;
        --ja|--yes|-y) ALLE_JA=1; shift ;;
        -h|--hilfe|--help) sed -n '9,23p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
        *) echo "Unbekannte Option: $1 (Hilfe: --hilfe)" >&2; exit 2 ;;
    esac
done

case "${LC_ALL:-${LC_MESSAGES:-${LANG:-}}}" in de*) DE=1 ;; *) DE=0 ;; esac
if [ -t 1 ]; then B=$'\033[1m'; G=$'\033[1;32m'; Y=$'\033[1;33m'; R=$'\033[1;31m'; C=$'\033[1;36m'; N=$'\033[0m'
else B=""; G=""; Y=""; R=""; C=""; N=""; fi
t()    { if [ "$DE" = 1 ]; then printf '%s' "$1"; else printf '%s' "$2"; fi; }
info() { printf '%s==>%s %s\n' "$C" "$N" "$(t "$1" "$2")"; }
ok()   { printf ' %s✓%s %s\n' "$G" "$N" "$(t "$1" "$2")"; }
warn() { printf ' %s!%s %s\n' "$Y" "$N" "$(t "$1" "$2")" >&2; }
fail() { printf ' %s✗ %s%s\n' "$R" "$(t "$1" "$2")" "$N" >&2; exit 1; }
trap 'printf " %s✗ %s%s\n" "$R" "$(t "Abbruch in Zeile $LINENO – siehe Ausgabe oben." "Aborted at line $LINENO – see output above.")" "$N" >&2' ERR

# Rückfrage über das Terminal (funktioniert auch, wenn das Skript per Pipe kommt).
# $1/$2 Frage de/en, $3 Vorgabe j|n. Ohne Terminal gilt die Vorgabe.
frage() {
    local antwort=""
    [ "$ALLE_JA" = 1 ] && return 0
    if ! ( : </dev/tty ) 2>/dev/null; then
        warn "Keine Rückfrage möglich (kein Terminal) – nehme die Vorgabe. Mit --ja alles bestätigen." \
             "Cannot ask (no terminal) – using the default. Use --ja to confirm everything."
        [ "$3" = j ]; return
    fi
    if [ "$3" = j ]; then printf ' %s?%s %s %s ' "$Y" "$N" "$(t "$1" "$2")" "$(t '[J/n]' '[Y/n]')" >/dev/tty
    else printf ' %s?%s %s %s ' "$Y" "$N" "$(t "$1" "$2")" "$(t '[j/N]' '[y/N]')" >/dev/tty; fi
    IFS= read -r antwort </dev/tty || antwort=""
    case "$antwort" in
        [jJyY]*) return 0 ;;
        "") [ "$3" = j ] ;;
        *) return 1 ;;
    esac
}

installiert() { dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -q 'install ok installed'; }

# ---------------------------------------------------------------- Vorprüfung
[ -r /etc/os-release ] || fail "/etc/os-release fehlt – unbekanntes System." "/etc/os-release missing – unknown system."
# shellcheck disable=SC1091
. /etc/os-release
REPO=""; SUITE=""
if [ "${ID:-}" = ubuntu ] || [ -n "${UBUNTU_CODENAME:-}" ]; then
    SYS=apt; REPO=ubuntu; SUITE="${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}"
elif [ "${ID:-}" = debian ] || case " ${ID_LIKE:-} " in *" debian "*) true ;; *) false ;; esac; then
    SYS=apt; REPO=debian; SUITE="${VERSION_CODENAME:-}"
elif [ "${ID:-}" = arch ] || case " ${ID_LIKE:-} " in *" arch "*) true ;; *) false ;; esac; then
    SYS=pacman
else
    fail "Dieses Skript ist für Ubuntu, Debian und Arch Linux (gefunden: ${PRETTY_NAME:-unbekannt}). Andere Systeme: siehe Installationsanleitung." \
         "This script is for Ubuntu, Debian and Arch Linux (found: ${PRETTY_NAME:-unknown}). Other systems: see the installation guide."
fi
if [ "$SYS" = apt ]; then
    [ -n "$SUITE" ] || fail "Versions-Codename nicht ermittelbar." "Could not determine the release codename."
    command -v apt-get >/dev/null 2>&1 || fail "apt-get fehlt." "apt-get is missing."
else
    command -v pacman >/dev/null 2>&1 || fail "pacman fehlt." "pacman is missing."
fi

if [ "$(id -u)" -eq 0 ]; then
    SUDO=""; NUTZER="${SUDO_USER:-root}"
else
    command -v sudo >/dev/null 2>&1 || fail "sudo fehlt – bitte als root ausführen." "sudo is missing – please run as root."
    SUDO="sudo"; NUTZER="$(id -un)"
fi
GRUPPE="$(id -gn "$NUTZER")"
HEIM="$(getent passwd "$NUTZER" | cut -d: -f6)"

printf '\n%sLocalEye / AllInOne – %s%s\n' "$B" "$(t "Installation vorbereiten" "prepare installation")" "$N"
info "System: ${PRETTY_NAME:-$ID} · Benutzer: $NUTZER · Ordner: $ORDNER" \
     "System: ${PRETTY_NAME:-$ID} · user: $NUTZER · folder: $ORDNER"
if [ -n "$SUDO" ]; then
    info "Für die Installation braucht es Administratorrechte – sudo fragt gleich nach deinem Passwort." \
         "Installing needs administrator rights – sudo will ask for your password."
    sudo -v
fi

apt_get() { $SUDO env DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 "$@"; }
apt_update() {   # wartet, solange die automatischen Updates (frisches Ubuntu!) apt belegen
    local i log
    log="$(mktemp)"
    for i in $(seq 1 60); do
        if apt_get update >"$log" 2>&1; then rm -f "$log"; return 0; fi
        if grep -qi 'lock' "$log"; then
            [ "$i" = 1 ] && info "apt ist gerade belegt (automatische Updates) – warte …" "apt is busy (automatic updates) – waiting …"
            sleep 10; continue
        fi
        cat "$log" >&2; rm -f "$log"; return 1
    done
    cat "$log" >&2; rm -f "$log"; return 1
}

# ------------------------------------------------ 1. Snap-Fassung von Docker
if command -v snap >/dev/null 2>&1 && snap list docker >/dev/null 2>&1; then
    warn "Docker ist als Snap installiert. Die Snap-Fassung kommt nur an Dateien im Home-Ordner heran und taugt nicht für $ORDNER." \
         "Docker is installed as a snap. The snap version can only access files in your home folder and cannot be used for $ORDNER."
    if frage "Snap-Docker jetzt entfernen? Alle Container, Images und Volumes der Snap-Fassung werden dabei gelöscht." \
             "Remove the Docker snap now? All containers, images and volumes of the snap version will be deleted." n; then
        $SUDO snap remove --purge docker
        hash -r
        ok "Snap-Docker entfernt." "Docker snap removed."
    else
        fail "Abgebrochen – ohne Entfernen der Snap-Fassung geht es nicht weiter. Es wurde nichts verändert." \
             "Aborted – cannot continue without removing the snap version. Nothing was changed."
    fi
fi

# ------------------------------------------------------- 2. Docker Engine
NEUSTART_NOETIG=0
if [ "$SYS" = apt ]; then
    info "Werkzeuge prüfen (curl, Zertifikate, python3) …" "Checking tools (curl, certificates, python3) …"
    apt_update
    apt_get install -y ca-certificates curl python3 >/dev/null
    ok "curl, Zertifikate und python3 vorhanden." "curl, certificates and python3 present."
fi

if [ "$SYS" = pacman ]; then
    # Arch kennt keine Teil-Updates: neue Pakete nur zusammen mit einem vollständigen -Syu.
    # pacman fragt selbst nach (Enter bestätigt) und bietet bei Konflikten (z. B. podman-docker) das Entfernen an.
    FEHLT=()
    for p in docker docker-compose docker-buildx; do
        if ! pacman -Q "$p" >/dev/null 2>&1; then FEHLT+=( "$p" ); fi
    done
    command -v curl >/dev/null 2>&1 || FEHLT+=( curl )
    command -v python3 >/dev/null 2>&1 || FEHLT+=( python )
    if [ ${#FEHLT[@]} -eq 0 ]; then
        ok "Docker ist schon installiert." "Docker is already installed."
    else
        for i in $(seq 1 60); do
            if [ ! -e /var/lib/pacman/db.lck ]; then break; fi
            if [ "$i" = 1 ]; then info "pacman ist gerade belegt – warte …" "pacman is busy – waiting …"; fi
            sleep 10
        done
        info "System aktualisieren und installieren: ${FEHLT[*]} – pacman fragt gleich nach, Enter bestätigt." \
             "Updating the system and installing: ${FEHLT[*]} – pacman will ask, press Enter to confirm."
        if [ "$ALLE_JA" = 1 ]; then $SUDO pacman -Syu --needed --noconfirm "${FEHLT[@]}"
        else $SUDO pacman -Syu --needed "${FEHLT[@]}"; fi
    fi
    # Wurde dabei der Kernel erneuert, fehlen die Module des laufenden Kernels – Docker startet erst nach einem Neustart.
    if [ ! -d "/usr/lib/modules/$(uname -r)" ]; then NEUSTART_NOETIG=1; fi
elif installiert docker-ce; then
    ok "Docker aus der offiziellen Paketquelle ist schon installiert." "Docker from the official repository is already installed."
    installiert docker-compose-plugin || apt_get install -y docker-compose-plugin
else
    curl -fsSL -o /dev/null "https://download.docker.com/linux/$REPO/gpg" \
        || fail "Keine Verbindung zu download.docker.com – Internetzugang prüfen." "Cannot reach download.docker.com – check the internet connection."
    if curl -fsSL -o /dev/null "https://download.docker.com/linux/$REPO/dists/$SUITE/Release"; then
        KONFLIKT=""
        for p in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
            if installiert "$p"; then KONFLIKT="$KONFLIKT $p"; fi
        done
        if [ -n "$KONFLIKT" ]; then
            warn "Gefunden:$KONFLIKT – diese Pakete der Distribution kollidieren mit der offiziellen Docker-Installation." \
                 "Found:$KONFLIKT – these distribution packages conflict with the official Docker installation."
            if frage "Diese Pakete entfernen? Images und Container unter /var/lib/docker bleiben erhalten." \
                     "Remove these packages? Images and containers in /var/lib/docker are kept." n; then
                # shellcheck disable=SC2086
                apt_get remove -y $KONFLIKT
            else
                fail "Abgebrochen auf Wunsch." "Aborted as requested."
            fi
        fi
        info "Docker-Paketquelle einrichten ($REPO $SUITE) …" "Setting up the Docker repository ($REPO $SUITE) …"
        $SUDO install -m 0755 -d /etc/apt/keyrings
        $SUDO curl -fsSL "https://download.docker.com/linux/$REPO/gpg" -o /etc/apt/keyrings/docker.asc
        $SUDO chmod a+r /etc/apt/keyrings/docker.asc
        # Ältere Einträge (get.docker.com, Handanleitungen) beiseitelegen – doppelte Quellen mit
        # anderem Schlüssel bringen apt sonst zum Abbruch. *.bak ignoriert apt stillschweigend.
        for f in /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do
            if [ -f "$f" ] && [ "$f" != /etc/apt/sources.list.d/docker.sources ] && grep -q 'download\.docker\.com' "$f"; then
                $SUDO mv "$f" "$f.bak"
            fi
        done
        printf 'Types: deb\nURIs: https://download.docker.com/linux/%s\nSuites: %s\nComponents: stable\nSigned-By: /etc/apt/keyrings/docker.asc\n' \
            "$REPO" "$SUITE" | $SUDO tee /etc/apt/sources.list.d/docker.sources >/dev/null
        apt_update
        info "Docker Engine + Compose-Plugin installieren …" "Installing Docker Engine + Compose plugin …"
        apt_get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    elif [ "$REPO" = ubuntu ]; then
        warn "Docker bietet für $SUITE noch keine Pakete an – nehme die Docker-Pakete von Ubuntu." \
             "Docker offers no packages for $SUITE yet – using Ubuntu's Docker packages."
        apt_get install -y docker.io docker-compose-v2
    else
        fail "Docker bietet für Debian $SUITE keine Pakete an." "Docker offers no packages for Debian $SUITE."
    fi
fi
if [ -d /run/systemd/system ]; then
    if [ "$NEUSTART_NOETIG" = 1 ]; then
        $SUDO systemctl enable docker >/dev/null 2>&1 || true
    else
        $SUDO systemctl enable --now docker >/dev/null 2>&1 || warn "Docker-Dienst ließ sich nicht starten: sudo systemctl status docker" \
                                                                  "Could not start the Docker service: sudo systemctl status docker"
    fi
fi
docker compose version >/dev/null 2>&1 || fail "docker compose fehlt nach der Installation." "docker compose is missing after installation."
ok "$(docker --version) · $(docker compose version)" "$(docker --version) · $(docker compose version)"

# ------------------------------------------------------ 3. Gruppe docker
NEU_ANMELDEN=0
if [ "$NUTZER" != root ]; then
    $SUDO groupadd -f docker
    if id -nG "$NUTZER" | tr ' ' '\n' | grep -qx docker; then
        ok "$NUTZER ist in der Gruppe docker." "$NUTZER is in the docker group."
    else
        $SUDO usermod -aG docker "$NUTZER"
        ok "$NUTZER in die Gruppe docker aufgenommen." "Added $NUTZER to the docker group."
    fi
    # Die laufende Sitzung kennt die neue Gruppe erst nach Neuanmeldung.
    id -nG | tr ' ' '\n' | grep -qx docker || NEU_ANMELDEN=1
fi
if [ "$NEUSTART_NOETIG" = 1 ]; then
    warn "Der Kernel wurde eben aktualisiert – Docker startet erst nach einem Neustart." \
         "The kernel was just updated – Docker will only start after a reboot."
elif $SUDO docker run --rm hello-world >/dev/null 2>&1; then
    ok "Docker läuft (Test-Container hello-world erfolgreich)." "Docker works (test container hello-world succeeded)."
else
    warn "Test-Container hello-world ließ sich nicht starten – Dienst prüfen: sudo systemctl status docker" \
         "Test container hello-world failed – check the service: sudo systemctl status docker"
fi

# ------------------------------------------------- 4. Installationsordner
$SUDO mkdir -p "$ORDNER"
$SUDO chown "$NUTZER:$GRUPPE" "$ORDNER"
ok "Ordner $ORDNER angelegt (gehört $NUTZER)." "Folder $ORDNER created (owned by $NUTZER)."

kopiere() {   # $1 Quelle, $2 Ziel – nur wenn neu oder neuer
    if [ ! -e "$2" ] || [ "$1" -nt "$2" ]; then
        $SUDO cp -p -- "$1" "$2"
        $SUDO chown "$NUTZER:$GRUPPE" "$2"
        ok "kopiert: $(basename "$1") → $2" "copied: $(basename "$1") → $2"
    fi
}
DL=""
if command -v xdg-user-dir >/dev/null 2>&1; then
    if [ "$NUTZER" = "$(id -un)" ]; then DL="$(xdg-user-dir DOWNLOAD 2>/dev/null || true)"
    else DL="$(sudo -u "$NUTZER" -H xdg-user-dir DOWNLOAD 2>/dev/null || true)"; fi
fi
{ [ -n "$DL" ] && [ "$DL" != "$HEIM" ] && [ -d "$DL" ]; } || DL="$HEIM/Downloads"
SKRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd || true)"
shopt -s nullglob
# Neue Dateien kommen aus dem Download-Ordner; liegt dort nichts, ersatzweise neben diesem Skript.
for q in "$DL" "$SKRIPT_DIR"; do
    [ -n "$q" ] && [ -d "$q" ] || continue
    [ "$(cd "$q" && pwd)" = "$(cd "$ORDNER" && pwd)" ] && continue
    zips=( "$q"/allinone_release*.zip "$q"/allinone_demo_release*.zip "$q"/allinone_whitelabel*release*.zip )
    skripte=()   # "manage.sh" ist kein Muster – nullglob lässt es stehen, auch wenn die Datei fehlt
    for s in "$q"/manage.sh "$q"/manage\ \(*\).sh; do
        if [ -f "$s" ]; then skripte+=( "$s" ); fi
    done
    [ ${#zips[@]} -gt 0 ] || [ ${#skripte[@]} -gt 0 ] || continue
    for z in "${zips[@]}"; do
        case "$z" in *.deployed_*) continue ;; esac
        kopiere "$z" "$ORDNER/$(basename "$z")"
    done
    if [ ${#skripte[@]} -gt 0 ]; then
        neuestes="${skripte[0]}"
        for s in "${skripte[@]}"; do
            if [ "$s" -nt "$neuestes" ]; then neuestes="$s"; fi
        done
        kopiere "$neuestes" "$ORDNER/manage.sh"
    fi
    break
done
# Kein eigenes manage.sh heruntergeladen? Jede Release-ZIP enthält es – aus der neuesten ZIP entnehmen.
if [ ! -f "$ORDNER/manage.sh" ]; then
    ZIP_NEU=""
    for z in "$ORDNER"/allinone*release*.zip; do
        if [ -z "$ZIP_NEU" ] || [ "$z" -nt "$ZIP_NEU" ]; then ZIP_NEU="$z"; fi
    done
    if [ -n "$ZIP_NEU" ]; then
        $SUDO python3 -c 'import sys, zipfile; open(sys.argv[2], "wb").write(zipfile.ZipFile(sys.argv[1]).read("manage.sh"))' \
            "$ZIP_NEU" "$ORDNER/manage.sh"
        $SUDO chown "$NUTZER:$GRUPPE" "$ORDNER/manage.sh"
        ok "manage.sh aus $(basename "$ZIP_NEU") entnommen." "Took manage.sh from $(basename "$ZIP_NEU")."
    fi
fi
shopt -u nullglob
if [ -f "$ORDNER/manage.sh" ]; then $SUDO chmod +x "$ORDNER/manage.sh"; fi

# ------------------------------------------------------- 5. Einrichtung
HAT_ZIP=0
for z in "$ORDNER"/allinone*release*.zip; do if [ -f "$z" ]; then HAT_ZIP=1; fi; done
GESTARTET=0
if [ "$NEUSTART_NOETIG" = 0 ] && [ -f "$ORDNER/manage.sh" ] && [ "$HAT_ZIP" = 1 ]; then
    if frage "Jetzt die Suite einrichten (./manage.sh update)? Beim ersten Mal dauert das einige Minuten." \
             "Set up the suite now (./manage.sh update)? The first run takes a few minutes." j; then
        GESTARTET=1
        printf '\n'
        # sudo -u lädt die Gruppen frisch – so greift die eben vergebene Gruppe docker sofort.
        if [ "$NUTZER" = root ]; then (cd "$ORDNER" && ./manage.sh update) || GESTARTET=2
        else sudo -u "$NUTZER" -H -- sh -c 'cd "$1" && ./manage.sh update' sh "$ORDNER" || GESTARTET=2; fi
    fi
fi

# ----------------------------------------------------------- Zusammenfassung
IP="$(hostname -I 2>/dev/null | awk '{print $1}' || true)"   # Arch hat oft kein hostname-Kommando
[ -n "$IP" ] || IP="$(ip -4 -o addr show scope global 2>/dev/null | awk '{sub(/\/.*/, "", $4); print $4; exit}' || true)"
IP="${IP:-<IP>}"
printf '\n%s%s%s\n' "$B" "$(t "Fertig." "Done.")" "$N"
if [ "$NEUSTART_NOETIG" = 1 ]; then
    warn "Jetzt den Rechner neu starten und danach dieses Skript erneut ausführen – es macht dort weiter." \
         "Now restart the computer and then run this script again – it continues from there."
elif [ "$GESTARTET" = 1 ]; then
    info "Weiter im Browser: http://$IP:8987/anlage/ – Einrichtungscode und genaue Adresse stehen oben in der Ausgabe." \
         "Continue in the browser: http://$IP:8987/anlage/ – setup code and exact address are shown in the output above."
elif [ "$GESTARTET" = 2 ]; then
    warn "manage.sh meldete einen Fehler – Ausgabe oben prüfen, erneut: cd $ORDNER && ./manage.sh update" \
         "manage.sh reported an error – check the output above, retry: cd $ORDNER && ./manage.sh update"
elif [ -f "$ORDNER/manage.sh" ] && [ "$HAT_ZIP" = 1 ]; then
    info "Einrichten: cd $ORDNER && ./manage.sh update" "Set up: cd $ORDNER && ./manage.sh update"
else
    info "Nächster Schritt: allinone_release.zip und manage.sh nach $ORDNER kopieren und dieses Skript erneut starten (oder: cd $ORDNER && ./manage.sh update)." \
         "Next step: copy allinone_release.zip and manage.sh to $ORDNER and run this script again (or: cd $ORDNER && ./manage.sh update)."
fi
if [ "$NEU_ANMELDEN" = 1 ]; then
    warn "Damit docker in neuen Terminals ohne sudo geht: Rechner einmal neu starten (per SSH: ab- und wieder anmelden)." \
         "For docker without sudo in new terminals: restart the computer once (via SSH: log out and back in)."
fi
