Files
docker-podman-installation-…/scripts/migrate_creator.sh
T
duffyduckandClaude Opus 5 1693330162 Terminal-Browser, konfigurierbarer Admin-Port und Einstellungsmenue
Der NGINX Proxy Manager laesst sich jetzt auch dann einrichten, wenn
seine Oberflaeche auf Port 81 aus dem Netz nicht erreichbar ist.

browser
  Terminal-Browser in einem eigenen Container, gestartet mit
  "run --rm -it --network host" - damit erreicht er 127.0.0.1 genau wie
  der Proxy Manager selbst. Engines: browsh und carbonyl (rendern einen
  echten Browser und koennen JavaScript, das die Adminoberflaeche
  benoetigt) sowie links2/w3m/lynx und eine Shell mit curl/jq/openssl
  fuer Diagnose. Werkzeug-Image unter containers/browser/.
  Bewusst kein Compose-Dienst: ein Browser ist interaktiv, und
  podman-compose behandelt Profile unzuverlaessig.

npm-port
  Port der Adminoberflaeche frei waehlbar. Das Image bindet fest auf 81
  ("listen 81 default;"), eine Umgebungsvariable dafuer gibt es in 2.15.1
  noch nicht. Ein abweichender Port wird deshalb ueber den nginx-
  Erweiterungspunkt /data/nginx/custom/http.conf bereitgestellt. Nur der
  markierte Block wird verwaltet, eigene Eintraege bleiben erhalten.
  Port 81 lauscht weiterhin lokal und ist per Firewall zu sperren.

config
  Alle Werte der .env sind menuegefuehrt bzw. ueber config get/set
  aenderbar - der Editor bleibt als Menuepunkt erhalten. Geprueft wird
  beim Setzen: Portbereich und Kollisionen, Site-Namen, vollqualifizierte
  Image-Namen, installierte Runtime, Zahlen- und URL-Felder.

Fehlerbehebungen
  - ui_*-Funktionen initialisieren sich selbst; eine von aussen gesetzte
    UI_BIN fuehrte unter "set -u" zum Abbruch.
  - Eine nicht installierte CMK_RUNTIME machte das gesamte Werkzeug
    unbenutzbar, auch den Aufruf zum Korrigieren. Jetzt wird gewarnt und
    selbst gesucht.
  - env_set setzt Werte mit Leerzeichen in Anfuehrungszeichen und laesst
    optional leere Werte zu.

README auf 22 Kapitel erweitert: Kapitel 11 (Terminal-Browser inkl.
NPM-API als Fallback fuer den Zertifikats-Upload), Kapitel 12 (Admin-Port)
und Abschnitt 14.3 (Einstellungsmenue).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 10:48:42 +02:00

389 lines
16 KiB
Bash
Executable File

