Files
docker-podman-installation-…/scripts/migrate_creator.sh
T
2026-08-20 10:33:59 +02:00

379 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) 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 <<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