#!/usr/bin/env bash
set -u -o pipefail

# ==============================================================================
# Name:        ssh_schluessel_einrichten.sh
# Version:     1.0.2
# Autor:       Josef Lehner
# Datum:       29.09.2026
#
# BESCHREIBUNG
# ------------------------------------------------------------------------------
# Richtet eine SSH-Schlüsselanmeldung für einen Rechner oder eine VM ein.
# Vor Änderungen wird geprüft, ob Schlüssel, Alias und Schlüsselanmeldung
# bereits vollständig funktionieren. Ist das der Fall, wird nichts verändert.
#
# KONFIGURATION
# ------------------------------------------------------------------------------
# Für ein neues Ziel nur diese vier Werte ändern:
#   SSH_USER
#   SSH_HOST
#   SSH_ALIAS
#   SSH_PASSWORT
#
# SSH_PASSWORT leer lassen; bei Bedarf verdeckte Eingabe im Terminal.
#
# VORAUSSETZUNGEN
# ------------------------------------------------------------------------------
# Grundsätzlich: bash, ssh, ssh-keygen, ssh-copy-id
# Für die erstmalige Passwortübertragung zusätzlich: sshpass
#
# Fehlende Pakete werden nur gemeldet. Das Skript installiert nichts selbst.
#
# VERWENDUNG
# ------------------------------------------------------------------------------
# ./ssh_schluessel_einrichten.sh
# ./ssh_schluessel_einrichten.sh --help
# ./ssh_schluessel_einrichten.sh --version
#
# ABLAUF
# ------------------------------------------------------------------------------
# 1. Voraussetzungen prüfen.
# 2. Vorhandenen Schlüssel und Alias feststellen.
# 3. Bestehende Schlüsselanmeldung testen.
# 4. Wenn alles funktioniert: ohne Änderung beenden.
# 5. Falls nötig: Schlüssel erzeugen und/oder Alias ergänzen.
# 6. Vor Änderung einer bestehenden ~/.ssh/config einmalig config.original
#    erzeugen. Eine vorhandene .original wird niemals überschrieben.
# 7. Falls nötig öffentlichen Schlüssel mit ssh-copy-id übertragen.
# 8. Abschließend reine Schlüsselanmeldung testen.
#
# DATEIEN
# ------------------------------------------------------------------------------
# Schlüssel:          ~/.ssh/id_ed25519_<alias>
# SSH-Konfiguration:  ~/.ssh/config
# Originalsicherung:  ~/.ssh/config.original
# Log:                neben dieser .sh, gleicher Basisname mit .log
#
# LOG
# ------------------------------------------------------------------------------
# UTF-8-Textdatei. Bei jedem Start wird das alte Log geleert.
# Bei einem Absturz bleibt das aktuelle Log bis zum nächsten Start erhalten.
# Passwörter werden nicht protokolliert.
# Datumsformat: TT.MM.YYYY HH:MM
#
# SICHERHEIT
# ------------------------------------------------------------------------------
# Vorhandene Schlüssel werden nicht überschrieben.
# Vorhandene Alias-Einträge werden nicht ungefragt verändert.
# Host-Key-Prüfung wird nicht deaktiviert.
# Es erfolgt keine automatische Paketinstallation.
# Die Passwort-Anmeldung des Zielrechners wird nicht deaktiviert.
#
# RÜCKGABECODES
# ------------------------------------------------------------------------------
# 0 = Erfolgreich / keine Änderung erforderlich
# 1 = Allgemeiner Fehler
# 2 = Falsche Parameter
# 3 = Voraussetzung fehlt
# 5 = Datei-/Berechtigungsfehler
# 6 = SSH-/Netzwerkfehler
# 7 = Benutzereingabe erforderlich / Abbruch
#
# ÄNDERUNGEN
# ------------------------------------------------------------------------------
# 1.0.0 - 22.09.2026
# - Ausführliche Dokumentation direkt im Skript
# - Bestandsprüfung vor Änderungen
# - --help und --version
# - Logdatei neben dem Skript
# - einmalige .original-Sicherung
# - bestehendes SSH_PASSWORT-Bedienkonzept beibehalten
# 1.0.2 - 29.09.2026
# - Downloadfassung mit neutralen Zielwerten und ohne gespeichertes Passwort
# 1.0.1 - 23.09.2026
# - wirksames Alias-Ziel prüfen; unvollständige Schlüssel sicher behandeln
# - Schreibfehler prüfen und Passwort bei Bedarf verdeckt abfragen
# ==============================================================================