#!/usr/bin/env bash
# =====================================================================
# migrate_creator.sh
#
# Wird auf den QUELLSERVER kopiert (klassische Checkmk-/OMD-Direkt-
# installation auf Bare-Metal oder VM) und dort als root ausgefuehrt.
#
# Aufgabe: eine oder mehrere Sites auswaehlen und daraus ein Migrations-
# Bundle (.tar.gz) erzeugen, das auf dem Zielsystem mit
# ./scripts/cmk-manage.sh import --file <bundle>
# in den Checkmk-Container uebernommen wird.
#
# Die Agents muessen dabei NICHT neu eingerichtet werden, solange
# * der Site-Name unveraendert bleibt und
# * das Zielsystem dieselbe IP-Adresse / denselben DNS-Namen bekommt.
# Die Site-Zertifikate (Agent-Registrierung) sind im omd-Backup enthalten.
#
# Dieses Skript ist bewusst eigenstaendig - es hat ausser bash, tar und
# omd keine Abhaengigkeiten. Fuer die Oberflaeche wird dialog oder
# whiptail genutzt, falls vorhanden.
# =====================================================================
set -euo pipefail
VERSION="1.0"
BUNDLE_DIRNAME="cmkbundle"
OUT_DIR="${PWD}"
declare -a SITES=()
ALL=""; STOP_SITE=""; ASSUME_YES=""; USE_GUI=""; SCP_TARGET=""; LABEL=""
declare -a OMD_OPTS=()
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[ INFO ]%s %s\n' "$C_BLU" "$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; }
usage() {
cat <<'EOF'
migrate_creator.sh - Migrations-Bundle einer Checkmk-Direktinstallation erzeugen
AUFRUF
migrate_creator.sh [Optionen]
OPTIONEN
-l, --list vorhandene Sites anzeigen und beenden
-s, --site SITE Site auswaehlen (mehrfach angebbar)
-a, --all alle Sites sichern
-o, --out VERZEICHNIS Zielverzeichnis fuer die .tar.gz-Bundles (Vorgabe: aktuelles)
--label TEXT Freitext-Kennzeichnung im Dateinamen
--no-rrds Performance-Daten (RRDs) auslassen -> kleineres Archiv
--no-logs Logdateien auslassen
--stop Site waehrend des Backups stoppen (konsistenteste Variante)
--scp ZIEL Bundle anschliessend per scp uebertragen,
z.B. --scp root@10.0.0.5:/opt/checkmk-stack/backups/
-g, --gui ncurses-Oberflaeche (dialog/whiptail)
-y, --yes keine Rueckfragen
-h, --help diese Hilfe
BEISPIELE
./migrate_creator.sh --list
./migrate_creator.sh --gui
./migrate_creator.sh --site prod --stop --out /var/tmp
./migrate_creator.sh --all --no-logs --scp root@10.0.0.5:/opt/checkmk-stack/backups/
EOF
}
# ---------------------------------------------------------------------
# Hilfsfunktionen
# ---------------------------------------------------------------------
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 a; read -r -p "${1:-Fortfahren?} [j/N] " 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}'
}
all_ips() { hostname -I 2>/dev/null | tr ' ' '\n' | sed '/^$/d' | paste -sd, - ; }
list_sites() { ls -1 /omd/sites 2>/dev/null | sed '/^$/d'; }
site_version(){ omd version -b "$1" 2>/dev/null | tr -d '\r'; }
site_state() { local rc=0; omd status --bare "$1" >/dev/null 2>&1 || rc=$?
case "$rc" in 0) echo laeuft ;; 1) echo teilweise ;; *) echo gestoppt ;; esac; }
site_size() { du -sb "/omd/sites/$1" 2>/dev/null | awk '{print $1}'; }
# ---------------------------------------------------------------------
# TUI-Helfer
# ---------------------------------------------------------------------
ui_init() {
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_BACKTITLE="migrate_creator $VERSION - $(hostname)"
}
ui_menu() { local t="$1" x="$2"; shift 2; "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$t" --menu "$x" 20 76 11 "$@" 3>&1 1>&2 2>&3; }
ui_checklist() { local t="$1" x="$2"; shift 2; "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$t" --checklist "$x" 20 76 11 "$@" 3>&1 1>&2 2>&3; }
ui_input() { "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" --inputbox "$2" 10 76 "${3:-}" 3>&1 1>&2 2>&3; }
ui_msg() { "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" --msgbox "$2" 18 76; }
ui_yesno() { "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" --yesno "$2" 14 76; }
ui_text() { "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" --textbox "$2" 22 78; }
ui_info() { "$UI_BIN" --backtitle "$UI_BACKTITLE" --title "$1" --infobox "$2" 8 76; }
# ---------------------------------------------------------------------
# Bundle erzeugen
# ---------------------------------------------------------------------
create_bundle() {
local site="$1"
local version state ts outfile tmp restarted="" sum size
version="$(site_version "$site")"
[ -n "$version" ] || die "Konnte die Version der Site '$site' nicht ermitteln."
state="$(site_state "$site")"
ts="$(date +%Y%m%d-%H%M%S)"
outfile="$OUT_DIR/cmkmigrate_${site}_${version}${LABEL:+_$LABEL}_${ts}.tar.gz"
log "Site '$site' Version $version Status $state Groesse $(human_size "$(site_size "$site")")"
if [ -n "$STOP_SITE" ] && [ "$state" != "gestoppt" ]; then
log "Stoppe Site '$site' ..."
omd stop "$site" >/dev/null
restarted=1
fi
tmp="$(mktemp -d)"
mkdir -p "$tmp/$BUNDLE_DIRNAME/siteinfo"
log "Erzeuge omd-Backup ${OMD_OPTS[*]+(${OMD_OPTS[*]})} - das kann dauern ..."
if ! omd backup ${OMD_OPTS[@]+"${OMD_OPTS[@]}"} "$site" "$tmp/$BUNDLE_DIRNAME/site.tar.gz"; then
[ -n "$restarted" ] && omd start "$site" >/dev/null || true
rm -rf "$tmp"
die "omd backup der Site '$site' ist fehlgeschlagen."
fi
if [ -n "$restarted" ]; then
log "Starte Site '$site' wieder ..."
omd start "$site" >/dev/null || warn "Site '$site' konnte nicht wieder gestartet werden."
fi
# --- Zusatzinformationen (rein informativ, fuer die Nachkontrolle) ---
omd config "$site" show >"$tmp/$BUNDLE_DIRNAME/siteinfo/omd-config.txt" 2>/dev/null || true
omd status --bare "$site" >"$tmp/$BUNDLE_DIRNAME/siteinfo/omd-status.txt" 2>/dev/null || true
ip -4 addr show >"$tmp/$BUNDLE_DIRNAME/siteinfo/ip-addr.txt" 2>/dev/null || true
{
echo "hostname : $(hostname -f 2>/dev/null || hostname)"
echo "ips : $(all_ips)"
echo "kernel : $(uname -r)"
[ -r /etc/os-release ] && (. /etc/os-release && echo "os : $PRETTY_NAME")
echo "omd sites:"; omd sites 2>/dev/null || true
} >"$tmp/$BUNDLE_DIRNAME/siteinfo/system.txt"
# Anzahl registrierter Agent-Verbindungen (nur zur Kontrolle nach dem Import)
if [ -d "/omd/sites/$site/var/agent-receiver" ]; then
find "/omd/sites/$site/var/agent-receiver" -maxdepth 2 -type d 2>/dev/null \
| wc -l >"$tmp/$BUNDLE_DIRNAME/siteinfo/agent-receiver-entries.txt" || true
fi
sum="$(sha256sum "$tmp/$BUNDLE_DIRNAME/site.tar.gz" | awk '{print $1}')"
size="$(file_size "$tmp/$BUNDLE_DIRNAME/site.tar.gz")"
printf '%s site.tar.gz\n' "$sum" >"$tmp/$BUNDLE_DIRNAME/site.tar.gz.sha256"
cat >"$tmp/$BUNDLE_DIRNAME/MANIFEST" <<EOF
BUNDLE_VERSION=1
BUNDLE_TYPE=migrate
SITE=$site
CMK_VERSION=$version
SOURCE_KIND=baremetal
SOURCE_HOST=$(hostname -f 2>/dev/null || hostname)
SOURCE_IP=$(primary_ip)
SOURCE_IPS=$(all_ips)
SOURCE_OS=$( [ -r /etc/os-release ] && (. /etc/os-release && echo "$PRETTY_NAME") || echo unbekannt )
CREATED=$(date -Is)
CREATED_BY=migrate_creator.sh/$VERSION
ARCHIVE=site.tar.gz
ARCHIVE_SHA256=$sum
ARCHIVE_SIZE=$size
OMD_BACKUP_OPTS=${OMD_OPTS[*]-}
LABEL=$LABEL
SITE_STOPPED_DURING_BACKUP=$([ -n "$STOP_SITE" ] && echo yes || echo no)
AGENT_RECEIVER_PORT=8000
EOF
cat >"$tmp/$BUNDLE_DIRNAME/INFO.txt" <<EOF
Checkmk Migrations-Bundle
=========================
Site : $site
Checkmk-Version : $version
Quellserver : $(hostname -f 2>/dev/null || hostname)
Quell-IP(s) : $(all_ips)
Erstellt : $(date -Is)
Nutzdaten : site.tar.gz ($(human_size "$size"))
SHA256 : $sum
Import auf dem Zielsystem (Container-Installation):
1) Bundle in das Verzeichnis ./backups des Projekts kopieren
2) Passendes Image beschaffen (Version MUSS uebereinstimmen):
./scripts/cmk-manage.sh image pull --edition $(
case "$version" in
*.cre) echo raw ;;
*.cee) echo enterprise ;;
*.cce) echo cloud ;;
*.cme) echo managed ;;
*) echo raw ;;
esac) --version ${version%.*}
Die Enterprise-Edition liegt NICHT auf Docker Hub - dort zusaetzlich
--user/--password (Checkmk-Kundenportal) angeben oder
"image download" verwenden.
Danach: ./scripts/cmk-manage.sh up
3) Import ausfuehren:
./scripts/cmk-manage.sh import --file backups/$(basename "$outfile") --set-default-site
Damit die vorhandenen Agents ohne Neuinstallation weiterarbeiten:
* Der Site-Name muss "$site" bleiben (kein --as-site verwenden).
* Das Zielsystem muss die IP/den DNS-Namen des Quellservers uebernehmen.
* Port 8000 (Agent-Receiver) und 443/80 (Web) muessen erreichbar sein.
* Der alte Server sollte danach abgeschaltet bzw. dessen Site gestoppt werden.
EOF
log "Verpacke Bundle ..."
mkdir -p "$OUT_DIR"
tar -czf "$outfile" -C "$tmp" "$BUNDLE_DIRNAME"
rm -rf "$tmp"
ok "Bundle erstellt: $outfile ($(human_size "$(file_size "$outfile")"))"
LAST_BUNDLE="$outfile"
if [ -n "$SCP_TARGET" ]; then
log "Uebertrage per scp nach $SCP_TARGET ..."
scp "$outfile" "$SCP_TARGET" && ok "Uebertragung abgeschlossen." \
|| warn "scp fehlgeschlagen - bitte manuell kopieren."
fi
}
print_summary() {
cat <<EOF
${C_BLD}Weiteres Vorgehen${C_RST}
1) Bundle auf das Zielsystem kopieren, z.B.:
scp ${LAST_BUNDLE:-<bundle>} root@<ziel>:/opt/checkmk-stack/backups/
2) Dort importieren:
cd /opt/checkmk-stack
./scripts/cmk-manage.sh import --file backups/$(basename "${LAST_BUNDLE:-<bundle>}") --set-default-site
3) Quellserver: Site stoppen bzw. Server abschalten, damit es keine
doppelte Ueberwachung und keinen IP-Konflikt gibt:
omd stop <site> && omd disable ... / systemctl poweroff
4) Zielsystem auf die IP/den DNS-Namen des Quellservers umstellen -
dann melden sich die vorhandenen Agents unveraendert weiter.
EOF
}
# ---------------------------------------------------------------------
# GUI-Ablauf
# ---------------------------------------------------------------------
gui_main() {
ui_init
[ -n "$UI_BIN" ] || die "Fuer --gui wird 'dialog' oder 'whiptail' benoetigt.
Debian/Ubuntu : apt-get install -y dialog
RHEL/Rocky : dnf install -y dialog"
local -a items=() s
while IFS= read -r s; do
[ -n "$s" ] || continue
items+=("$s" "$(site_version "$s") | $(site_state "$s") | $(human_size "$(site_size "$s")")" off)
done < <(list_sites)
[ ${#items[@]} -gt 0 ] || die "Auf diesem System wurde keine OMD-Site gefunden."
ui_msg "Checkmk Migration" \
"Dieses Skript erzeugt aus einer Direktinstallation ein Migrations-Bundle
(.tar.gz) fuer den Import in eine Checkmk-Container-Installation.
Server : $(hostname -f 2>/dev/null || hostname)
IP(s) : $(all_ips)
Sites : $(list_sites | tr '\n' ' ')
Die Agents muessen spaeter NICHT neu eingerichtet werden, solange der
Site-Name gleich bleibt und das Zielsystem dieselbe IP uebernimmt."
local chosen
chosen="$(ui_checklist "Sites auswaehlen" "Mit LEERTASTE markieren, mit ENTER bestaetigen:" "${items[@]}")" || exit 0
[ -n "$chosen" ] || { ui_msg "Abbruch" "Es wurde keine Site ausgewaehlt."; exit 0; }
SITES=()
local x
for x in $(printf '%s' "$chosen" | tr -d '"'); do SITES+=("$x"); done
local opt
opt="$(ui_checklist "Optionen" "Optionen fuer das Backup:" \
stop "Site waehrend des Backups stoppen (empfohlen)" on \
no-rrds "Performance-Daten (RRD) auslassen" off \
no-logs "Logdateien auslassen" off)" || exit 0
case "$opt" in *stop*) STOP_SITE=1 ;; esac
case "$opt" in *no-rrds*) OMD_OPTS+=(--no-rrds) ;; esac
case "$opt" in *no-logs*) OMD_OPTS+=(--no-logs) ;; esac
OUT_DIR="$(ui_input "Zielverzeichnis" "Wohin sollen die Bundles geschrieben werden?" "${OUT_DIR}")" || exit 0
LABEL="$(ui_input "Kennzeichnung" "Optionaler Text fuer den Dateinamen (leer = keiner):" "")" || LABEL=""
LABEL="$(printf '%s' "$LABEL" | tr -cd 'A-Za-z0-9._-')"
if ui_yesno "Direkt uebertragen?" "Sollen die Bundles anschliessend per scp auf das Zielsystem kopiert werden?"; then
SCP_TARGET="$(ui_input "scp-Ziel" "Ziel angeben (Benutzer@Host:/Pfad/):" "root@zielserver:/opt/checkmk-stack/backups/")" || SCP_TARGET=""
fi
ui_yesno "Starten" "Jetzt Bundle(s) erzeugen fuer:\n\n ${SITES[*]}\n\nZiel: $OUT_DIR\n$([ -n "$STOP_SITE" ] && echo 'Die Site(s) werden dabei kurz gestoppt!')" || exit 0
local logf; logf="$(mktemp)"
clear
ASSUME_YES=1
local site
for site in "${SITES[@]}"; do
create_bundle "$site" 2>&1 | tee -a "$logf"
done
print_summary | tee -a "$logf" >/dev/null
ui_text "Fertig" "$logf"
rm -f "$logf"
print_summary
}
# ---------------------------------------------------------------------
# Argumente
# ---------------------------------------------------------------------
while [ $# -gt 0 ]; do
case "$1" in
-l|--list) LIST_ONLY=1; shift ;;
-s|--site) SITES+=("$2"); shift 2 ;;
-a|--all) ALL=1; shift ;;
-o|--out) OUT_DIR="$2"; shift 2 ;;
--label) LABEL="$2"; shift 2 ;;
--no-rrds) OMD_OPTS+=(--no-rrds); shift ;;
--no-logs) OMD_OPTS+=(--no-logs); shift ;;
--stop) STOP_SITE=1; shift ;;
--scp) SCP_TARGET="$2"; shift 2 ;;
-g|--gui) USE_GUI=1; shift ;;
-y|--yes) ASSUME_YES=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "Unbekannte Option: $1 (--help fuer Hilfe)" ;;
esac
done
# ---------------------------------------------------------------------
# Vorbedingungen
# ---------------------------------------------------------------------
command -v omd >/dev/null 2>&1 \
|| die "Auf diesem System ist keine OMD-/Checkmk-Direktinstallation gefunden worden ('omd' fehlt)."
[ "$(id -u)" -eq 0 ] || die "Bitte als root ausfuehren (sudo $0 ...)."
if [ -n "${LIST_ONLY:-}" ]; then
printf '%-20s %-18s %-12s %s\n' "SITE" "VERSION" "STATUS" "GROESSE"
while IFS= read -r s; do
[ -n "$s" ] || continue
printf '%-20s %-18s %-12s %s\n' "$s" "$(site_version "$s")" "$(site_state "$s")" "$(human_size "$(site_size "$s")")"
done < <(list_sites)
exit 0
fi
if [ -n "$USE_GUI" ] || { [ ${#SITES[@]} -eq 0 ] && [ -z "$ALL" ] && [ -t 0 ]; }; then
gui_main
exit 0
fi
[ -n "$ALL" ] && mapfile -t SITES < <(list_sites)
[ ${#SITES[@]} -gt 0 ] || die "Keine Site angegeben. --site SITE, --all oder --gui verwenden."
for s in "${SITES[@]}"; do
list_sites | grep -qxF "$s" || die "Site '$s' existiert auf diesem Server nicht."
done
printf '\n%sZu sichernde Sites:%s %s\n' "$C_BLD" "$C_RST" "${SITES[*]}"
printf '%sZielverzeichnis :%s %s\n\n' "$C_BLD" "$C_RST" "$OUT_DIR"
[ -n "$STOP_SITE" ] && warn "Die Site(s) werden waehrend des Backups gestoppt!"
confirm "Jetzt Migrations-Bundle(s) erzeugen?" || die "Abgebrochen."
for s in "${SITES[@]}"; do
create_bundle "$s"
done
print_summary