Auf RHEL ist firewalld ab Werk aktiv und gibt in aller Regel nur SSH
frei. Der Stack laeuft dann sauber, ist aber von aussen nicht zu
erreichen: die Agents kommen nicht durch den Receiver auf 8000, und im
Browser passiert nichts. Das war bisher nur eine Handvoll statischer
Beispielzeilen in der README und in install.sh.
Neu ist "cmk-manage.sh firewall". Erkannt werden firewalld, ufw,
nftables und iptables; welche Ports noetig sind, ergibt sich aus der
Konfiguration statt aus einer festen Liste:
- ohne Proxy Manager wird 5000 zum Pflichtport, mit ihm soll es zu
bleiben - offenes 5000 wird deshalb ausdruecklich angemerkt
- der Admin-Port kommt aus NPM_ADMIN_PORT, nicht fest aus 81
- FW_ROLE unterscheidet single/central/remote: eine Remote-Site
braucht 6557 eingehend, die Zentrale baut die Verbindungen selbst
auf und braucht dafuer nichts. Steht FW_ROLE=remote bei
CMK_LIVESTATUS_TCP=off, ist der Port zwar offen, es lauscht aber
niemand - auch darauf weist der Bericht hin.
- Event Console ueber FW_EC_SYSLOG / FW_EC_SNMPTRAP / FW_EC_TCP,
Agent dieses Hosts ueber FW_AGENT, alles Weitere (Grafana, NSCA)
ueber FW_EXTRA_PORTS
Syslog und SNMP-Traps laufen ueber UDP, eine TCP-Freigabe nuetzt ihnen
nichts. Ports werden deshalb durchweg als nummer/protokoll gefuehrt -
beim Pruefen wie beim Erzeugen der Regeln. Bestehende Regeln werden
entsprechend gelesen: Portbereiche (6556-6558 bzw. 6556:6558) werden
aufgeloest, firewalld-Dienstnamen und Rich Rules zaehlen mit, bei
firewalld alle aktiven Zonen statt nur der Standardzone, und eine
ufw-Regel ohne Protokoll zaehlt fuer TCP und UDP - so wie ufw sie
anlegt.
"firewall open" zeigt die passenden Kommandos, --apply setzt sie nach
Rueckfrage, --source begrenzt sie auf ein Quellnetz (fuer Port 80 mit
Warnung, weil Lets Encrypt von aussen prueft). Ohne Befund ist der
Rueckgabewert 1, damit sich das auswerten laesst. In der Oberflaeche
steckt derselbe Bericht unter "Firewall pruefen".
Nebenbei: env_set hat Werte mit Leerzeichen samt Anfuehrungszeichen in
die laufende Umgebung exportiert. In der Datei gehoeren sie hin, im
Wert nicht - das fiel erst mit den Beschreibungen in FW_EXTRA_PORTS auf.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
525 lines
20 KiB
Bash
525 lines
20 KiB
Bash
#!/usr/bin/env bash
|
|
# =====================================================================
|
|
# common.sh - gemeinsame Funktionen fuer cmk-manage.sh
|
|
# Wird per "source" eingebunden, nicht direkt ausgefuehrt.
|
|
# =====================================================================
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Ausgabe / Logging
|
|
# ---------------------------------------------------------------------
|
|
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
|
|
C_RED=$'\033[31m'; C_GRN=$'\033[32m'; C_YEL=$'\033[33m'
|
|
C_BLU=$'\033[34m'; C_BLD=$'\033[1m'; C_RST=$'\033[0m'
|
|
else
|
|
C_RED=; C_GRN=; C_YEL=; C_BLU=; C_BLD=; C_RST=
|
|
fi
|
|
|
|
log() { printf '%s[ %s ]%s %s\n' "$C_BLU" "INFO" "$C_RST" "$*"; }
|
|
ok() { printf '%s[ OK ]%s %s\n' "$C_GRN" "$C_RST" "$*"; }
|
|
warn() { printf '%s[ WARN ]%s %s\n' "$C_YEL" "$C_RST" "$*" >&2; }
|
|
err() { printf '%s[FEHLER]%s %s\n' "$C_RED" "$C_RST" "$*" >&2; }
|
|
die() { err "$*"; exit 1; }
|
|
debug() { [ -n "${VERBOSE:-}" ] && printf ' > %s\n' "$*" >&2 || true; }
|
|
|
|
# ---------------------------------------------------------------------
|
|
# .env laden
|
|
# ---------------------------------------------------------------------
|
|
load_env() {
|
|
local envfile="${1:-$PROJECT_DIR/.env}"
|
|
if [ -f "$envfile" ]; then
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
. "$envfile"
|
|
set +a
|
|
ENV_FILE="$envfile"
|
|
else
|
|
warn "Keine .env gefunden ($envfile) - es gelten die Standardwerte."
|
|
ENV_FILE=""
|
|
fi
|
|
# Rueckfallwerte, falls ein Schluessel in der .env fehlt.
|
|
# Die .env bleibt die massgebliche Konfiguration - hier steht nur,
|
|
# was gilt, wenn dort nichts eingetragen ist.
|
|
CMK_CONTAINER_NAME="${CMK_CONTAINER_NAME:-checkmk}"
|
|
NPM_CONTAINER_NAME="${NPM_CONTAINER_NAME:-nginx-proxy-manager}"
|
|
CMK_SITE_ID="${CMK_SITE_ID:-cmk}"
|
|
CMK_IMAGE="${CMK_IMAGE:-docker.io/checkmk/check-mk-raw:2.3.0-latest}"
|
|
NPM_ENABLED="${NPM_ENABLED:-on}"
|
|
BACKUP_AUTO="${BACKUP_AUTO:-off}"
|
|
CMK_IMAGE_SOURCE="${CMK_IMAGE_SOURCE:-auto}"
|
|
PROXY_FORWARD_HOST="${PROXY_FORWARD_HOST:-127.0.0.1}"
|
|
PROXY_FORWARD_PORT="${PROXY_FORWARD_PORT:-5000}"
|
|
NPM_ADMIN_PORT="${NPM_ADMIN_PORT:-81}"
|
|
# Leer = automatisch aus dem Admin-Port ableiten
|
|
NPM_ADMIN_URL="${NPM_ADMIN_URL:-http://127.0.0.1:$NPM_ADMIN_PORT}"
|
|
BACKUP_DIR="$(abs_path "${BACKUP_DIR:-$PROJECT_DIR/backups}")"
|
|
IMAGE_DIR="$(abs_path "${IMAGE_DIR:-$PROJECT_DIR/images}")"
|
|
}
|
|
|
|
# relativen Pfad (./foo) am Projektverzeichnis ausrichten
|
|
abs_path() {
|
|
case "$1" in
|
|
/*) printf '%s\n' "$1" ;;
|
|
*) printf '%s\n' "$PROJECT_DIR/${1#./}" ;;
|
|
esac
|
|
}
|
|
|
|
# .env-Wert dauerhaft setzen
|
|
# env_set KEY WERT [allow_empty]
|
|
env_set() {
|
|
local key="$1" val="$2" allow_empty="${3:-}" f="${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
[ -f "$f" ] || die "Keine .env vorhanden - kann '$key' nicht setzen."
|
|
# mehrzeilige Werte wuerden die .env zerstoeren
|
|
case "$val" in
|
|
*$'\n'*) die "Ungueltiger Wert fuer '$key' (mehrzeilig)." ;;
|
|
esac
|
|
[ -n "$val" ] || [ -n "$allow_empty" ] || die "Leerer Wert fuer '$key' - abgebrochen."
|
|
# Werte mit Leerzeichen gehoeren in der Datei in Anfuehrungszeichen -
|
|
# in der Umgebung aber ohne, sonst stecken die Zeichen spaeter im Wert
|
|
local quoted="$val"
|
|
case "$val" in
|
|
*" "*) case "$val" in \"*\") : ;; *) quoted="\"$val\"" ;; esac ;;
|
|
esac
|
|
if grep -qE "^[#[:space:]]*${key}=" "$f"; then
|
|
sed -i -E "s|^[#[:space:]]*${key}=.*|${key}=${quoted}|" "$f"
|
|
else
|
|
printf '%s=%s\n' "$key" "$quoted" >>"$f"
|
|
fi
|
|
export "$key=$val"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Container-Runtime erkennen (docker / podman)
|
|
# ---------------------------------------------------------------------
|
|
detect_runtime() {
|
|
if [ -n "${CMK_RUNTIME:-}" ] && command -v "$CMK_RUNTIME" >/dev/null 2>&1; then
|
|
RUNTIME="$CMK_RUNTIME"
|
|
elif [ -n "${CMK_RUNTIME:-}" ]; then
|
|
# Falsch gesetzter Wert darf das Werkzeug nicht unbenutzbar machen
|
|
warn "CMK_RUNTIME='$CMK_RUNTIME' ist nicht installiert - suche selbst.
|
|
Korrigieren mit: cmk-manage.sh config set CMK_RUNTIME auto"
|
|
CMK_RUNTIME=""
|
|
detect_runtime
|
|
return
|
|
elif command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1; then
|
|
RUNTIME=docker
|
|
elif command -v podman >/dev/null 2>&1; then
|
|
RUNTIME=podman
|
|
elif command -v docker >/dev/null 2>&1; then
|
|
RUNTIME=docker # vorhanden, aber Daemon evtl. gestoppt
|
|
else
|
|
die "Weder docker noch podman gefunden. Bitte zuerst ./install.sh ausfuehren."
|
|
fi
|
|
debug "Runtime: $RUNTIME"
|
|
}
|
|
|
|
# Compose-Kommando ermitteln (als Array COMPOSE_CMD)
|
|
detect_compose() {
|
|
COMPOSE_CMD=()
|
|
if [ "$RUNTIME" = docker ]; then
|
|
if docker compose version >/dev/null 2>&1; then
|
|
COMPOSE_CMD=(docker compose)
|
|
elif command -v docker-compose >/dev/null 2>&1; then
|
|
COMPOSE_CMD=(docker-compose)
|
|
fi
|
|
else
|
|
if command -v podman-compose >/dev/null 2>&1; then
|
|
COMPOSE_CMD=(podman-compose)
|
|
elif podman compose version >/dev/null 2>&1; then
|
|
COMPOSE_CMD=(podman compose)
|
|
fi
|
|
fi
|
|
[ ${#COMPOSE_CMD[@]} -gt 0 ] || die "Kein Compose-Kommando gefunden (docker compose / podman-compose)."
|
|
debug "Compose: ${COMPOSE_CMD[*]}"
|
|
}
|
|
|
|
compose() {
|
|
detect_compose
|
|
( cd "$PROJECT_DIR" && "${COMPOSE_CMD[@]}" "$@" )
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Container-Helfer
|
|
# ---------------------------------------------------------------------
|
|
# Ist der NGINX Proxy Manager eingeschaltet? (on/true/1/yes = ja)
|
|
npm_enabled() {
|
|
case "$(printf '%s' "${NPM_ENABLED:-on}" | tr 'A-Z' 'a-z')" in
|
|
on|true|1|yes|ja|y) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
container_exists() { $RUNTIME container inspect "$1" >/dev/null 2>&1; }
|
|
container_running() { [ "$($RUNTIME container inspect -f '{{.State.Running}}' "$1" 2>/dev/null)" = "true" ]; }
|
|
|
|
require_cmk_running() {
|
|
container_exists "$CMK_CONTAINER_NAME" \
|
|
|| die "Container '$CMK_CONTAINER_NAME' existiert nicht. Zuerst: $0 up"
|
|
container_running "$CMK_CONTAINER_NAME" \
|
|
|| die "Container '$CMK_CONTAINER_NAME' laeuft nicht. Zuerst: $0 up"
|
|
}
|
|
|
|
# Kommando im Checkmk-Container als root ausfuehren
|
|
ct() { $RUNTIME exec "$CMK_CONTAINER_NAME" "$@"; }
|
|
# ... mit stdin
|
|
cti() { $RUNTIME exec -i "$CMK_CONTAINER_NAME" "$@"; }
|
|
|
|
# ---------------------------------------------------------------------
|
|
# OMD-Helfer (im Container)
|
|
# ---------------------------------------------------------------------
|
|
cmk_sites() {
|
|
ct sh -c 'ls -1 /omd/sites 2>/dev/null' | sed '/^$/d' || true
|
|
}
|
|
|
|
cmk_site_exists() {
|
|
( set +o pipefail; cmk_sites | grep -qxF "$1" )
|
|
}
|
|
|
|
cmk_site_version() {
|
|
ct omd version -b "$1" 2>/dev/null | tr -d '\r' || true
|
|
}
|
|
|
|
# 0 = laeuft komplett, 1 = teilweise, 2 = gestoppt
|
|
cmk_site_state() {
|
|
local rc=0
|
|
ct omd status --bare "$1" >/dev/null 2>&1 || rc=$?
|
|
echo "$rc"
|
|
}
|
|
|
|
cmk_site_state_text() {
|
|
case "$(cmk_site_state "$1")" in
|
|
0) echo "laeuft" ;;
|
|
1) echo "teilweise" ;;
|
|
*) echo "gestoppt" ;;
|
|
esac
|
|
}
|
|
|
|
cmk_versions_installed() {
|
|
ct omd versions -b 2>/dev/null | tr -d '\r' || ct sh -c 'ls -1 /omd/versions | grep -v "^default$"' 2>/dev/null || true
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Diverses
|
|
# ---------------------------------------------------------------------
|
|
human_size() {
|
|
local b="${1:-0}"
|
|
if [ "$b" -ge 1073741824 ] 2>/dev/null; then awk -v b="$b" 'BEGIN{printf "%.1f GiB", b/1073741824}'
|
|
elif [ "$b" -ge 1048576 ] 2>/dev/null; then awk -v b="$b" 'BEGIN{printf "%.1f MiB", b/1048576}'
|
|
elif [ "$b" -ge 1024 ] 2>/dev/null; then awk -v b="$b" 'BEGIN{printf "%.1f KiB", b/1024}'
|
|
else echo "${b} B"; fi
|
|
}
|
|
|
|
file_size() { stat -c %s "$1" 2>/dev/null || echo 0; }
|
|
|
|
confirm() {
|
|
[ -n "${ASSUME_YES:-}" ] && return 0
|
|
local prompt="${1:-Fortfahren?} [j/N] " a
|
|
read -r -p "$prompt" a </dev/tty || return 1
|
|
case "$a" in [jJyY]*) return 0 ;; *) return 1 ;; esac
|
|
}
|
|
|
|
primary_ip() {
|
|
ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1);exit}}' \
|
|
|| hostname -I 2>/dev/null | awk '{print $1}'
|
|
}
|
|
|
|
# =====================================================================
|
|
# TUI-Helfer (dialog bevorzugt, sonst whiptail) - ncurses wie im mc
|
|
# =====================================================================
|
|
UI_BACKTITLE="Checkmk Stack Manager - Container-Runtime: ${RUNTIME:-?}"
|
|
|
|
# Groesse des Terminals ermitteln (mit Rueckfallwerten)
|
|
ui_term_size() {
|
|
UI_ROWS="$(tput lines 2>/dev/null || true)"
|
|
UI_COLS="$(tput cols 2>/dev/null || true)"
|
|
case "$UI_ROWS" in ''|*[!0-9]*) UI_ROWS=24 ;; esac
|
|
case "$UI_COLS" in ''|*[!0-9]*) UI_COLS=80 ;; esac
|
|
if [ "$UI_ROWS" -lt 10 ]; then UI_ROWS=24; fi
|
|
if [ "$UI_COLS" -lt 40 ]; then UI_COLS=80; fi
|
|
return 0
|
|
}
|
|
|
|
# Fenstergroesse aus Textlaenge, Anzahl der Eintraege und Terminalgroesse
|
|
# berechnen. Vorher waren die Werte fest - bei langem Text blieb dann nur
|
|
# eine Zeile fuer die Liste uebrig.
|
|
#
|
|
# ACHTUNG: hier bewusst mit if-Bloecken statt "[ ... ] && VAR=...".
|
|
# Letzteres liefert 1, wenn die Bedingung nicht zutrifft, und beendet
|
|
# unter "set -e" die Funktion bzw. das ganze Skript.
|
|
#
|
|
# ui_calc TEXT FELDER_JE_EINTRAG EINTRAEGE...
|
|
ui_calc() {
|
|
local text="$1" per="$2"; shift 2
|
|
ui_term_size
|
|
|
|
local tl maxw
|
|
tl="$(printf '%s\n' "$text" | wc -l)"
|
|
maxw="$(printf '%s\n' "$text" | awk '{ if (length($0) > m) m = length($0) } END { print m+0 }')"
|
|
|
|
# breiteste Zeile der Liste (Kennung und Beschriftung stehen nebeneinander)
|
|
local -a args=("$@")
|
|
local i=0 w itemw=0 n=0
|
|
while [ "$i" -lt "${#args[@]}" ]; do
|
|
w=$(( ${#args[$i]} + 4 ))
|
|
if [ $((i+1)) -lt "${#args[@]}" ]; then
|
|
w=$(( w + ${#args[$((i+1))]} ))
|
|
fi
|
|
if [ "$w" -gt "$itemw" ]; then itemw="$w"; fi
|
|
i=$(( i + per )); n=$(( n + 1 ))
|
|
done
|
|
|
|
# Breite: der breitere von Text und Liste, plus Rahmen
|
|
if [ "$maxw" -gt "$itemw" ]; then UI_W=$(( maxw + 10 )); else UI_W=$(( itemw + 10 )); fi
|
|
if [ "$UI_W" -lt 60 ]; then UI_W=60; fi
|
|
if [ "$UI_W" -gt $(( UI_COLS - 4 )) ]; then UI_W=$(( UI_COLS - 4 )); fi
|
|
|
|
# Listenhoehe: moeglichst alle Eintraege zeigen, ohne aus dem Bild zu laufen
|
|
local avail=$(( UI_ROWS - tl - 9 ))
|
|
if [ "$avail" -lt 3 ]; then avail=3; fi
|
|
if [ "$n" -lt "$avail" ]; then UI_MH="$n"; else UI_MH="$avail"; fi
|
|
if [ "$UI_MH" -lt 1 ]; then UI_MH=1; fi
|
|
|
|
UI_H=$(( tl + UI_MH + 8 ))
|
|
if [ "$UI_H" -gt $(( UI_ROWS - 2 )) ]; then UI_H=$(( UI_ROWS - 2 )); fi
|
|
if [ "$UI_H" -lt 10 ]; then UI_H=10; fi
|
|
return 0
|
|
}
|
|
|
|
# Hoehe fuer Fenster ohne Liste (Meldung, Abfrage, Eingabe)
|
|
ui_calc_text() { # text zusatzzeilen
|
|
local text="$1" extra="${2:-7}"
|
|
ui_term_size
|
|
local tl maxw
|
|
tl="$(printf '%s\n' "$text" | wc -l)"
|
|
maxw="$(printf '%s\n' "$text" | awk '{ if (length($0) > m) m = length($0) } END { print m+0 }')"
|
|
UI_W=$(( maxw + 10 ))
|
|
if [ "$UI_W" -lt 60 ]; then UI_W=60; fi
|
|
if [ "$UI_W" -gt $(( UI_COLS - 4 )) ]; then UI_W=$(( UI_COLS - 4 )); fi
|
|
UI_H=$(( tl + extra ))
|
|
if [ "$UI_H" -gt $(( UI_ROWS - 2 )) ]; then UI_H=$(( UI_ROWS - 2 )); fi
|
|
if [ "$UI_H" -lt 8 ]; then UI_H=8; fi
|
|
return 0
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Rahmenzeichen in PuTTY & Co.
|
|
#
|
|
# Ohne UTF-8-Locale zeichnen dialog/whiptail die Rahmen ueber die alte
|
|
# VT100-Umschaltung "ESC(0". Terminals, die im UTF-8-Modus laufen -
|
|
# PuTTY in der Vorgabe -, ignorieren diese Umschaltung und zeigen
|
|
# stattdessen die rohen Buchstaben q, x, l, m.
|
|
#
|
|
# Nachgemessen: bei LC_ALL=C schickt whiptail eine ESC(0-Umschaltung und
|
|
# 91 rohe "q"; mit UTF-8-Locale keine einzige. Es genuegt, LC_CTYPE zu
|
|
# setzen - Sortierung und Zahlenformat bleiben damit unberuehrt.
|
|
# Zwingt ncurses zu echten Unicode-Rahmenzeichen.
|
|
#
|
|
# Auch mit UTF-8-Locale benutzt ncurses die VT100-Umschaltung ESC(0,
|
|
# solange das terminfo sie anbietet (bei TERM=xterm der Fall). Terminals
|
|
# im UTF-8-Modus - PuTTY - machen die nicht mit und zeigen q, x, l, m.
|
|
# Gemessen mit dialog unter LANG=C.UTF-8, TERM=xterm:
|
|
# ohne Schalter : 38 ESC(0, 0 Rahmenbytes, 182 rohe q
|
|
# mit Schalter : 0 ESC(0, 220 Rahmenbytes, 0 rohe q
|
|
# whiptail (slang) braucht ihn nicht, es zeichnet bei UTF-8-Locale schon
|
|
# von sich aus Unicode.
|
|
ui_use_unicode_acs() {
|
|
# CMK_TUI_ACS=1 erzwingt umgekehrt das alte Verhalten - fuer
|
|
# Terminals, die ESC(0 koennen, aber kein UTF-8.
|
|
if [ -n "${CMK_TUI_ACS:-}" ]; then
|
|
unset NCURSES_NO_UTF8_ACS 2>/dev/null || true
|
|
return 0
|
|
fi
|
|
export NCURSES_NO_UTF8_ACS=1
|
|
return 0
|
|
}
|
|
|
|
ui_fix_locale() {
|
|
[ -n "${UI_LOCALE_DONE:-}" ] && return 0
|
|
UI_LOCALE_DONE=1
|
|
UI_LOCALE_NOTE=""
|
|
case "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" in
|
|
*UTF-8*|*utf-8*|*UTF8*|*utf8*) ui_use_unicode_acs; return 0 ;;
|
|
esac
|
|
local l
|
|
for l in C.UTF-8 C.utf8 en_US.UTF-8 en_US.utf8 de_DE.UTF-8 de_DE.utf8; do
|
|
if ( set +o pipefail; locale -a 2>/dev/null | grep -qix "$l" ); then
|
|
# LC_ALL sticht jede einzelne Kategorie aus - solange es auf
|
|
# einem Nicht-UTF-8-Wert steht, bliebe LC_CTYPE wirkungslos.
|
|
if [ -n "${LC_ALL:-}" ]; then unset LC_ALL; fi
|
|
export LC_CTYPE="$l"
|
|
UI_LOCALE_NOTE="LC_CTYPE auf $l gesetzt (war nicht UTF-8)"
|
|
ui_use_unicode_acs
|
|
return 0
|
|
fi
|
|
done
|
|
# Keine UTF-8-Locale vorhanden - dann bleibt nur ASCII
|
|
UI_ASCII=1
|
|
UI_LOCALE_NOTE="keine UTF-8-Locale gefunden - einfache Rahmen"
|
|
return 1
|
|
}
|
|
|
|
# Zusaetzliche Argumente, wenn nur ASCII-Rahmen moeglich sind
|
|
ui_ascii_args() {
|
|
UI_EXTRA=()
|
|
case "${CMK_TUI_ASCII:-}${UI_ASCII:-}" in
|
|
'') return 0 ;;
|
|
esac
|
|
if [ "${UI_BIN:-}" = dialog ] && dialog --ascii-lines --print-version >/dev/null 2>&1; then
|
|
UI_EXTRA=(--ascii-lines)
|
|
elif [ -z "${UI_ASCII_WARNED:-}" ]; then
|
|
UI_ASCII_WARNED=1
|
|
warn "Einfache Rahmen kann nur 'dialog' zeichnen - 'whiptail' kennt
|
|
die Option nicht. Installieren mit:
|
|
dnf install -y dialog (RHEL/Rocky/Alma)
|
|
apt-get install -y dialog (Debian/Ubuntu)"
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
ui_init() {
|
|
ui_fix_locale || true
|
|
|
|
# Geometrie IMMER setzen - sonst laeuft das Skript unter "set -u" auf
|
|
# eine unbound variable, wenn UI_BIN von aussen vorgegeben wurde.
|
|
UI_H="${UI_H:-22}"; UI_W="${UI_W:-78}"; UI_MH="${UI_MH:-13}"
|
|
|
|
if [ -z "${UI_BIN:-}" ]; then
|
|
if command -v dialog >/dev/null 2>&1; then UI_BIN=dialog
|
|
elif command -v whiptail >/dev/null 2>&1; then UI_BIN=whiptail
|
|
else UI_BIN=""; fi
|
|
export DIALOGRC="${DIALOGRC:-}"
|
|
fi
|
|
|
|
# Einfache Rahmen koennen nur mit dialog gezeichnet werden. Ist das
|
|
# gewuenscht und dialog vorhanden, wird es dafuer vorgezogen.
|
|
if [ -n "${CMK_TUI_ASCII:-}${UI_ASCII:-}" ] && command -v dialog >/dev/null 2>&1; then
|
|
UI_BIN=dialog
|
|
fi
|
|
ui_ascii_args
|
|
}
|
|
|
|
ui_available() { ui_init; [ -n "$UI_BIN" ]; }
|
|
|
|
ui_require() {
|
|
ui_available || die "Fuer die TUI wird 'dialog' oder 'whiptail' benoetigt.
|
|
Debian/Ubuntu : apt-get install -y dialog
|
|
RHEL/Rocky : dnf install -y dialog"
|
|
UI_BACKTITLE="Checkmk Stack Manager - Runtime: $RUNTIME"
|
|
}
|
|
|
|
# =====================================================================
|
|
# Alle Dialoge laufen ueber genau drei Engstellen.
|
|
#
|
|
# Damit stimmt die Fenstergroesse automatisch - auch fuer Dialoge, die
|
|
# spaeter dazukommen. NEUE FENSTER BITTE IMMER UEBER DIESE DREI BAUEN
|
|
# und niemals "$UI_BIN" direkt aufrufen; "cmk-manage.sh selftest"
|
|
# prueft das nach.
|
|
#
|
|
# _ui_list Listenfenster (menu, checklist, radiolist) -> ui_calc
|
|
# _ui_box Textfenster (msgbox, yesno, inputbox, ...) -> ui_calc_text
|
|
# _ui_file Dateianzeige (textbox) -> volles Bild
|
|
# =====================================================================
|
|
|
|
# _ui_list WIDGET TITEL TEXT FELDER_JE_EINTRAG EINTRAEGE...
|
|
_ui_list() {
|
|
ui_init
|
|
local widget="$1" title="$2" text="$3" per="$4"; shift 4
|
|
ui_calc "$text" "$per" "$@"
|
|
"$UI_BIN" ${UI_EXTRA[@]+"${UI_EXTRA[@]}"} --backtitle "$UI_BACKTITLE" --title "$title" \
|
|
"$widget" "$text" "$UI_H" "$UI_W" "$UI_MH" "$@" 3>&1 1>&2 2>&3
|
|
}
|
|
|
|
# _ui_box WIDGET TITEL TEXT ZUSATZZEILEN [weitere Argumente]
|
|
# Widgets ohne Rueckgabe (msgbox, yesno, infobox) liefern nur den Status.
|
|
_ui_box() {
|
|
ui_init
|
|
local widget="$1" title="$2" text="$3" extra="$4"; shift 4
|
|
ui_calc_text "$text" "$extra"
|
|
case "$widget" in
|
|
--inputbox|--passwordbox)
|
|
"$UI_BIN" ${UI_EXTRA[@]+"${UI_EXTRA[@]}"} --backtitle "$UI_BACKTITLE" --title "$title" \
|
|
"$widget" "$text" "$UI_H" "$UI_W" "$@" 3>&1 1>&2 2>&3 ;;
|
|
*)
|
|
"$UI_BIN" ${UI_EXTRA[@]+"${UI_EXTRA[@]}"} --backtitle "$UI_BACKTITLE" --title "$title" \
|
|
"$widget" "$text" "$UI_H" "$UI_W" "$@" ;;
|
|
esac
|
|
}
|
|
|
|
# _ui_file TITEL DATEI - nutzt das Terminal so weit wie moeglich aus
|
|
_ui_file() {
|
|
ui_init
|
|
ui_term_size
|
|
# Farbige Ausgaben landen hier als rohe Steuerzeichen (^[[1m ...),
|
|
# weil dialog/whiptail sie nicht auswerten. Die Farbvariablen werden
|
|
# beim Laden dieser Datei einmal festgelegt - ein NO_COLOR beim
|
|
# spaeteren Aufruf wirkt also nicht mehr. Deshalb hier entfernen;
|
|
# das greift fuer JEDE Textanzeige.
|
|
local clean; clean="$(mktemp)"
|
|
sed -E 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\x1b\([AB012]//g' "$2" >"$clean" 2>/dev/null \
|
|
|| cp "$2" "$clean"
|
|
set -- "$1" "$clean"
|
|
"$UI_BIN" ${UI_EXTRA[@]+"${UI_EXTRA[@]}"} --backtitle "$UI_BACKTITLE" --title "$1" \
|
|
--textbox "$2" $(( UI_ROWS - 2 )) $(( UI_COLS - 4 ))
|
|
local rc=$?
|
|
rm -f "$clean"
|
|
return $rc
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Die eigentlichen Dialoge - alle nur noch duenne Huellen
|
|
# ---------------------------------------------------------------------
|
|
ui_menu() { local t="$1" x="$2"; shift 2; _ui_list --menu "$t" "$x" 2 "$@"; }
|
|
ui_checklist() { local t="$1" x="$2"; shift 2; _ui_list --checklist "$t" "$x" 3 "$@"; }
|
|
ui_radiolist() { local t="$1" x="$2"; shift 2; _ui_list --radiolist "$t" "$x" 3 "$@"; }
|
|
|
|
ui_input() { _ui_box --inputbox "$1" "$2" 10 "${3:-}"; }
|
|
ui_password() { _ui_box --passwordbox "$1" "$2" 10; }
|
|
ui_msg() { _ui_box --msgbox "$1" "$2" 7; }
|
|
ui_yesno() { _ui_box --yesno "$1" "$2" 8; }
|
|
ui_info() { _ui_box --infobox "$1" "$2" 5; }
|
|
ui_text() { _ui_file "$1" "$2"; }
|
|
|
|
# Kommando ausfuehren, Ausgabe live in Datei, danach in Textbox anzeigen
|
|
ui_run() { # title cmd...
|
|
ui_init
|
|
local title="$1"; shift
|
|
local logf; logf="$(mktemp)"
|
|
ui_info "$title" "Bitte warten ...
|
|
|
|
$*"
|
|
if "$@" >"$logf" 2>&1; then
|
|
ui_text "$title - erfolgreich" "$logf"
|
|
rm -f "$logf"; return 0
|
|
else
|
|
ui_text "$title - FEHLGESCHLAGEN" "$logf"
|
|
rm -f "$logf"; return 1
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Dateiauswahl im Midnight-Commander-Stil (funktioniert auch mit whiptail)
|
|
# ---------------------------------------------------------------------
|
|
ui_pick_file() { # startdir [glob] -> Dateipfad auf stdout
|
|
ui_init
|
|
local dir="${1:-$PWD}" glob="${2:-*}" sel
|
|
dir="$(cd "$dir" 2>/dev/null && pwd || echo "$PWD")"
|
|
while :; do
|
|
local -a items=()
|
|
items+=(".." "<Verzeichnis hoeher>")
|
|
local d f
|
|
while IFS= read -r d; do
|
|
[ -n "$d" ] && items+=("$d/" "<DIR>")
|
|
done < <(find "$dir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | sort)
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] || continue
|
|
items+=("$f" "$(human_size "$(file_size "$dir/$f")")")
|
|
done < <(find "$dir" -mindepth 1 -maxdepth 1 -type f -name "$glob" -printf '%f\n' 2>/dev/null | sort)
|
|
|
|
sel="$(ui_menu "Datei waehlen" "Verzeichnis: $dir\nFilter: $glob" "${items[@]}")" || return 1
|
|
case "$sel" in
|
|
"..") dir="$(dirname "$dir")" ;;
|
|
*/) dir="$dir/${sel%/}" ;;
|
|
*) printf '%s\n' "$dir/$sel"; return 0 ;;
|
|
esac
|
|
done
|
|
}
|