VERSION="1.0.2"

# Für ein neues Ziel nur diese vier Werte ändern.
SSH_USER="benutzer"
SSH_HOST="rechner.local"
SSH_ALIAS="zielrechner"

# Leer lassen; ein erforderliches Passwort wird verdeckt abgefragt.
SSH_PASSWORT="pw vom ziel rechner"

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
LOG_FILE="$SCRIPT_DIR/${SCRIPT_NAME%.sh}.log"

: > "$LOG_FILE" || { printf 'FEHLER: Logdatei nicht beschreibbar: %s\n' "$LOG_FILE" >&2; exit 5; }

timestamp() { date '+%d.%m.%Y %H:%M'; }
log() { printf '%s %s\n' "$(timestamp)" "$*" | tee -a "$LOG_FILE"; }
die() { local code="$1"; shift; log "FEHLER: $*"; exit "$code"; }

show_help() {
cat <<'EOF'
SSH-Schlüssel für Rechner/VM einrichten

Verwendung:
  ./ssh_schluessel_einrichten.sh
  ./ssh_schluessel_einrichten.sh --help
  ./ssh_schluessel_einrichten.sh --version

Das Skript prüft zuerst, ob Schlüssel, Alias und Schlüsselanmeldung bereits
funktionieren. Ist alles eingerichtet, werden keine Änderungen vorgenommen.

Für ein neues Ziel im Skript SSH_USER, SSH_HOST und SSH_ALIAS anpassen.
Ein erforderliches Passwort wird verdeckt abgefragt.
EOF
}

case "${1:-}" in
    -h|--help) show_help; exit 0 ;;
    --version) printf '%s\n' "$VERSION"; exit 0 ;;
    "") ;;
    *) show_help >&2; exit 2 ;;
esac

KEY="$HOME/.ssh/id_ed25519_${SSH_ALIAS}"
CONFIG="$HOME/.ssh/config"
ORIGINAL="${CONFIG}.original"

log "PROGRAMM: $SCRIPT_NAME"
log "VERSION: $VERSION"
log "START: $(timestamp)"
log "ZIEL: ${SSH_USER}@${SSH_HOST}"
log "ALIAS: ${SSH_ALIAS}"

missing=()
for cmd in ssh ssh-keygen ssh-copy-id; do
    command -v "$cmd" >/dev/null 2>&1 || missing+=("$cmd")
done

