#!/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 # 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/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" </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" </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) In der .env die passende Checkmk-Version eintragen: CMK_IMAGE_TAG=${version%.*} und den Stack neu erstellen: ./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 <} root@:/opt/checkmk-stack/backups/ 2) Dort importieren: cd /opt/checkmk-stack ./scripts/cmk-manage.sh import --file backups/$(basename "${LAST_BUNDLE:-}") --set-default-site 3) Quellserver: Site stoppen bzw. Server abschalten, damit es keine doppelte Ueberwachung und keinen IP-Konflikt gibt: omd stop && 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