#!/usr/bin/env bash # Preflight-Prüfungen für first-run.sh und setup-lxc.sh (wird gesourced, braucht kein root, ändert nichts am System). # Eigene Ausgabefunktionen mit Präfix pf_, damit sie unabhängig von common.sh funktionieren. PF_LIB_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) PF_REPO_DIR=$(dirname "$(dirname "$PF_LIB_DIR")") PF_MIN_PYTHON=${PF_MIN_PYTHON:-3.11} PF_MIN_DISK_MB=${PF_MIN_DISK_MB:-500} PF_NET_TIMEOUT=${PF_NET_TIMEOUT:-8} PF_PYPI_URL=${PF_PYPI_URL:-https://pypi.org/simple/} PF_UBUNTU_MIN_MAJOR=22 PF_PACKAGES_PENDING=${PF_PACKAGES_PENDING:-0} PF_ERRORS=0 PF_WARNINGS=0 PF_REPORT=${PF_REPORT:-} PF_VIRT_KIND=unbekannt PF_VIRT_NAME= pf_emit() { printf '%s\n' "$1" if [[ -n $PF_REPORT ]]; then printf '%s\n' "$1" >> "$PF_REPORT"; fi } pf_ok() { pf_emit "[ok] $1"; } pf_info() { pf_emit "[info] $1"; } pf_warn() { PF_WARNINGS=$((PF_WARNINGS + 1)) pf_emit "[WARNUNG] $1" pf_emit " Abhilfe: $2" } pf_fail() { PF_ERRORS=$((PF_ERRORS + 1)) pf_emit "[FEHLER] $1" pf_emit " Abhilfe: $2" } pf_check_python() { local version if ! command -v python3 >/dev/null 2>&1; then if (( PF_PACKAGES_PENDING )); then pf_warn "python3 ist nicht installiert." "wird vom Skript per apt installiert." else pf_fail "python3 ist nicht installiert." "apt-get install python3 python3-venv python3-pip" fi return 0 fi version=$(python3 -c 'import sys; print("%d.%d.%d" % sys.version_info[:3])' 2>/dev/null) || version="" if [[ -z $version ]]; then pf_fail "python3 lässt sich nicht ausführen." "Python-Installation prüfen (python3 --version)." return 0 fi if python3 -c "import sys; sys.exit(0 if sys.version_info >= tuple(map(int, '$PF_MIN_PYTHON'.split('.'))) else 1)"; then pf_ok "Python $version (mindestens $PF_MIN_PYTHON)." else pf_fail "Python $version ist älter als $PF_MIN_PYTHON." "Debian 12/13 oder Ubuntu 22.04+ verwenden bzw. ein neueres Python bereitstellen." fi if python3 -c 'import venv, ensurepip' >/dev/null 2>&1; then pf_ok "Python-Module venv und ensurepip vorhanden." elif (( PF_PACKAGES_PENDING )); then pf_warn "Python-Modul venv/ensurepip fehlt." "wird mit python3-venv vom Skript installiert." else pf_fail "Python-Modul venv/ensurepip fehlt." "apt-get install python3-venv python3-pip" fi } pf_existing_parent() { local path=$1 while [[ ! -d $path && $path != / && -n $path ]]; do path=$(dirname "$path"); done printf '%s' "${path:-/}" } pf_check_disk_dir() { local dir existing free dir=$1 existing=$(pf_existing_parent "$dir") free=$(df -Pm "$existing" 2>/dev/null | awk 'NR==2 {print $4}') if [[ ! $free =~ ^[0-9]+$ ]]; then pf_warn "Freier Platz für $dir nicht ermittelbar." "df -h $existing manuell prüfen." elif (( free < PF_MIN_DISK_MB )); then pf_fail "Nur $free MB frei für $dir (benötigt: $PF_MIN_DISK_MB MB)." "Platz schaffen oder ein anderes Verzeichnis wählen." else pf_ok "Freier Platz für $dir: $free MB (mindestens $PF_MIN_DISK_MB MB)." fi } pf_check_os() { local id="" version="" pretty="" major if [[ ! -r /etc/os-release ]]; then pf_warn "/etc/os-release fehlt, Betriebssystem unbekannt." "Getestet ist nur Debian 12/13 und Ubuntu ab $PF_UBUNTU_MIN_MAJOR.04." return 0 fi id=$(. /etc/os-release && printf '%s' "${ID:-}") version=$(. /etc/os-release && printf '%s' "${VERSION_ID:-}") pretty=$(. /etc/os-release && printf '%s' "${PRETTY_NAME:-$id $version}") major=${version%%.*} case $id in debian) if [[ $version == 12 || $version == 13 ]]; then pf_ok "Betriebssystem: $pretty." else pf_warn "Betriebssystem: $pretty, getestet ist nur Debian 12/13." "Auf eigene Verantwortung fortfahren oder Debian 12/13 verwenden." fi ;; ubuntu) if [[ $major =~ ^[0-9]+$ ]] && (( major >= PF_UBUNTU_MIN_MAJOR )); then pf_ok "Betriebssystem: $pretty." else pf_warn "Betriebssystem: $pretty, erwartet Ubuntu ab $PF_UBUNTU_MIN_MAJOR.04." "Neuere Ubuntu-Version verwenden." fi ;; *) pf_warn "Betriebssystem: $pretty, getestet ist nur Debian 12/13 und Ubuntu ab $PF_UBUNTU_MIN_MAJOR.04." "Auf eigene Verantwortung fortfahren." ;; esac } # Setzt PF_VIRT_KIND (container, vm, bare-metal, unbekannt) und PF_VIRT_NAME ohne Ausgabe. pf_detect_env() { local name="" PF_VIRT_KIND=unbekannt PF_VIRT_NAME= if command -v systemd-detect-virt >/dev/null 2>&1; then if name=$(systemd-detect-virt --container 2>/dev/null); then PF_VIRT_KIND=container; PF_VIRT_NAME=$name; return 0 fi if name=$(systemd-detect-virt --vm 2>/dev/null); then PF_VIRT_KIND=vm; PF_VIRT_NAME=$name; return 0 fi PF_VIRT_KIND=bare-metal return 0 fi if [[ -r /run/systemd/container ]]; then PF_VIRT_KIND=container; PF_VIRT_NAME=$(tr -d '\n' < /run/systemd/container 2>/dev/null || true); return 0 fi if [[ -f /.dockerenv ]] || tr '\0' '\n' < /proc/1/environ 2>/dev/null | grep -q '^container='; then PF_VIRT_KIND=container; PF_VIRT_NAME=lxc-oder-docker; return 0 fi if grep -qw hypervisor /proc/cpuinfo 2>/dev/null; then PF_VIRT_KIND=vm; return 0 fi if [[ -r /proc/cpuinfo ]]; then PF_VIRT_KIND=bare-metal; fi return 0 } pf_is_container() { [[ $PF_VIRT_KIND == container ]]; } pf_report_env() { pf_detect_env case $PF_VIRT_KIND in container) pf_info "Umgebung: Container${PF_VIRT_NAME:+ ($PF_VIRT_NAME)}." ;; vm) pf_info "Umgebung: virtuelle Maschine${PF_VIRT_NAME:+ ($PF_VIRT_NAME)}." ;; bare-metal) pf_info "Umgebung: Hardware (weder VM noch Container erkannt)." ;; *) pf_info "Umgebung: nicht erkennbar." ;; esac } pf_check_pypi() { local ok=0 hint="Proxy setzen (export https_proxy=http://proxy:3128 bzw. pip --proxy) oder einen internen PyPI-Mirror verwenden; ohne Zugriff schlägt pip install fehl." if command -v curl >/dev/null 2>&1; then if curl -sS -o /dev/null --max-time "$PF_NET_TIMEOUT" --proto '=https' --tlsv1.2 -I "$PF_PYPI_URL" >/dev/null 2>&1; then ok=1; fi elif command -v python3 >/dev/null 2>&1; then if python3 -I -c 'import sys, urllib.request; urllib.request.urlopen(sys.argv[1], timeout=float(sys.argv[2])).close()' "$PF_PYPI_URL" "$PF_NET_TIMEOUT" >/dev/null 2>&1; then ok=1; fi else pf_warn "pypi.org nicht prüfbar (weder curl noch python3 vorhanden)." "$hint" return 0 fi if (( ok )); then pf_ok "pypi.org per HTTPS erreichbar." else pf_warn "pypi.org per HTTPS nicht erreichbar (Timeout ${PF_NET_TIMEOUT} s)." "$hint" fi } # Gibt nur Host:Port aus (ohne Schema, Zugangsdaten, Pfad und Query). pf_hostport() { local url=$1 scheme="" rest host port="" if [[ $url == *://* ]]; then scheme=${url%%://*}; rest=${url#*://}; else rest=$url; fi rest=${rest%%/*}; rest=${rest%%\?*}; rest=${rest%%#*} rest=${rest##*@} if [[ $rest == \[*\]* ]]; then host=${rest%%]*}; host=${host#[} port=${rest#*]}; port=${port#:} else host=${rest%%:*} [[ $rest == *:* ]] && port=${rest##*:} fi if [[ -z $port ]]; then case ${scheme,,} in http) port=80 ;; *) port=443 ;; esac fi [[ -n $host && $port =~ ^[0-9]+$ ]] || return 1 printf '%s %s' "$host" "$port" } pf_check_tcp() { local label=$1 url=$2 hp host port shown if ! hp=$(pf_hostport "$url"); then pf_warn "$label: Wert nicht auswertbar (erwartet: URL wie https://host:port)." "Variable prüfen." return 0 fi read -r host port <<<"$hp" shown="$host:$port" [[ $host == *:* ]] && shown="[$host]:$port" if timeout "$PF_NET_TIMEOUT" bash -c 'exec 3<>"/dev/tcp/$1/$2"' _ "$host" "$port" >/dev/null 2>&1; then pf_ok "$label erreichbar: $shown." else pf_warn "$label nicht erreichbar: $shown." "DNS, Routing und Firewall von diesem Rechner zu $shown prüfen." fi } pf_check_services() { if [[ -n ${CHECKMK_URL:-} ]]; then pf_check_tcp "Checkmk" "$CHECKMK_URL" else pf_info "Checkmk-Erreichbarkeit übersprungen (CHECKMK_URL nicht gesetzt)." fi if [[ -n ${OLLAMA_URL:-} ]]; then pf_check_tcp "Ollama" "$OLLAMA_URL" else pf_info "Ollama-Erreichbarkeit übersprungen (OLLAMA_URL nicht gesetzt)." fi } pf_report_git() { local rev if command -v git >/dev/null 2>&1 && git -c safe.directory="$PF_REPO_DIR" -C "$PF_REPO_DIR" rev-parse --git-dir >/dev/null 2>&1; then rev=$(git -c safe.directory="$PF_REPO_DIR" -C "$PF_REPO_DIR" rev-parse --short HEAD 2>/dev/null || echo unbekannt) if [[ -n $(git -c safe.directory="$PF_REPO_DIR" -C "$PF_REPO_DIR" status --porcelain 2>/dev/null) ]]; then pf_info "Git-Revision: $rev (mit nicht committeten Änderungen)." else pf_info "Git-Revision: $rev (sauber)." fi else pf_info "Git-Revision: kein Git-Checkout oder git fehlt." fi } # Aufruf: preflight_run ... ; /tmp wird immer mitgeprüft. Rückgabe 1 bei mindestens einem FEHLER. preflight_run() { local dir PF_ERRORS=0 PF_WARNINGS=0 if [[ -n $PF_REPORT ]]; then : > "$PF_REPORT"; fi pf_emit "== Preflight $(date '+%Y-%m-%d %H:%M:%S')" pf_check_python for dir in "$@" /tmp; do pf_check_disk_dir "$dir"; done pf_check_os pf_report_env pf_check_pypi pf_check_services pf_report_git pf_emit "== Preflight: $PF_ERRORS Fehler, $PF_WARNINGS Warnungen" (( PF_ERRORS == 0 )) }