#!/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 # Defaults CMK_CONTAINER_NAME="${CMK_CONTAINER_NAME:-checkmk}" NPM_CONTAINER_NAME="${NPM_CONTAINER_NAME:-nginx-proxy-manager}" CMK_SITE_ID="${CMK_SITE_ID:-cmk}" BACKUP_DIR="${BACKUP_DIR:-$PROJECT_DIR/backups}" case "$BACKUP_DIR" in /*) : ;; *) BACKUP_DIR="$PROJECT_DIR/${BACKUP_DIR#./}" ;; esac } # .env-Wert dauerhaft setzen env_set() { local key="$1" val="$2" f="${ENV_FILE:-$PROJECT_DIR/.env}" [ -f "$f" ] || die "Keine .env vorhanden - kann '$key' nicht setzen." if grep -qE "^[#[:space:]]*${key}=" "$f"; then sed -i -E "s|^[#[:space:]]*${key}=.*|${key}=${val}|" "$f" else printf '%s=%s\n' "$key" "$val" >>"$f" fi export "$key=$val" } # --------------------------------------------------------------------- # Container-Runtime erkennen (docker / podman) # --------------------------------------------------------------------- detect_runtime() { if [ -n "${CMK_RUNTIME:-}" ]; then command -v "$CMK_RUNTIME" >/dev/null 2>&1 \ || die "CMK_RUNTIME='$CMK_RUNTIME' ist nicht installiert." RUNTIME="$CMK_RUNTIME" 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 # --------------------------------------------------------------------- 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() { 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/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:-?}" ui_init() { [ -n "${UI_BIN:-}" ] && return 0 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 UI_H="${UI_H:-22}"; UI_W="${UI_W:-78}"; UI_MH="${UI_MH:-13}" export DIALOGRC="${DIALOGRC:-}" } 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" } ui_menu() { # title text tag item [tag item ...] -> gewaehlter tag auf stdout local title="$1" text="$2"; shift 2 "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$title" \ --menu "$text" "$UI_H" "$UI_W" "$UI_MH" "$@" 3>&1 1>&2 2>&3 } ui_checklist() { # title text tag item status ... local title="$1" text="$2"; shift 2 "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$title" \ --checklist "$text" "$UI_H" "$UI_W" "$UI_MH" "$@" 3>&1 1>&2 2>&3 } ui_radiolist() { local title="$1" text="$2"; shift 2 "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$title" \ --radiolist "$text" "$UI_H" "$UI_W" "$UI_MH" "$@" 3>&1 1>&2 2>&3 } ui_input() { # title text default "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" \ --inputbox "$2" 10 "$UI_W" "${3:-}" 3>&1 1>&2 2>&3 } ui_password() { "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" \ --passwordbox "$2" 10 "$UI_W" 3>&1 1>&2 2>&3 } ui_msg() { # title text "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" --msgbox "$2" 16 "$UI_W" } ui_yesno() { # title text -> exit 0 = ja "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" --yesno "$2" 14 "$UI_W" } ui_text() { # title file "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" --textbox "$2" "$UI_H" "$UI_W" } ui_info() { # title text (ohne OK-Button) "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" --infobox "$2" 8 "$UI_W" } # Kommando ausfuehren, Ausgabe live in Datei, danach in Textbox anzeigen ui_run() { # title cmd... local title="$1"; shift local logf; logf="$(mktemp)" ui_info "$title" "Bitte warten ...\n\n$*" 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 local dir="${1:-$PWD}" glob="${2:-*}" sel dir="$(cd "$dir" 2>/dev/null && pwd || echo "$PWD")" while :; do local -a items=() items+=(".." "") local d f while IFS= read -r d; do [ -n "$d" ] && items+=("$d/" "") 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 }