if ((${#missing[@]})); then
    {
        echo "FEHLENDE VORAUSSETZUNGEN"
        printf 'Fehlt: %s\n' "${missing[@]}"
        echo "Vorschlag unter Linux Mint:"
        echo "sudo apt install openssh-client"
        echo "Es wird NICHT automatisch installiert."
    } | tee -a "$LOG_FILE"
    exit 3
fi

[[ "$SSH_ALIAS" =~ ^[a-zA-Z0-9_-]+$ && "$SSH_USER" =~ ^[a-zA-Z0-9_.-]+$ &&
   "$SSH_HOST" =~ ^[a-zA-Z0-9_.:-]+$ ]] || die 2 "Ungültiger Alias, Benutzer oder Host."

alias_ok=false
if [[ -f "$CONFIG" ]] && grep -Eq "^[[:space:]]*Host[[:space:]]+${SSH_ALIAS}([[:space:]]|$)" "$CONFIG"; then
    effective="$(ssh -G "$SSH_ALIAS" 2>/dev/null)" || die 5 "SSH-Konfiguration ist ungültig."
    actual_host="$(awk '$1 == "hostname" {print $2; exit}' <<< "$effective")"
    actual_user="$(awk '$1 == "user" {print $2; exit}' <<< "$effective")"
    actual_key="$(awk '$1 == "identityfile" {print $2; exit}' <<< "$effective")"
    if [[ "$actual_host" != "$SSH_HOST" || "$actual_user" != "$SSH_USER" || "$actual_key" != "$KEY" ]]; then
        die 5 "Alias '$SSH_ALIAS' zeigt nicht auf die eingetragenen Werte. Bitte ~/.ssh/config prüfen."
    fi
    alias_ok=true
fi

key_ok=false
if [[ -e "$KEY" || -e "${KEY}.pub" ]]; then
    [[ -f "$KEY" && -f "${KEY}.pub" ]] || die 5 "Schlüsselpaar unvollständig; vorhandene Dateien bleiben unverändert."
    derived="$(ssh-keygen -y -f "$KEY" 2>/dev/null)" || die 5 "Privater Schlüssel kann nicht geprüft werden."
    [[ "$(awk '{print $1, $2}' <<< "$derived")" == "$(awk 'NR==1 {print $1, $2}' "${KEY}.pub")" ]] ||
        die 5 "Privater und öffentlicher Schlüssel passen nicht zusammen."
    key_ok=true
fi

# Zuerst ausschließlich prüfen. Bei funktionierender Einrichtung nichts ändern.
if $key_ok && $alias_ok; then
    log "INFO: Schlüssel und Alias sind vorhanden."
    if ssh -o BatchMode=yes \
           -o PasswordAuthentication=no \
           -o KbdInteractiveAuthentication=no \
           -o PreferredAuthentications=publickey \
           -o IdentitiesOnly=yes -i "$KEY" \
           -o ConnectTimeout=8 \
           "$SSH_ALIAS" true >/dev/null 2>&1; then
        log "BESTANDEN: SSH-Schlüsselanmeldung funktioniert."
        log "Keine Änderung erforderlich."
        exit 0
    fi
    log "WARNUNG: Schlüssel und Alias vorhanden, Verbindungstest fehlgeschlagen."
fi

if ! $key_ok; then
    mkdir -p "$HOME/.ssh" || die 5 "Kann $HOME/.ssh nicht erstellen."
    chmod 700 "$HOME/.ssh" || die 5 "Kann Rechte von ~/.ssh nicht setzen."
    log "Erzeuge ED25519-Schlüssel: $KEY"
    ssh-keygen -q -t ed25519 -f "$KEY" -C "PC-${SSH_ALIAS}" -N "" ||
        die 1 "SSH-Schlüssel konnte nicht erzeugt werden."
else
    log "INFO: Vorhandener Schlüssel wird weiterverwendet: $KEY"
fi

[[ -f "${KEY}.pub" ]] || die 1 "Öffentlicher Schlüssel fehlt: ${KEY}.pub"
chmod 600 "$KEY" || die 5 "Kann Rechte des privaten Schlüssels nicht setzen."
chmod 644 "${KEY}.pub" || die 5 "Kann Rechte des öffentlichen Schlüssels nicht setzen."

if ! $alias_ok; then
    mkdir -p "$HOME/.ssh" || die 5 "Kann $HOME/.ssh nicht erstellen."
    chmod 700 "$HOME/.ssh" || die 5 "Kann Rechte von ~/.ssh nicht setzen."
    touch "$CONFIG" || die 5 "SSH-Konfiguration konnte nicht angelegt werden."
    chmod 600 "$CONFIG" || die 5 "Kann Rechte der SSH-Konfiguration nicht setzen."

    if [[ -s "$CONFIG" && ! -e "$ORIGINAL" ]]; then
        cp -a -- "$CONFIG" "$ORIGINAL" ||
            die 5 "Originalsicherung konnte nicht erstellt werden."
        log "Originalsicherung erstellt: $ORIGINAL"
    elif [[ -e "$ORIGINAL" ]]; then
        log "INFO: $ORIGINAL existiert bereits und wird nicht überschrieben."
    fi

    cat >> "$CONFIG" <<EOF

Host ${SSH_ALIAS}
    HostName ${SSH_HOST}
    User ${SSH_USER}
    IdentityFile ${KEY}
    IdentitiesOnly yes
EOF
    [[ $? -eq 0 ]] || die 5 "SSH-Alias konnte nicht geschrieben werden."
    effective="$(ssh -G "$SSH_ALIAS" 2>/dev/null)" || die 5 "SSH-Konfiguration ist ungültig."
    [[ "$(awk '$1 == "hostname" {print $2; exit}' <<< "$effective")" == "$SSH_HOST" &&
       "$(awk '$1 == "user" {print $2; exit}' <<< "$effective")" == "$SSH_USER" &&
       "$(awk '$1 == "identityfile" {print $2; exit}' <<< "$effective")" == "$KEY" ]] ||
        die 5 "Wirksamer Alias weicht ab; bitte ~/.ssh/config prüfen."
    log "SSH-Alias '${SSH_ALIAS}' wurde ergänzt."
fi

# Nach Schlüssel/Alias erneut prüfen, bevor sshpass benötigt wird.
if ssh -o BatchMode=yes \
       -o PasswordAuthentication=no \
       -o KbdInteractiveAuthentication=no \
       -o PreferredAuthentications=publickey \
       -o IdentitiesOnly=yes -i "$KEY" \
       -o ConnectTimeout=8 \
       "$SSH_ALIAS" true >/dev/null 2>&1; then
    log "BESTANDEN: SSH-Schlüsselanmeldung funktioniert."
    log "Keine weitere Änderung erforderlich."
    exit 0
fi

if ! command -v sshpass >/dev/null 2>&1; then
    {
        echo "FEHLENDE VORAUSSETZUNG"
        echo "Fehlt: sshpass"
        echo "Vorschlag unter Linux Mint:"
        echo "sudo apt install sshpass"
        echo "Es wird NICHT automatisch installiert."
    } | tee -a "$LOG_FILE"
    exit 3
fi

if [[ -z "$SSH_PASSWORT" ]]; then
    [[ -t 0 ]] || die 7 "Passwort nötig; bitte interaktiv im Terminal starten."
    read -r -s -p "SSH-Passwort für ${SSH_USER}@${SSH_HOST}: " SSH_PASSWORT
    printf '\n'
    [[ -n "$SSH_PASSWORT" ]] || die 7 "Kein Passwort eingegeben."
fi

# sshpass liest das Passwort aus der Umgebungsvariable; es erscheint nicht
# als Kommandozeilenargument.
export SSHPASS="$SSH_PASSWORT"

log "Übertrage öffentlichen Schlüssel auf den Zielrechner ..."
if ! sshpass -e ssh-copy-id \
        -o StrictHostKeyChecking=accept-new \
        -i "${KEY}.pub" \
        "${SSH_USER}@${SSH_HOST}"; then
    unset SSHPASS
    die 6 "ssh-copy-id ist fehlgeschlagen."
fi
unset SSHPASS
SSH_PASSWORT=""

log "Teste Anmeldung ausschließlich mit SSH-Schlüssel ..."
if ssh -o BatchMode=yes \
       -o PasswordAuthentication=no \
       -o KbdInteractiveAuthentication=no \
       -o PreferredAuthentications=publickey \
       -o IdentitiesOnly=yes -i "$KEY" \
       -o ConnectTimeout=8 \
       "$SSH_ALIAS" true; then
    log "BESTANDEN: SSH-Schlüsselanmeldung funktioniert."
    log "Künftig genügt: ssh ${SSH_ALIAS}"
    log "Die Passwort-Anmeldung auf dem Ziel wurde NICHT deaktiviert."
    exit 0
fi

die 6 "Der abschließende SSH-Schlüsseltest ist fehlgeschlagen."
