"Login Succeeded" und trotzdem "unable to copy from source": der
Repository-Pfad enterprise/check-mk-enterprise war geraten, nicht
belegt. Statt weiter zu raten fragt das Werkzeug jetzt nach.
image repos [--edition E] [--registry R] [--user/--password]
fragt zuerst /v2/_catalog ab (sperren viele Registries) und probiert
danach die ueblichen Pfade durch, je Edition und deren Aliasnamen:
<ed>/check-mk-<ed>, check-mk-<ed>, checkmk/check-mk-<ed>,
enterprise/check-mk-<ed>
Gemeldet wird pro Pfad der HTTP-Status samt Tag-Anzahl. Scheitert ein
"image pull" gegen eine private Registry, laeuft die Pruefung von
selbst an und zeigt gleich, was dort liegt.
Wichtig bei der Deutung und deshalb auch so beschriftet: 401 heisst
nicht "vorhanden". Docker Hub antwortet auch auf nicht existierende
Repositories mit 401. Verlaesslich sind nur 200 und 404.
Nebenbei am Versionsarchiv geklaert, dass die Editionsumbenennung erst
mit 2.5 greift:
2.4.0p9 -> check-mk-{raw,enterprise,cloud,managed}-docker-...
2.5.0p13 -> check-mk-{community,pro,ultimate,ultimatemt}-docker-...
Fuer 2.4 ist "--edition enterprise" also richtig.
registry_get_token nimmt jetzt einen frei waehlbaren Scope, damit auch
registry:catalog:* angefordert werden kann.
Gegen Docker Hub verifiziert: findet checkmk/check-mk-raw (2394 Tags)
und checkmk/check-mk-community (534 Tags); gegen registry.checkmk.com
ohne Zugangsdaten sauberer Fehlbericht statt Haenger.
Dokumentiert als Abschnitt 8.8 in beiden Sprachfassungen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4085 lines
165 KiB
Bash
Executable File
4085 lines
165 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# =====================================================================
|
|
# cmk-manage.sh
|
|
# Verwaltung des Checkmk-/NGINX-Proxy-Manager-Stacks:
|
|
# Stack-Steuerung, Backup, Restore und Import (Migration) von Sites.
|
|
#
|
|
# Zwei Bedienarten:
|
|
# 1) Parametergesteuert : ./scripts/cmk-manage.sh backup --site cmk
|
|
# 2) ncurses-Oberflaeche: ./scripts/cmk-manage.sh gui
|
|
#
|
|
# Backups/Bundles sind immer .tar.gz
|
|
# =====================================================================
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
SELF="$SCRIPT_DIR/cmk-manage.sh"
|
|
# shellcheck source=lib/common.sh
|
|
. "$SCRIPT_DIR/lib/common.sh"
|
|
|
|
BUNDLE_DIRNAME="cmkbundle"
|
|
ASSUME_YES=""
|
|
VERBOSE=""
|
|
ENVFILE_OPT=""
|
|
|
|
# =====================================================================
|
|
# Hilfe
|
|
# =====================================================================
|
|
usage() {
|
|
cat <<'EOF'
|
|
cmk-manage.sh - Checkmk Stack Manager (Docker & Podman)
|
|
|
|
AUFRUF
|
|
cmk-manage.sh [globale Optionen] <kommando> [Optionen]
|
|
|
|
GLOBALE OPTIONEN
|
|
--env-file DATEI abweichende .env verwenden
|
|
-y, --yes alle Rueckfragen automatisch mit "ja" beantworten
|
|
-v, --verbose ausfuehrliche Ausgabe
|
|
-h, --help diese Hilfe
|
|
|
|
KOMMANDOS - Oberflaeche
|
|
gui | menu ncurses-Oberflaeche starten (dialog/whiptail)
|
|
|
|
KOMMANDOS - Stack
|
|
up Stack starten (compose up -d)
|
|
down Stack stoppen und Container entfernen
|
|
restart Stack neu starten
|
|
pull Images aktualisieren
|
|
status Status von Containern und Sites anzeigen
|
|
logs [-f] [DIENST] Logs anzeigen
|
|
shell Root-Shell im Checkmk-Container
|
|
omd ARG... beliebiges omd-Kommando im Container ausfuehren
|
|
|
|
KOMMANDOS - Sites
|
|
sites Sites im Container auflisten
|
|
site-start SITE Site starten
|
|
site-stop SITE Site stoppen
|
|
start-all-sites alle vorhandenen Sites starten (fuer Autostart nach Reboot)
|
|
|
|
KOMMANDOS - Backup / Restore / Migration
|
|
backup [Optionen] Backup einer/aller Sites als .tar.gz-Bundle erzeugen
|
|
--site SITE Site (mehrfach moeglich)
|
|
--all alle Sites sichern
|
|
--dir VERZ Zielverzeichnis (Vorgabe: $BACKUP_DIR)
|
|
--label TEXT Freitext-Kennzeichnung im Dateinamen
|
|
--no-rrds Performance-Daten (RRDs) auslassen
|
|
--no-logs Logdateien auslassen
|
|
--stop Site waehrend des Backups stoppen (konsistenter)
|
|
|
|
list vorhandene Backups/Bundles auflisten
|
|
verify --file DATEI Bundle pruefen (Manifest + SHA256)
|
|
|
|
restore [Optionen] Backup wiederherstellen
|
|
--file DATEI Bundle oder rohes omd-Backup (.tar.gz)
|
|
--as-site NAME unter anderem Site-Namen wiederherstellen
|
|
(ACHTUNG: Agents muessen dann neu registriert werden!)
|
|
--reuse vorhandene Site ueberschreiben
|
|
--kill laufende Prozesse der Site beenden
|
|
--no-start Site nach dem Restore nicht starten
|
|
|
|
import [Optionen] Site von einer Direktinstallation uebernehmen
|
|
(Bundle aus migrate_creator.sh)
|
|
--file DATEI Migrations-Bundle (.tar.gz)
|
|
--reuse vorhandene Site ueberschreiben
|
|
--kill laufende Prozesse der Site beenden
|
|
--set-default-site CMK_SITE_ID in der .env auf die importierte Site setzen
|
|
--no-start Site nach dem Import nicht starten
|
|
|
|
prune --keep N nur die N neuesten Backups behalten
|
|
|
|
job list angelegte Backup-Jobs anzeigen
|
|
job status Zeitplan, Cron-Datei und Schalter anzeigen
|
|
job add --name N --cron "15 2 * * *" [Optionen] Job anlegen
|
|
--sites ALL|a,b zu sichernde Sites (Vorgabe ALL)
|
|
--dir VERZ Zielverzeichnis des Jobs (Vorgabe: $BACKUP_DIR)
|
|
--keep N Anzahl Backups, die im Ziel bleiben (0 = alle)
|
|
--no-rrds --no-logs --stop wie bei "backup"
|
|
--disabled angelegt, aber noch nicht eingeplant
|
|
job set NAME [Optionen] vorhandenen Job aendern
|
|
job del NAME Job loeschen
|
|
job enable|disable NAME Job ein-/ausschalten
|
|
job run NAME Job sofort ausfuehren
|
|
job run-all alle aktiven Jobs ausfuehren
|
|
job auto [on|off] automatische Ausfuehrung per cron ein/aus
|
|
|
|
KOMMANDOS - Checkmk-Image beschaffen
|
|
image status konfiguriertes und lokal vorhandene Images anzeigen
|
|
image list lokal vorhandene Checkmk-Images
|
|
image versions [Opt.] verfuegbare Versionen auflisten (zum Kopieren)
|
|
--edition E Edition (Vorgabe: CMK_EDITION)
|
|
--source registry|download|web|all Quelle (Vorgabe all)
|
|
web = oeffentliches Versionsarchiv von checkmk.com,
|
|
funktioniert ohne Zugangsdaten
|
|
--filter TEXT nur Versionen, die TEXT enthalten, z.B. 2.3.0
|
|
--limit N hoechstens N Eintraege (Vorgabe 25), --all fuer alle
|
|
--user U --password P statt der Werte aus der .env
|
|
--bare nur die nackte Liste, ohne Rahmen
|
|
image repos [Opt.] herausfinden, welche Repositories die Registry
|
|
wirklich anbietet (Katalog + uebliche Pfade)
|
|
image test [Opt.] Zugangsdaten pruefen, ohne etwas herunterzuladen
|
|
--edition E Edition, deren Repository geprueft wird
|
|
--version V Version fuer die Pruefung des Downloadportals
|
|
--user U --password P statt der Werte aus der .env
|
|
--only registry|download nur einen Bezugsweg pruefen
|
|
image pull [Opt.] Image aus einer Registry holen
|
|
--edition E raw|community|free|cloud|managed|pro|ultimate|
|
|
ultimatemt|enterprise (Vorgabe: CMK_EDITION)
|
|
--version V z.B. 2.3.0p23 (Vorgabe: CMK_VERSION)
|
|
--registry R abweichende Registry
|
|
--user U --password P Zugangsdaten (Enterprise: Kundenportal)
|
|
--image REF fertige Referenz statt Edition/Version
|
|
--remember CMK_EDITION und CMK_VERSION mit in die .env schreiben
|
|
--no-set .env nicht veraendern
|
|
image download [Opt.] Image-Tarball vom Downloadportal holen und einspielen
|
|
--edition/--version/--user/--password wie oben
|
|
--url URL vollstaendige URL fest vorgeben
|
|
--dir VERZ Ablage (Vorgabe: $IMAGE_DIR)
|
|
--keep Tarball nach dem Einspielen behalten
|
|
--remember CMK_EDITION und CMK_VERSION mit in die .env schreiben
|
|
image load --file F bereits heruntergeladenen Tarball einspielen
|
|
image use REF vorhandenes Image als CMK_IMAGE in die .env eintragen
|
|
|
|
KOMMANDOS - Konfiguration
|
|
config list [--category KAT] alle Einstellungen mit aktuellen Werten
|
|
config get KEY einzelnen Wert ausgeben
|
|
config set KEY WERT Wert setzen (mit Pruefung)
|
|
config edit .env im Editor oeffnen
|
|
npm-port [PORT] Port der Proxy-Manager-Oberflaeche
|
|
(ohne PORT: aktuellen Stand anzeigen)
|
|
npm-enable [on|off] NGINX Proxy Manager ein-/ausschalten.
|
|
"off" stoppt und entfernt den Container,
|
|
die Daten unter ./data/npm bleiben erhalten
|
|
(fuer den Fall, dass ein anderer NGINX die
|
|
HTTPS-Terminierung uebernimmt)
|
|
|
|
KOMMANDOS - Lokale Bedienung ohne externen Zugriff
|
|
browser [Optionen] Terminal-Browser im Container starten, um den
|
|
NGINX Proxy Manager direkt auf der Maschine
|
|
einzurichten (Host-Netzwerk, erreicht 127.0.0.1:81)
|
|
-e, --engine E browsh (Vorgabe) | carbonyl | links | w3m | lynx | shell
|
|
browsh/carbonyl koennen JavaScript - die Textbrowser
|
|
NICHT, damit laesst sich der Proxy Manager nicht bedienen
|
|
-u, --url URL Startseite (Vorgabe: http://127.0.0.1:81)
|
|
--build Werkzeug-Image neu bauen
|
|
--pull Engine-Image neu laden
|
|
|
|
KOMMANDOS - Zertifikat
|
|
cert [Optionen] selbstsigniertes Zertifikat fuer den Proxy erzeugen
|
|
--domain FQDN Common Name / SAN
|
|
--alt NAME zusaetzlicher SAN (mehrfach; DNS-Name oder IP)
|
|
--days N Gueltigkeit in Tagen (Vorgabe 3650)
|
|
|
|
BEISPIELE
|
|
./scripts/cmk-manage.sh up
|
|
./scripts/cmk-manage.sh backup --all --no-logs
|
|
./scripts/cmk-manage.sh import --file backups/cmkmigrate_prod_2.3.0p23_*.tar.gz --set-default-site
|
|
./scripts/cmk-manage.sh image test --edition enterprise --version 2.3.0p23
|
|
./scripts/cmk-manage.sh image pull --edition enterprise --version 2.3.0p23 \
|
|
--user <portal-benutzer> --password <kennwort>
|
|
./scripts/cmk-manage.sh image download --edition enterprise --version 2.3.0p23
|
|
./scripts/cmk-manage.sh image load --file images/check-mk-enterprise-2.3.0p23.docker.tar.gz
|
|
./scripts/cmk-manage.sh npm-port 10443 # Adminoberflaeche auf Port 10443
|
|
./scripts/cmk-manage.sh config set CMK_SITE_ID prod
|
|
./scripts/cmk-manage.sh browser # Proxy Manager lokal bedienen
|
|
./scripts/cmk-manage.sh gui
|
|
EOF
|
|
}
|
|
|
|
# =====================================================================
|
|
# Bundle-Format
|
|
#
|
|
# <name>.tar.gz
|
|
# cmkbundle/MANIFEST key=value, per "source" lesbar
|
|
# cmkbundle/INFO.txt menschenlesbare Zusammenfassung
|
|
# cmkbundle/site.tar.gz das eigentliche "omd backup"-Archiv
|
|
# cmkbundle/site.tar.gz.sha256
|
|
# =====================================================================
|
|
|
|
edition_from_version() {
|
|
case "$1" in
|
|
*.cre) echo "Raw Edition" ;;
|
|
*.cee) echo "Enterprise Edition" ;;
|
|
*.cme) echo "Managed Services Edition" ;;
|
|
*.cce) echo "Cloud Edition" ;;
|
|
*) echo "unbekannt" ;;
|
|
esac
|
|
}
|
|
|
|
# Manifest eines Bundles einlesen -> Variablen B_*
|
|
bundle_read() {
|
|
local file="$1"
|
|
B_SITE=""; B_CMK_VERSION=""; B_SOURCE_KIND=""; B_SOURCE_HOST=""
|
|
B_SOURCE_IP=""; B_CREATED=""; B_ARCHIVE_SHA256=""; B_LABEL=""
|
|
B_BUNDLE_TYPE=""; B_ARCHIVE_SIZE=""; B_OMD_BACKUP_OPTS=""; B_SOURCE_OS=""
|
|
B_IS_BUNDLE=0
|
|
|
|
if tar -tzf "$file" 2>/dev/null | grep -qx "$BUNDLE_DIRNAME/MANIFEST"; then
|
|
B_IS_BUNDLE=1
|
|
local mf; mf="$(mktemp)"
|
|
tar -xzOf "$file" "$BUNDLE_DIRNAME/MANIFEST" >"$mf"
|
|
# nur bekannte Schluessel uebernehmen (kein blindes eval)
|
|
local k v
|
|
while IFS='=' read -r k v; do
|
|
case "$k" in
|
|
SITE) B_SITE="$v" ;;
|
|
CMK_VERSION) B_CMK_VERSION="$v" ;;
|
|
SOURCE_KIND) B_SOURCE_KIND="$v" ;;
|
|
SOURCE_HOST) B_SOURCE_HOST="$v" ;;
|
|
SOURCE_IP) B_SOURCE_IP="$v" ;;
|
|
SOURCE_OS) B_SOURCE_OS="$v" ;;
|
|
CREATED) B_CREATED="$v" ;;
|
|
ARCHIVE_SHA256) B_ARCHIVE_SHA256="$v" ;;
|
|
ARCHIVE_SIZE) B_ARCHIVE_SIZE="$v" ;;
|
|
LABEL) B_LABEL="$v" ;;
|
|
BUNDLE_TYPE) B_BUNDLE_TYPE="$v" ;;
|
|
OMD_BACKUP_OPTS) B_OMD_BACKUP_OPTS="$v" ;;
|
|
esac
|
|
done <"$mf"
|
|
rm -f "$mf"
|
|
else
|
|
# rohes omd-Backup: Site-Name ist die erste Pfadkomponente.
|
|
# Ein omd-Archiv beginnt immer mit "<site>/..." - alles andere ist
|
|
# kein Checkmk-Backup und wird abgewiesen.
|
|
local first
|
|
first="$(tar -tzf "$file" 2>/dev/null | head -n1)"
|
|
case "$first" in
|
|
*/*) B_SITE="${first%%/*}" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
B_BUNDLE_TYPE="raw"
|
|
B_SOURCE_KIND="unbekannt"
|
|
fi
|
|
[ -n "$B_SITE" ] || return 1
|
|
}
|
|
|
|
bundle_show() {
|
|
local file="$1"
|
|
bundle_read "$file" || { echo "Kein gueltiges Checkmk-Backup: $file"; return 1; }
|
|
cat <<EOF
|
|
Datei : $file
|
|
Groesse : $(human_size "$(file_size "$file")")
|
|
Typ : $([ "$B_IS_BUNDLE" = 1 ] && echo "Bundle (${B_BUNDLE_TYPE:-backup})" || echo "rohes omd-Backup")
|
|
Site : $B_SITE
|
|
Checkmk-Version: ${B_CMK_VERSION:-unbekannt} ($(edition_from_version "${B_CMK_VERSION:-x}"))
|
|
Quelle : ${B_SOURCE_KIND:-?} ${B_SOURCE_HOST:+/ $B_SOURCE_HOST}${B_SOURCE_IP:+ ($B_SOURCE_IP)}
|
|
Quell-OS : ${B_SOURCE_OS:-unbekannt}
|
|
Erstellt : ${B_CREATED:-unbekannt}
|
|
Kennzeichnung : ${B_LABEL:-keine}
|
|
omd-Optionen : ${B_OMD_BACKUP_OPTS:-keine}
|
|
EOF
|
|
}
|
|
|
|
bundle_verify() {
|
|
local file="$1"
|
|
[ -f "$file" ] || die "Datei nicht gefunden: $file"
|
|
log "Pruefe Archivstruktur ..."
|
|
tar -tzf "$file" >/dev/null || die "Archiv ist beschaedigt oder kein gueltiges tar.gz."
|
|
bundle_read "$file" || die "Im Archiv wurde keine Checkmk-Site gefunden."
|
|
if [ "$B_IS_BUNDLE" = 1 ] && [ -n "$B_ARCHIVE_SHA256" ]; then
|
|
log "Pruefe SHA256 der Nutzdaten ..."
|
|
local sum
|
|
sum="$(tar -xzOf "$file" "$BUNDLE_DIRNAME/site.tar.gz" | sha256sum | awk '{print $1}')"
|
|
[ "$sum" = "$B_ARCHIVE_SHA256" ] || die "Pruefsumme falsch!
|
|
erwartet : $B_ARCHIVE_SHA256
|
|
ermittelt: $sum"
|
|
ok "Pruefsumme korrekt."
|
|
else
|
|
warn "Kein Manifest/keine Pruefsumme vorhanden - nur Strukturpruefung."
|
|
fi
|
|
bundle_show "$file"
|
|
ok "Bundle ist in Ordnung."
|
|
}
|
|
|
|
# Nutzdaten (omd-Archiv) aus einem Bundle nach stdout schreiben
|
|
bundle_extract_payload() {
|
|
local file="$1"
|
|
if tar -tzf "$file" 2>/dev/null | grep -qx "$BUNDLE_DIRNAME/site.tar.gz"; then
|
|
tar -xzOf "$file" "$BUNDLE_DIRNAME/site.tar.gz"
|
|
else
|
|
cat "$file"
|
|
fi
|
|
}
|
|
|
|
# =====================================================================
|
|
# Checkmk-Image beschaffen
|
|
#
|
|
# Hintergrund:
|
|
# * Die Enterprise-Edition liegt NICHT auf Docker Hub. Dort gibt es nur
|
|
# raw / community / cloud / managed / pro / ultimate / ultimatemt.
|
|
# Enterprise kommt aus registry.checkmk.com (Login) oder als Tarball
|
|
# vom Checkmk-Downloadportal (HTTP-Basic-Auth).
|
|
# * Ein heruntergeladener Image-Tarball wird mit "load" eingespielt,
|
|
# NICHT mit "import". "import" erwartet ein Dateisystem-Archiv und
|
|
# erzeugt ein Image ohne Metadaten (ohne Entrypoint, ohne Tag) -
|
|
# danach "kennt" die Runtime das Image scheinbar nicht.
|
|
# * Nach dem Laden traegt dieses Skript den TATSAECHLICHEN, voll-
|
|
# qualifizierten Namen in CMK_IMAGE der .env ein.
|
|
# =====================================================================
|
|
|
|
edition_repo() { # Edition -> vollqualifiziertes Repository
|
|
case "$1" in
|
|
raw) echo "docker.io/checkmk/check-mk-raw" ;;
|
|
community) echo "docker.io/checkmk/check-mk-community" ;;
|
|
free) echo "docker.io/checkmk/check-mk-free" ;;
|
|
cloud) echo "docker.io/checkmk/check-mk-cloud" ;;
|
|
managed) echo "docker.io/checkmk/check-mk-managed" ;;
|
|
pro) echo "docker.io/checkmk/check-mk-pro" ;;
|
|
ultimate) echo "docker.io/checkmk/check-mk-ultimate" ;;
|
|
ultimatemt) echo "docker.io/checkmk/check-mk-ultimatemt" ;;
|
|
enterprise) echo "${CMK_REGISTRY:-registry.checkmk.com}/enterprise/check-mk-enterprise" ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
edition_known() { edition_repo "$1" >/dev/null 2>&1; }
|
|
|
|
# Braucht diese Edition eine Anmeldung an der Registry?
|
|
edition_needs_login() { [ "$1" = enterprise ]; }
|
|
|
|
edition_pkgname() { echo "check-mk-$1"; }
|
|
|
|
# Lokal vorhandene Checkmk-Images
|
|
image_local_list() {
|
|
$RUNTIME images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null \
|
|
| grep -Ei 'check-?mk' | grep -v '<none>' | sort -u || true
|
|
}
|
|
|
|
image_exists_local() {
|
|
$RUNTIME image inspect "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
image_is_short_name() {
|
|
# Kurzer Name = ohne Registry-Anteil. Podman muesste ihn dann ueber
|
|
# unqualified-search-registries erraten.
|
|
case "$1" in
|
|
*/*) : ;;
|
|
*) return 0 ;; # gar kein "/" -> immer kurz
|
|
esac
|
|
case "${1%%/*}" in
|
|
*.*|*:*|localhost) return 1 ;; # Registry mit Punkt, Port oder localhost
|
|
*) return 0 ;;
|
|
esac
|
|
}
|
|
|
|
image_set_env() {
|
|
local ref="$1"
|
|
env_set CMK_IMAGE "$ref"
|
|
ok "CMK_IMAGE=$ref in der .env eingetragen."
|
|
if image_is_short_name "$ref"; then
|
|
warn "'$ref' ist ein KURZER Name ohne Registry. Podman wuerde ihn ueber
|
|
unqualified-search-registries aufloesen (auf RHEL zuerst
|
|
registry.access.redhat.com). Besser vollqualifiziert angeben."
|
|
fi
|
|
log "Danach den Stack neu erstellen: $SELF up"
|
|
}
|
|
|
|
# Vor/Nachher-Vergleich, um den Namen eines geladenen Images sicher zu finden
|
|
image_snapshot() { $RUNTIME images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | sort -u || true; }
|
|
|
|
# Tarball einspielen -> gibt den geladenen Image-Namen auf stdout aus
|
|
image_load_file() {
|
|
local file="$1"
|
|
[ -f "$file" ] || die "Datei nicht gefunden: $file"
|
|
|
|
# Ist das ueberhaupt ein Container-Image-Archiv (docker-archive/OCI)?
|
|
if ! tar -tzf "$file" 2>/dev/null | grep -qE '^(manifest\.json|oci-layout|index\.json)$'; then
|
|
if tar -tf "$file" 2>/dev/null | grep -qE '^(manifest\.json|oci-layout|index\.json)$'; then
|
|
:
|
|
else
|
|
err "'$file' enthaelt kein manifest.json / oci-layout."
|
|
cat >&2 <<'EOF'
|
|
Das ist damit KEIN Container-Image-Archiv, sondern vermutlich ein
|
|
normales tar-Archiv. Nur "docker save"/"podman save"-Archive bzw. die
|
|
Image-Tarballs von Checkmk lassen sich mit "load" einspielen.
|
|
|
|
Haeufige Verwechslung:
|
|
podman import -> erwartet ein Dateisystem-Archiv (rootfs),
|
|
erzeugt ein Image OHNE Entrypoint und ohne Tag
|
|
podman load -> spielt ein echtes Image inkl. Metadaten ein <-- richtig
|
|
EOF
|
|
return 1
|
|
fi
|
|
fi
|
|
|
|
local before after loaded
|
|
before="$(image_snapshot)"
|
|
log "Spiele Image ein: $RUNTIME load -i $file (das dauert einen Moment) ..." >&2
|
|
local out
|
|
out="$($RUNTIME load -i "$file" 2>&1)" || { printf '%s\n' "$out" >&2; return 1; }
|
|
printf '%s\n' "$out" >&2
|
|
|
|
# 1. Versuch: Ausgabe auswerten
|
|
# docker: "Loaded image: repo:tag" podman: "Loaded image(s): repo:tag"
|
|
loaded="$(printf '%s\n' "$out" | sed -nE 's/^Loaded image(\(s\))?:[[:space:]]*//p' | head -n1 | tr -d '\r')"
|
|
|
|
# 2. Versuch: Differenz der Image-Liste (bevorzugt ein Checkmk-Image)
|
|
if [ -z "$loaded" ] || [ "${loaded#sha256:}" != "$loaded" ]; then
|
|
after="$(image_snapshot)"
|
|
local diff_new
|
|
diff_new="$(comm -13 <(printf '%s\n' "$before") <(printf '%s\n' "$after") | grep -v '<none>')"
|
|
local cand
|
|
cand="$(printf '%s\n' "$diff_new" | grep -Ei 'check-?mk' | head -n1)"
|
|
[ -n "$cand" ] || cand="$(printf '%s\n' "$diff_new" | sed '/^$/d' | head -n1)"
|
|
[ -n "$cand" ] && loaded="$cand"
|
|
fi
|
|
|
|
if [ -z "$loaded" ]; then
|
|
err "Der Name des geladenen Images konnte nicht ermittelt werden."
|
|
err "Bitte manuell nachsehen: $RUNTIME images"
|
|
return 1
|
|
fi
|
|
if [ "${loaded#sha256:}" != "$loaded" ]; then
|
|
err "Das Archiv enthaelt keinen Image-Namen (nur die ID $loaded)."
|
|
err "Manuell benennen: $RUNTIME tag $loaded localhost/checkmk/check-mk-enterprise:<version>"
|
|
return 1
|
|
fi
|
|
printf '%s\n' "$loaded"
|
|
}
|
|
|
|
# Datei mit HTTP-Basic-Auth holen (Zugangsdaten nicht in der Prozessliste)
|
|
image_fetch_url() {
|
|
local url="$1" out="$2" user="$3" pass="$4"
|
|
local host; host="$(printf '%s' "$url" | sed -E 's|^[a-z]+://([^/]+).*|\1|')"
|
|
|
|
if command -v curl >/dev/null 2>&1; then
|
|
local netrc=""
|
|
local -a auth=()
|
|
if [ -n "$user" ]; then
|
|
netrc="$(mktemp)"; chmod 600 "$netrc"
|
|
printf 'machine %s login %s password %s\n' "$host" "$user" "$pass" >"$netrc"
|
|
auth=(--netrc-file "$netrc")
|
|
fi
|
|
log "Lade $url"
|
|
if curl -fL --progress-bar ${auth[@]+"${auth[@]}"} -o "$out" "$url"; then
|
|
[ -n "$netrc" ] && rm -f "$netrc"
|
|
return 0
|
|
fi
|
|
[ -n "$netrc" ] && rm -f "$netrc"
|
|
return 1
|
|
elif command -v wget >/dev/null 2>&1; then
|
|
warn "curl fehlt - verwende wget (die Zugangsdaten sind dabei kurzzeitig
|
|
in der Prozessliste sichtbar)."
|
|
local -a wa=()
|
|
[ -n "$user" ] && wa=(--user="$user" --password="$pass")
|
|
wget ${wa[@]+"${wa[@]}"} -O "$out" "$url"
|
|
return $?
|
|
else
|
|
die "Weder curl noch wget vorhanden."
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Versionsarchiv von checkmk.com
|
|
#
|
|
# https://checkmk.com/download/archive ist serverseitig gerendert und
|
|
# braucht KEINE Zugangsdaten. Je Version steht dort
|
|
# data-handle="2.3.0p9" data-versions="{...}"
|
|
# und im JSON die echten Dateinamen je Edition. Daran haengt auch die
|
|
# Aufloesung der Verwirrung um die Editionsnamen: die Website benennt
|
|
# 2.3.0p9 als "pro", die Datei heisst weiterhin
|
|
# check-mk-enterprise-docker-2.3.0p9.tar.gz
|
|
# ---------------------------------------------------------------------
|
|
|
|
ARCHIVE_CACHE=""
|
|
|
|
archive_fetch() { # Seite einmal je Aufruf holen, Pfad auf stdout
|
|
if [ -n "$ARCHIVE_CACHE" ] && [ -s "$ARCHIVE_CACHE" ]; then
|
|
printf '%s\n' "$ARCHIVE_CACHE"; return 0
|
|
fi
|
|
command -v curl >/dev/null 2>&1 || return 1
|
|
local f; f="$(mktemp)"
|
|
if curl -sS -L --max-time 60 "${CMK_ARCHIVE_URL:-https://checkmk.com/download/archive}" -o "$f" 2>/dev/null \
|
|
&& [ -s "$f" ]; then
|
|
ARCHIVE_CACHE="$f"; printf '%s\n' "$f"; return 0
|
|
fi
|
|
rm -f "$f"; return 1
|
|
}
|
|
|
|
archive_versions() { # alle im Archiv gelisteten Versionen
|
|
local f; f="$(archive_fetch)" || return 1
|
|
grep -oE 'data-handle="[0-9]+\.[0-9]+\.[0-9]+[a-z0-9.]*"' "$f" \
|
|
| sed -E 's/data-handle="([^"]*)"/\1/' | sort -u
|
|
}
|
|
|
|
# Exakten Dateinamen des Docker-Tarballs ermitteln
|
|
archive_docker_file() { # edition version -> Dateiname auf stdout
|
|
local edition="$1" version="$2"
|
|
local f; f="$(archive_fetch)" || return 1
|
|
# Abschnitt dieser Version herausschneiden und die Docker-Dateinamen lesen
|
|
local names
|
|
names="$(grep -oE "data-handle=\"$version\"[^>]*" "$f" \
|
|
| grep -oE "check-mk-[a-z]+-docker-${version}\.tar\.gz" | sort -u)"
|
|
[ -n "$names" ] || return 1
|
|
local ed
|
|
for ed in $(edition_aliases "$edition"); do
|
|
if printf '%s\n' "$names" | grep -qx "check-mk-${ed}-docker-${version}.tar.gz"; then
|
|
printf 'check-mk-%s-docker-%s.tar.gz\n' "$ed" "$version"; return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Dateinamen der Image-Tarballs im Downloadportal.
|
|
# Bestaetigt am echten Server:
|
|
# .../checkmk/2.3.0p9/check-mk-raw-docker-2.3.0p9.tar.gz -> 200
|
|
# .../checkmk/2.3.0p9/check-mk-enterprise-docker-2.3.0p9.tar.gz -> 401 (geschuetzt)
|
|
# .../checkmk/2.3.0p9/check-mk-raw-2.3.0p9.docker.tar.gz -> 404
|
|
# Zusaetzlich die Editionsnamen ab 2.5 mitprobieren, damit derselbe
|
|
# Aufruf auch fuer neuere Versionen funktioniert.
|
|
edition_aliases() {
|
|
case "$1" in
|
|
enterprise) printf '%s\n' enterprise pro ;;
|
|
pro) printf '%s\n' pro enterprise ;;
|
|
raw) printf '%s\n' raw community ;;
|
|
community) printf '%s\n' community raw ;;
|
|
managed) printf '%s\n' managed ultimatemt ;;
|
|
ultimatemt) printf '%s\n' ultimatemt managed ;;
|
|
*) printf '%s\n' "$1" ;;
|
|
esac
|
|
}
|
|
|
|
download_url_candidates() { # edition version -> URLs, beste zuerst
|
|
local edition="$1" version="$2" ed
|
|
local base="${CMK_DOWNLOAD_BASEURL:-https://download.checkmk.com/checkmk}"
|
|
# 1. Wahl: der im Archiv hinterlegte, echte Dateiname
|
|
local exact
|
|
if exact="$(archive_docker_file "$edition" "$version" 2>/dev/null)" && [ -n "$exact" ]; then
|
|
printf '%s/%s/%s
|
|
' "$base" "$version" "$exact"
|
|
fi
|
|
for ed in $(edition_aliases "$edition"); do
|
|
printf '%s/%s/check-mk-%s-docker-%s.tar.gz\n' "$base" "$version" "$ed" "$version"
|
|
done
|
|
# aelteres Schema, zur Sicherheit
|
|
for ed in $(edition_aliases "$edition"); do
|
|
printf '%s/%s/check-mk-%s-%s.docker.tar.gz\n' "$base" "$version" "$ed" "$version"
|
|
done
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# image pull - aus einer Registry holen
|
|
# ---------------------------------------------------------------------
|
|
image_cmd_pull() {
|
|
local edition="${CMK_EDITION:-raw}" version="${CMK_VERSION:-}" registry="${CMK_REGISTRY:-}"
|
|
local user="${CMK_REGISTRY_USER:-}" pass="${CMK_REGISTRY_PASSWORD:-}" ref="" setenv=1
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--edition) edition="$2"; shift 2 ;;
|
|
--version) version="$2"; shift 2 ;;
|
|
--registry) registry="$2"; shift 2 ;;
|
|
--user) user="$2"; shift 2 ;;
|
|
--password) pass="$2"; shift 2 ;;
|
|
--image) ref="$2"; shift 2 ;;
|
|
--no-set) setenv=""; shift ;;
|
|
--remember) remember=1; shift ;;
|
|
*) die "Unbekannte Option fuer 'image pull': $1" ;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$ref" ]; then
|
|
edition_known "$edition" || die "Unbekannte Edition '$edition'.
|
|
Moeglich: raw community free cloud managed pro ultimate ultimatemt enterprise"
|
|
[ -n "$version" ] || die "Keine Version angegeben (--version oder CMK_VERSION)."
|
|
CMK_REGISTRY="${registry:-${CMK_REGISTRY:-registry.checkmk.com}}"
|
|
ref="$(edition_repo "$edition"):$version"
|
|
fi
|
|
|
|
local reg="${ref%%/*}"
|
|
if edition_needs_login "$edition" || [ -n "$user" ]; then
|
|
if [ -z "$user" ]; then
|
|
err "Fuer '$edition' ist eine Anmeldung an $reg noetig."
|
|
cat >&2 <<EOF
|
|
Zugangsdaten (Checkmk-Kundenportal, portal.checkmk.com) in der .env
|
|
hinterlegen oder als Option uebergeben:
|
|
CMK_REGISTRY_USER=...
|
|
CMK_REGISTRY_PASSWORD=...
|
|
bzw. $SELF image pull --edition $edition --version $version \\
|
|
--user <benutzer> --password <kennwort>
|
|
EOF
|
|
return 1
|
|
fi
|
|
log "Melde an $reg an ..."
|
|
printf '%s' "$pass" | $RUNTIME login --username "$user" --password-stdin "$reg" \
|
|
|| die "Anmeldung an $reg fehlgeschlagen."
|
|
ok "Anmeldung erfolgreich."
|
|
fi
|
|
|
|
log "Hole Image: $ref"
|
|
if ! $RUNTIME pull "$ref"; then
|
|
err "Das Image '$ref' konnte nicht geholt werden."
|
|
if [ "$reg" != "docker.io" ] && [ -n "$user" ]; then
|
|
warn "Die Anmeldung hat geklappt, der Pfad oder das Tag aber nicht.
|
|
Ich sehe nach, was diese Registry tatsaechlich anbietet ..."
|
|
image_cmd_repos --edition "$edition" --registry "$reg" \
|
|
--user "$user" --password "$pass" || true
|
|
return 1
|
|
fi
|
|
if [ "$edition" = enterprise ]; then
|
|
cat >&2 <<'EOF'
|
|
|
|
Hinweis: die Enterprise-Edition gibt es NICHT auf Docker Hub.
|
|
Zwei Wege stehen zur Verfuegung:
|
|
a) private Registry:
|
|
cmk-manage.sh image pull --edition enterprise --version 2.3.0p23 \
|
|
--user <portal-benutzer> --password <kennwort>
|
|
b) Tarball vom Downloadportal:
|
|
cmk-manage.sh image download --edition enterprise --version 2.3.0p23 \
|
|
--user <portal-benutzer> --password <kennwort>
|
|
Ab Checkmk 2.5 heisst diese Edition "pro" und liegt frei auf Docker Hub:
|
|
cmk-manage.sh image pull --edition pro --version 2.5.0p11
|
|
EOF
|
|
fi
|
|
return 1
|
|
fi
|
|
ok "Image vorhanden: $ref"
|
|
if [ -n "$remember" ]; then
|
|
env_set CMK_EDITION "$edition"; env_set CMK_VERSION "$version"
|
|
log "CMK_EDITION=$edition und CMK_VERSION=$version uebernommen."
|
|
fi
|
|
[ -n "$setenv" ] && image_set_env "$ref"
|
|
return 0
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# image download - Tarball vom Downloadportal holen und einspielen
|
|
# ---------------------------------------------------------------------
|
|
image_cmd_download() {
|
|
local edition="${CMK_EDITION:-raw}" version="${CMK_VERSION:-}"
|
|
local user="${CMK_DOWNLOAD_USER:-}" pass="${CMK_DOWNLOAD_PASSWORD:-}"
|
|
local url="${CMK_DOWNLOAD_URL:-}" dir="" keep="" setenv=1 remember=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--edition) edition="$2"; shift 2 ;;
|
|
--version) version="$2"; shift 2 ;;
|
|
--url) url="$2"; shift 2 ;;
|
|
--user) user="$2"; shift 2 ;;
|
|
--password) pass="$2"; shift 2 ;;
|
|
--dir) dir="$2"; shift 2 ;;
|
|
--keep) keep=1; shift ;;
|
|
--no-set) setenv=""; shift ;;
|
|
--remember) remember=1; shift ;;
|
|
*) die "Unbekannte Option fuer 'image download': $1" ;;
|
|
esac
|
|
done
|
|
|
|
dir="${dir:-${IMAGE_DIR:-$PROJECT_DIR/images}}"
|
|
case "$dir" in /*) : ;; *) dir="$PROJECT_DIR/${dir#./}" ;; esac
|
|
mkdir -p "$dir"
|
|
|
|
local -a urls=()
|
|
if [ -n "$url" ]; then
|
|
urls=("$url")
|
|
else
|
|
[ -n "$version" ] || die "Keine Version angegeben (--version oder CMK_VERSION)."
|
|
case "$version" in
|
|
*-latest|latest) die "Fuer den Tarball-Download wird eine exakte Version benoetigt,
|
|
z.B. 2.3.0p23 - '$version' funktioniert dort nicht." ;;
|
|
esac
|
|
edition_known "$edition" || die "Unbekannte Edition '$edition'."
|
|
mapfile -t urls < <(download_url_candidates "$edition" "$version")
|
|
fi
|
|
|
|
local target="" u
|
|
for u in "${urls[@]}"; do
|
|
target="$dir/$(basename "$u")"
|
|
if image_fetch_url "$u" "$target" "$user" "$pass"; then
|
|
ok "Heruntergeladen: $target ($(human_size "$(file_size "$target")"))"
|
|
break
|
|
fi
|
|
rm -f "$target"; target=""
|
|
warn "Nicht gefunden/abgelehnt: $u"
|
|
done
|
|
|
|
if [ -z "$target" ]; then
|
|
err "Der Image-Tarball konnte nicht geladen werden."
|
|
cat >&2 <<EOF
|
|
|
|
Bitte pruefen:
|
|
* Zugangsdaten des Downloadportals (CMK_DOWNLOAD_USER/-PASSWORD)
|
|
* exakte Version (z.B. 2.3.0p23)
|
|
* Dateiname/URL - notfalls die vollstaendige URL fest vorgeben:
|
|
$SELF image download --url 'https://download.checkmk.com/checkmk/<version>/<datei>' \\
|
|
--user <benutzer> --password <kennwort>
|
|
* oder die Datei manuell im Kundenportal herunterladen und einspielen:
|
|
$SELF image load --file /pfad/zur/datei.tar.gz
|
|
EOF
|
|
return 1
|
|
fi
|
|
|
|
local loaded
|
|
loaded="$(image_load_file "$target")" || return 1
|
|
ok "Image eingespielt: $loaded"
|
|
if [ -n "$remember" ]; then
|
|
env_set CMK_EDITION "$edition"; env_set CMK_VERSION "$version"
|
|
log "CMK_EDITION=$edition und CMK_VERSION=$version uebernommen."
|
|
fi
|
|
[ -z "$keep" ] && { rm -f "$target"; log "Tarball entfernt (mit --keep behalten)."; }
|
|
[ -n "$setenv" ] && image_set_env "$loaded"
|
|
return 0
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# image load - bereits heruntergeladenen Tarball einspielen
|
|
# ---------------------------------------------------------------------
|
|
image_cmd_load() {
|
|
local file="" setenv=1
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--file) file="$2"; shift 2 ;;
|
|
--no-set) setenv=""; shift ;;
|
|
*) [ -z "$file" ] && { file="$1"; shift; } || die "Unbekannte Option: $1" ;;
|
|
esac
|
|
done
|
|
[ -n "$file" ] || die "--file fehlt."
|
|
local loaded
|
|
loaded="$(image_load_file "$file")" || return 1
|
|
ok "Image eingespielt: $loaded"
|
|
[ -n "$setenv" ] && image_set_env "$loaded"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# image use / list / status
|
|
# ---------------------------------------------------------------------
|
|
image_cmd_use() {
|
|
[ $# -ge 1 ] || die "Image-Referenz fehlt, z.B.:
|
|
$SELF image use localhost/checkmk/check-mk-enterprise:2.3.0p23"
|
|
local ref="$1"
|
|
image_exists_local "$ref" || warn "'$ref' ist lokal (noch) nicht vorhanden."
|
|
image_set_env "$ref"
|
|
}
|
|
|
|
image_cmd_list() {
|
|
printf '%sLokal vorhandene Checkmk-Images%s\n' "$C_BLD" "$C_RST"
|
|
local l n=0
|
|
while IFS= read -r l; do
|
|
[ -n "$l" ] || continue
|
|
if [ "$l" = "${CMK_IMAGE:-}" ]; then
|
|
printf ' %s %s<- laut .env in Verwendung%s\n' "$l" "$C_GRN" "$C_RST"
|
|
else
|
|
printf ' %s\n' "$l"
|
|
fi
|
|
n=$((n+1))
|
|
done < <(image_local_list)
|
|
[ "$n" -eq 0 ] && echo " (keines)"
|
|
return 0
|
|
}
|
|
|
|
# Passt CMK_IMAGE zu CMK_EDITION/CMK_VERSION?
|
|
#
|
|
# CMK_IMAGE ist der einzige Wert, den compose und die systemd-Units
|
|
# verwenden. CMK_EDITION und CMK_VERSION sind nur Eingaben fuer den
|
|
# Downloader. Wer sie aendert, ohne anschliessend ein Image zu holen,
|
|
# betreibt weiter das alte - das faellt sonst nirgends auf.
|
|
#
|
|
# Rueckgabe: 0 = stimmig (oder nicht beurteilbar), 1 = Abweichung.
|
|
# Die Erklaerung geht nach stdout.
|
|
image_consistency_check() {
|
|
local img="${CMK_IMAGE:-}" ed="${CMK_EDITION:-}" ver="${CMK_VERSION:-}"
|
|
[ -n "$img" ] && [ -n "$ed" ] || return 0
|
|
|
|
local repo tag name
|
|
tag="${img##*:}"
|
|
repo="${img%:*}"
|
|
case "$repo" in */*) name="${repo##*/}" ;; *) name="$repo" ;; esac
|
|
# kein Tag angegeben -> nicht beurteilbar
|
|
[ "$tag" = "$img" ] && tag=""
|
|
|
|
local ed_ok="" a
|
|
for a in $(edition_aliases "$ed"); do
|
|
[ "$name" = "check-mk-$a" ] && ed_ok=1
|
|
done
|
|
|
|
local ver_ok=1
|
|
[ -n "$ver" ] && [ -n "$tag" ] && [ "$tag" != "$ver" ] && ver_ok=""
|
|
|
|
[ -n "$ed_ok" ] && [ -n "$ver_ok" ] && return 0
|
|
|
|
printf 'CMK_IMAGE passt nicht zu den eingestellten Vorgaben.\n\n'
|
|
printf ' verwendet wird : %s\n' "$img"
|
|
[ -z "$ed_ok" ] && printf ' Edition : eingestellt "%s", im Image steckt "%s"\n' \
|
|
"$ed" "${name#check-mk-}"
|
|
[ -z "$ver_ok" ] && printf ' Version : eingestellt "%s", im Image steckt "%s"\n' "$ver" "$tag"
|
|
printf '\nCompose und die systemd-Units verwenden ausschliesslich CMK_IMAGE.\n'
|
|
printf 'Edition und Version sind nur Vorgaben fuer den Downloader - sie\n'
|
|
printf 'wirken erst, wenn daraus ein Image geholt wird:\n\n'
|
|
printf ' %s image pull --edition %s --version %s\n' "$SELF" "$ed" "${ver:-<version>}"
|
|
printf ' %s up\n' "$SELF"
|
|
return 1
|
|
}
|
|
|
|
image_cmd_status() {
|
|
local msg
|
|
printf '\n%sKonfiguriertes Image (.env: CMK_IMAGE)%s\n' "$C_BLD" "$C_RST"
|
|
printf ' %s\n' "${CMK_IMAGE:-<nicht gesetzt>}"
|
|
if [ -n "${CMK_IMAGE:-}" ]; then
|
|
if image_exists_local "$CMK_IMAGE"; then
|
|
printf ' Status : %slokal vorhanden%s\n' "$C_GRN" "$C_RST"
|
|
else
|
|
printf ' Status : %snicht lokal vorhanden%s (wird beim Start geholt)\n' "$C_YEL" "$C_RST"
|
|
fi
|
|
if image_is_short_name "$CMK_IMAGE"; then
|
|
printf ' %sACHTUNG%s: kurzer Name ohne Registry - unter Podman fehleranfaellig.\n' "$C_RED" "$C_RST"
|
|
fi
|
|
fi
|
|
if ! msg="$(image_consistency_check)"; then
|
|
printf '\n'
|
|
warn "$msg"
|
|
fi
|
|
|
|
printf '\n%sVorgaben fuer den Downloader%s\n' "$C_BLD" "$C_RST"
|
|
printf ' Edition : %s -> %s\n' "${CMK_EDITION:-raw}" "$(edition_repo "${CMK_EDITION:-raw}" 2>/dev/null || echo '?')"
|
|
printf ' Version : %s\n' "${CMK_VERSION:-<nicht gesetzt>}"
|
|
printf ' Registry : %s (Benutzer: %s)\n' "${CMK_REGISTRY:-registry.checkmk.com}" "${CMK_REGISTRY_USER:-<leer>}"
|
|
printf ' Download : %s (Benutzer: %s)\n' "${CMK_DOWNLOAD_BASEURL:-https://download.checkmk.com/checkmk}" "${CMK_DOWNLOAD_USER:-<leer>}"
|
|
echo
|
|
image_cmd_list
|
|
echo
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Zugangsdaten pruefen
|
|
#
|
|
# Testet, ohne etwas herunterzuladen oder eine Anmeldung zu speichern:
|
|
# 1. Registry (registry.checkmk.com): Docker-Registry-v2-Anmeldung und
|
|
# ob das Konto Zugriff auf das Enterprise-Repository hat.
|
|
# 2. Downloadportal: HTTP-Basic-Anmeldung auf die Image-Tarballs.
|
|
# Die Zugangsdaten gehen per temporaerer .netrc an curl und tauchen
|
|
# damit nicht in der Prozessliste auf.
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Schreibt eine temporaere .netrc fuer die uebergebenen Hosts
|
|
netrc_make() { # user pass host [host ...] -> Pfad auf stdout
|
|
local user="$1" pass="$2"; shift 2
|
|
local f h; f="$(mktemp)"; chmod 600 "$f"
|
|
for h in "$@"; do
|
|
[ -n "$h" ] && printf 'machine %s login %s password %s\n' "$h" "$user" "$pass" >>"$f"
|
|
done
|
|
printf '%s\n' "$f"
|
|
}
|
|
|
|
# HTTP-Status einer URL holen (optional mit .netrc)
|
|
http_code() { # url [netrc] [extra-header]
|
|
local url="$1" netrc="${2:-}" hdr="${3:-}"
|
|
local -a a=(-sS -o /dev/null -w '%{http_code}' --max-time 30)
|
|
[ -n "$netrc" ] && a+=(--netrc-file "$netrc")
|
|
[ -n "$hdr" ] && a+=(-H "$hdr")
|
|
local code
|
|
code="$(curl "${a[@]}" "$url" 2>/dev/null)" || code=000
|
|
printf '%s\n' "${code:-000}"
|
|
}
|
|
|
|
test_result() { # status text [detail]
|
|
case "$1" in
|
|
ok) printf ' %s[ OK ]%s %s\n' "$C_GRN" "$C_RST" "$2" ;;
|
|
fail) printf ' %s[FEHLER]%s %s\n' "$C_RED" "$C_RST" "$2" ;;
|
|
warn) printf ' %s[ WARN ]%s %s\n' "$C_YEL" "$C_RST" "$2" ;;
|
|
*) printf ' [ INFO ] %s\n' "$2" ;;
|
|
esac
|
|
[ -n "${3:-}" ] && printf ' %s\n' "$3" || true
|
|
}
|
|
|
|
# --- Registry pruefen -------------------------------------------------
|
|
# Der Name im Image-Bezeichner ist nicht immer der API-Host:
|
|
# "docker.io" spricht man als "registry-1.docker.io" an.
|
|
registry_api_host() {
|
|
case "$1" in
|
|
docker.io|index.docker.io) echo "registry-1.docker.io" ;;
|
|
*) echo "$1" ;;
|
|
esac
|
|
}
|
|
|
|
test_registry_login() { # reg repo user pass needs_login -> 0 = alles gut
|
|
local reg="$1" repo="$2" user="$3" pass="$4" needs_login="${5:-}"
|
|
local rc=0 anon=""
|
|
local api; api="$(registry_api_host "$reg")"
|
|
[ -z "$user" ] && anon=1
|
|
|
|
printf '\n%sRegistry %s%s\n' "$C_BLD" "$reg" "$C_RST"
|
|
[ "$api" != "$reg" ] && printf ' API-Host: %s\n' "$api"
|
|
|
|
if [ -n "$anon" ] && [ -n "$needs_login" ]; then
|
|
test_result fail "keine Zugangsdaten hinterlegt" \
|
|
"Diese Edition ist nur mit Anmeldung erreichbar. CMK_REGISTRY_USER/-PASSWORD
|
|
setzen oder --user/--password angeben."
|
|
return 1
|
|
fi
|
|
|
|
# 1) Erreichbarkeit
|
|
local hdrf; hdrf="$(mktemp)"
|
|
local code
|
|
code="$(curl -sS -o /dev/null -D "$hdrf" -w '%{http_code}' --max-time 30 "https://$api/v2/" 2>/dev/null)" || code=000
|
|
code="${code:-000}"
|
|
if [ "$code" = "000" ]; then
|
|
test_result fail "nicht erreichbar" "Netzwerk/Proxy/DNS pruefen: https://$api/v2/"
|
|
rm -f "$hdrf"; return 1
|
|
fi
|
|
test_result ok "erreichbar (HTTP $code)"
|
|
|
|
# 2) Authentifizierungsverfahren aus dem WWW-Authenticate-Kopf lesen
|
|
local wa realm service
|
|
wa="$(tr -d '\r' <"$hdrf" | grep -i '^www-authenticate:' | head -n1)"
|
|
rm -f "$hdrf"
|
|
|
|
[ -n "$anon" ] && test_result info "oeffentliches Repository - keine Anmeldung noetig"
|
|
|
|
case "$wa" in
|
|
*[Bb]earer*)
|
|
realm="$(printf '%s' "$wa" | sed -n 's/.*realm="\([^"]*\)".*/\1/p')"
|
|
service="$(printf '%s' "$wa" | sed -n 's/.*service="\([^"]*\)".*/\1/p')"
|
|
[ -n "$realm" ] || { test_result fail "Token-Dienst nicht ermittelbar" "$wa"; return 1; }
|
|
|
|
local realm_host; realm_host="$(printf '%s' "$realm" | sed -E 's|^[a-z]+://([^/]+).*|\1|')"
|
|
local netrc=""
|
|
[ -z "$anon" ] && netrc="$(netrc_make "$user" "$pass" "$api" "$realm_host")"
|
|
local url="$realm?service=$service&scope=repository:$repo:pull"
|
|
local body tcode
|
|
if [ -n "$netrc" ]; then
|
|
body="$(curl -sS --netrc-file "$netrc" --max-time 30 "$url" 2>/dev/null || true)"
|
|
tcode="$(http_code "$url" "$netrc")"
|
|
else
|
|
body="$(curl -sS --max-time 30 "$url" 2>/dev/null || true)"
|
|
tcode="$(http_code "$url")"
|
|
fi
|
|
|
|
if [ "$tcode" = "401" ] || [ "$tcode" = "403" ]; then
|
|
test_result fail "Anmeldung abgelehnt (HTTP $tcode)" \
|
|
"Benutzername/Kennwort des Checkmk-Kundenportals pruefen"
|
|
[ -n "$netrc" ] && rm -f "$netrc"; return 1
|
|
fi
|
|
if [ "$tcode" != "200" ]; then
|
|
test_result fail "Token-Abruf fehlgeschlagen (HTTP $tcode)"
|
|
[ -n "$netrc" ] && rm -f "$netrc"; return 1
|
|
fi
|
|
[ -n "$anon" ] && test_result ok "anonymer Zugriff moeglich" \
|
|
|| test_result ok "Anmeldung akzeptiert"
|
|
|
|
# 3) Zugriff auf das konkrete Repository
|
|
local token
|
|
token="$(printf '%s' "$body" | sed -n 's/.*"token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
|
|
[ -n "$token" ] || token="$(printf '%s' "$body" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
|
|
if [ -n "$token" ]; then
|
|
local lcode
|
|
lcode="$(http_code "https://$api/v2/$repo/tags/list" "" "Authorization: Bearer $token")"
|
|
case "$lcode" in
|
|
200) test_result ok "Zugriff auf '$repo' vorhanden" ;;
|
|
401|403) test_result fail "kein Zugriff auf '$repo' (HTTP $lcode)" \
|
|
"Die Anmeldung stimmt, das Konto hat aber keine Berechtigung fuer dieses Repository (Subskription?)"
|
|
rc=1 ;;
|
|
404) test_result warn "Repository '$repo' nicht gefunden (HTTP 404)" \
|
|
"Pfad pruefen - Anmeldung selbst ist in Ordnung" ;;
|
|
*) test_result warn "Repository-Pruefung nicht eindeutig (HTTP $lcode)" ;;
|
|
esac
|
|
fi
|
|
[ -n "$netrc" ] && rm -f "$netrc"
|
|
;;
|
|
*[Bb]asic*|"")
|
|
local netrc; netrc="$(netrc_make "$user" "$pass" "$api")"
|
|
local bcode; bcode="$(http_code "https://$api/v2/" "$netrc")"
|
|
rm -f "$netrc"
|
|
case "$bcode" in
|
|
200) test_result ok "Anmeldung akzeptiert (Basic)" ;;
|
|
401|403) test_result fail "Anmeldung abgelehnt (HTTP $bcode)"; rc=1 ;;
|
|
*) test_result warn "unklares Ergebnis (HTTP $bcode)"; rc=1 ;;
|
|
esac
|
|
;;
|
|
*)
|
|
test_result warn "unbekanntes Anmeldeverfahren" "$wa"; rc=1 ;;
|
|
esac
|
|
return $rc
|
|
}
|
|
|
|
# Rueckfall ohne curl: Anmeldung ueber die Runtime, in eine Wegwerfdatei
|
|
test_registry_login_runtime() { # reg user pass
|
|
local reg="$1" user="$2" pass="$3" tmp rc=0
|
|
tmp="$(mktemp -d)"
|
|
printf '\n%sRegistry %s (ueber %s)%s\n' "$C_BLD" "$reg" "$RUNTIME" "$C_RST"
|
|
if [ "$RUNTIME" = podman ]; then
|
|
if printf '%s' "$pass" | $RUNTIME login --username "$user" --password-stdin \
|
|
--authfile "$tmp/auth.json" "$reg" >/dev/null 2>&1; then
|
|
test_result ok "Anmeldung akzeptiert"
|
|
else
|
|
test_result fail "Anmeldung abgelehnt"; rc=1
|
|
fi
|
|
else
|
|
if printf '%s' "$pass" | DOCKER_CONFIG="$tmp" $RUNTIME login --username "$user" \
|
|
--password-stdin "$reg" >/dev/null 2>&1; then
|
|
test_result ok "Anmeldung akzeptiert"
|
|
else
|
|
test_result fail "Anmeldung abgelehnt"; rc=1
|
|
fi
|
|
fi
|
|
rm -rf "$tmp"
|
|
return $rc
|
|
}
|
|
|
|
# --- Downloadportal pruefen -------------------------------------------
|
|
test_download_login() { # baseurl edition version user pass
|
|
local base="$1" edition="$2" version="$3" user="$4" pass="$5"
|
|
local host; host="$(printf '%s' "$base" | sed -E 's|^[a-z]+://([^/]+).*|\1|')"
|
|
local rc=1
|
|
|
|
printf '\n%sDownloadportal %s%s\n' "$C_BLD" "$host" "$C_RST"
|
|
|
|
if [ -z "$user" ]; then
|
|
test_result warn "keine Zugangsdaten hinterlegt - uebersprungen" \
|
|
"CMK_DOWNLOAD_USER/-PASSWORD setzen oder --user/--password angeben"
|
|
return 1
|
|
fi
|
|
|
|
local netrc; netrc="$(netrc_make "$user" "$pass" "$host")"
|
|
local -a urls=()
|
|
if [ -n "${CMK_DOWNLOAD_URL:-}" ]; then
|
|
urls=("$CMK_DOWNLOAD_URL")
|
|
elif [ -n "$version" ]; then
|
|
mapfile -t urls < <(download_url_candidates "$edition" "$version")
|
|
else
|
|
urls=("$base/")
|
|
fi
|
|
|
|
local u code seen401="" seen404=""
|
|
for u in "${urls[@]}"; do
|
|
code="$(http_code "$u" "$netrc")"
|
|
case "$code" in
|
|
200) test_result ok "Datei erreichbar (HTTP 200)" "$u"; rc=0; break ;;
|
|
401|403) seen401=1; test_result fail "Anmeldung abgelehnt (HTTP $code)" "$u" ;;
|
|
404) seen404=1 ;;
|
|
000) test_result fail "nicht erreichbar" "$u" ;;
|
|
*) test_result warn "unerwartete Antwort (HTTP $code)" "$u" ;;
|
|
esac
|
|
done
|
|
rm -f "$netrc"
|
|
|
|
if [ "$rc" != 0 ] && [ -z "$seen401" ] && [ -n "$seen404" ]; then
|
|
test_result ok "Anmeldung akzeptiert, aber kein Tarball unter diesem Namen (HTTP 404)"
|
|
printf ' %s\n' "Version pruefen (z.B. 2.3.0p23, kein '-latest') oder"
|
|
printf ' %s\n' "CMK_DOWNLOAD_URL mit der vollstaendigen URL setzen."
|
|
rc=0
|
|
fi
|
|
return $rc
|
|
}
|
|
|
|
image_cmd_test() {
|
|
local edition="${CMK_EDITION:-raw}" version="${CMK_VERSION:-}"
|
|
local ruser="${CMK_REGISTRY_USER:-}" rpass="${CMK_REGISTRY_PASSWORD:-}"
|
|
local duser="${CMK_DOWNLOAD_USER:-}" dpass="${CMK_DOWNLOAD_PASSWORD:-}"
|
|
local reg="${CMK_REGISTRY:-registry.checkmk.com}"
|
|
local only=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--edition) edition="$2"; shift 2 ;;
|
|
--version) version="$2"; shift 2 ;;
|
|
--registry) reg="$2"; shift 2 ;;
|
|
--user) ruser="$2"; duser="$2"; shift 2 ;;
|
|
--password) rpass="$2"; dpass="$2"; shift 2 ;;
|
|
--only) only="$2"; shift 2 ;;
|
|
*) die "Unbekannte Option fuer 'image test': $1" ;;
|
|
esac
|
|
done
|
|
|
|
edition_known "$edition" || die "Unbekannte Edition '$edition'."
|
|
|
|
printf '\n%sPruefe Zugangsdaten%s\n' "$C_BLD" "$C_RST"
|
|
printf ' Edition : %s\n' "$edition"
|
|
printf ' Version : %s\n' "${version:-<nicht gesetzt>}"
|
|
|
|
local rc_reg=1 rc_dl=1 ran_reg="" ran_dl=""
|
|
|
|
if [ -z "$only" ] || [ "$only" = registry ]; then
|
|
ran_reg=1
|
|
local full repo
|
|
CMK_REGISTRY="$reg"
|
|
full="$(edition_repo "$edition")"
|
|
repo="${full#*/}"
|
|
local rhost="${full%%/*}"
|
|
local needs=""
|
|
edition_needs_login "$edition" && needs=1
|
|
if command -v curl >/dev/null 2>&1; then
|
|
test_registry_login "$rhost" "$repo" "$ruser" "$rpass" "$needs" && rc_reg=0
|
|
elif [ -n "$ruser" ]; then
|
|
test_registry_login_runtime "$rhost" "$ruser" "$rpass" && rc_reg=0
|
|
else
|
|
warn "Weder curl noch Zugangsdaten vorhanden - Registry uebersprungen."
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$only" ] || [ "$only" = download ]; then
|
|
ran_dl=1
|
|
if command -v curl >/dev/null 2>&1; then
|
|
test_download_login "${CMK_DOWNLOAD_BASEURL:-https://download.checkmk.com/checkmk}" \
|
|
"$edition" "$version" "$duser" "$dpass" && rc_dl=0
|
|
else
|
|
warn "curl fehlt - Downloadportal kann nicht geprueft werden."
|
|
fi
|
|
fi
|
|
|
|
[ -n "$ran_reg$ran_dl" ] || die "Unbekannter Wert fuer --only: '$only' (registry|download)"
|
|
|
|
echo
|
|
local good=""
|
|
[ -n "$ran_reg" ] && [ "$rc_reg" = 0 ] && good=1
|
|
[ -n "$ran_dl" ] && [ "$rc_dl" = 0 ] && good=1
|
|
if [ -n "$good" ]; then
|
|
ok "Zugangsweg nutzbar."
|
|
[ -n "$ran_reg" ] && [ "$rc_reg" = 0 ] && \
|
|
log "Weiter mit: $SELF image pull --edition $edition --version ${version:-<version>}"
|
|
[ -n "$ran_dl" ] && [ "$rc_dl" = 0 ] && [ "${rc_reg}" != 0 ] && \
|
|
log "Weiter mit: $SELF image download --edition $edition --version ${version:-<version>}"
|
|
echo
|
|
return 0
|
|
fi
|
|
err "Kein geprüfter Bezugsweg ist nutzbar - bitte Zugangsdaten pruefen."
|
|
echo
|
|
return 1
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# image versions - verfuegbare Versionen auflisten
|
|
#
|
|
# Registry : ueber den Docker-Registry-v2-Ablauf /v2/<repo>/tags/list
|
|
# Portal : ueber das Verzeichnis-Listing von download.checkmk.com
|
|
# (nur mit Zugangsdaten lesbar)
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Token fuer eine Registry besorgen ("" = anonym versuchen)
|
|
registry_get_token() { # api repo user pass [scope] -> Token auf stdout
|
|
local api="$1" repo="$2" user="$3" pass="$4" scope="${5:-}"
|
|
[ -n "$scope" ] || scope="repository:$repo:pull"
|
|
local hdrf; hdrf="$(mktemp)"
|
|
curl -sS -o /dev/null -D "$hdrf" --max-time 30 "https://$api/v2/" >/dev/null 2>&1 || true
|
|
local wa realm service
|
|
wa="$(tr -d '\r' <"$hdrf" | grep -i '^www-authenticate:' | head -n1)"
|
|
rm -f "$hdrf"
|
|
case "$wa" in
|
|
*[Bb]earer*) : ;;
|
|
*) return 0 ;; # Basic oder offen - kein Token noetig
|
|
esac
|
|
realm="$(printf '%s' "$wa" | sed -n 's/.*realm="\([^"]*\)".*/\1/p')"
|
|
service="$(printf '%s' "$wa" | sed -n 's/.*service="\([^"]*\)".*/\1/p')"
|
|
[ -n "$realm" ] || return 0
|
|
local realm_host; realm_host="$(printf '%s' "$realm" | sed -E 's|^[a-z]+://([^/]+).*|\1|')"
|
|
local url="$realm?service=$service&scope=$scope"
|
|
local body netrc=""
|
|
if [ -n "$user" ]; then
|
|
netrc="$(netrc_make "$user" "$pass" "$api" "$realm_host")"
|
|
body="$(curl -sS --netrc-file "$netrc" --max-time 30 "$url" 2>/dev/null || true)"
|
|
rm -f "$netrc"
|
|
else
|
|
body="$(curl -sS --max-time 30 "$url" 2>/dev/null || true)"
|
|
fi
|
|
local t
|
|
t="$(printf '%s' "$body" | sed -n 's/.*"token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
|
|
[ -n "$t" ] || t="$(printf '%s' "$body" | sed -n 's/.*"access_token"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
|
|
printf '%s' "$t"
|
|
}
|
|
|
|
registry_tags() { # api repo user pass -> Tags, eine je Zeile
|
|
local api="$1" repo="$2" user="$3" pass="$4"
|
|
local token; token="$(registry_get_token "$api" "$repo" "$user" "$pass")"
|
|
local -a a=(-sS --max-time 45)
|
|
if [ -n "$token" ]; then
|
|
a+=(-H "Authorization: Bearer $token")
|
|
elif [ -n "$user" ]; then
|
|
local netrc; netrc="$(netrc_make "$user" "$pass" "$api")"
|
|
a+=(--netrc-file "$netrc")
|
|
fi
|
|
local body
|
|
body="$(curl "${a[@]}" "https://$api/v2/$repo/tags/list" 2>/dev/null || true)"
|
|
printf '%s' "$body" \
|
|
| tr ',' '\n' \
|
|
| sed -n 's/.*"tags"[[:space:]]*:[[:space:]]*\[[[:space:]]*"\([^"]*\)".*/\1/p;s/^[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
| sed 's/[]["]//g' | sed '/^$/d'
|
|
}
|
|
|
|
download_versions() { # user pass -> Versionsverzeichnisse
|
|
local user="$1" pass="$2"
|
|
local base="${CMK_DOWNLOAD_BASEURL:-https://download.checkmk.com/checkmk}"
|
|
local host; host="$(printf '%s' "$base" | sed -E 's|^[a-z]+://([^/]+).*|\1|')"
|
|
local -a a=(-sS --max-time 45 -L)
|
|
local netrc=""
|
|
if [ -n "$user" ]; then
|
|
netrc="$(netrc_make "$user" "$pass" "$host")"; a+=(--netrc-file "$netrc")
|
|
fi
|
|
local body; body="$(curl "${a[@]}" "$base/" 2>/dev/null || true)"
|
|
[ -n "$netrc" ] && rm -f "$netrc"
|
|
printf '%s' "$body" \
|
|
| grep -oE 'href="[0-9]+\.[0-9]+\.[0-9]+[a-z0-9.]*/?"' \
|
|
| sed -E 's|href="([^"/]+)/?"|\1|' | sort -u
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Welches Repository heisst in der Registry wie?
|
|
#
|
|
# Der Pfad ist nicht zu erraten - "Login Succeeded" heisst noch lange
|
|
# nicht, dass enterprise/check-mk-enterprise existiert. Deshalb erst den
|
|
# Katalog fragen und, falls der gesperrt ist, die ueblichen Pfade
|
|
# durchprobieren. HTTP 404 = gibt es nicht, 401/403 = keine Berechtigung,
|
|
# 200 = nutzbar.
|
|
# ---------------------------------------------------------------------
|
|
registry_catalog() { # api user pass -> Repository-Namen
|
|
local api="$1" user="$2" pass="$3"
|
|
local token; token="$(registry_get_token "$api" "" "$user" "$pass" "registry:catalog:*")"
|
|
local -a a=(-sS --max-time 45)
|
|
if [ -n "$token" ]; then a+=(-H "Authorization: Bearer $token")
|
|
elif [ -n "$user" ]; then
|
|
local netrc; netrc="$(netrc_make "$user" "$pass" "$api")"; a+=(--netrc-file "$netrc")
|
|
fi
|
|
curl "${a[@]}" "https://$api/v2/_catalog?n=1000" 2>/dev/null \
|
|
| tr ',' '\n' | grep -oE '"[a-z0-9][a-z0-9._/-]*"' | tr -d '"' \
|
|
| grep -v '^repositories$' | sed '/^$/d' | sort -u
|
|
}
|
|
|
|
registry_repo_candidates() { # edition -> moegliche Repository-Pfade
|
|
local edition="$1" a
|
|
for a in $(edition_aliases "$edition"); do
|
|
printf '%s/check-mk-%s\n' "$a" "$a"
|
|
printf 'check-mk-%s\n' "$a"
|
|
printf 'checkmk/check-mk-%s\n' "$a"
|
|
printf 'enterprise/check-mk-%s\n' "$a"
|
|
done
|
|
}
|
|
|
|
# Status eines Repositories ermitteln: 200 / 401 / 403 / 404 / 000
|
|
registry_repo_status() { # api repo user pass -> "code anzahl_tags"
|
|
local api="$1" repo="$2" user="$3" pass="$4"
|
|
local token; token="$(registry_get_token "$api" "$repo" "$user" "$pass")"
|
|
local -a a=(-sS -o /tmp/.cmkrepo.$$ -w '%{http_code}' --max-time 30)
|
|
[ -n "$token" ] && a+=(-H "Authorization: Bearer $token")
|
|
local code
|
|
code="$(curl "${a[@]}" "https://$api/v2/$repo/tags/list" 2>/dev/null)" || code=000
|
|
local n=0
|
|
[ "${code:-000}" = 200 ] && n="$(tr ',' '\n' <"/tmp/.cmkrepo.$$" 2>/dev/null | grep -c '"' || echo 0)"
|
|
rm -f "/tmp/.cmkrepo.$$"
|
|
printf '%s %s\n' "${code:-000}" "$n"
|
|
}
|
|
|
|
image_cmd_repos() {
|
|
local edition="${CMK_EDITION:-enterprise}" reg="${CMK_REGISTRY:-registry.checkmk.com}"
|
|
local user="${CMK_REGISTRY_USER:-}" pass="${CMK_REGISTRY_PASSWORD:-}"
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--edition) edition="$2"; shift 2 ;;
|
|
--registry) reg="$2"; shift 2 ;;
|
|
--user) user="$2"; shift 2 ;;
|
|
--password) pass="$2"; shift 2 ;;
|
|
*) die "Unbekannte Option fuer 'image repos': $1" ;;
|
|
esac
|
|
done
|
|
command -v curl >/dev/null 2>&1 || die "Fuer 'image repos' wird curl benoetigt."
|
|
local api; api="$(registry_api_host "$reg")"
|
|
|
|
printf '\n%sRegistry %s%s\n' "$C_BLD" "$reg" "$C_RST"
|
|
[ -n "$user" ] || warn "Keine Zugangsdaten hinterlegt - ohne Anmeldung ist hier wenig zu sehen."
|
|
|
|
printf '\n%sKatalog (/v2/_catalog)%s\n' "$C_BLD" "$C_RST"
|
|
local -a cat=()
|
|
mapfile -t cat < <(registry_catalog "$api" "$user" "$pass" | grep -iE 'check|mk' | head -50)
|
|
if [ ${#cat[@]} -gt 0 ]; then
|
|
printf ' %s\n' "${cat[@]}"
|
|
else
|
|
printf ' (nicht abrufbar - viele Registries sperren den Katalog)\n'
|
|
fi
|
|
|
|
printf '\n%sUebliche Pfade fuer "%s" durchprobiert%s\n' "$C_BLD" "$edition" "$C_RST"
|
|
printf ' %-6s %-8s %s\n' "STATUS" "TAGS" "PFAD"
|
|
printf ' %s200 = nutzbar, 404 = gibt es nicht, 401/403 = kein Zugriff%s\n' "$C_BLU" "$C_RST"
|
|
local cand code n line ok=""
|
|
while IFS= read -r cand; do
|
|
[ -n "$cand" ] || continue
|
|
line="$(registry_repo_status "$api" "$cand" "$user" "$pass")"
|
|
code="${line%% *}"; n="${line##* }"
|
|
case "$code" in
|
|
200) printf ' %s%-6s%s %-8s %s\n' "$C_GRN" "$code" "$C_RST" "$n" "$cand"
|
|
[ -z "$ok" ] && ok="$cand" ;;
|
|
401|403) printf ' %s%-6s%s %-8s %s (kein Zugriff - oder gibt es nicht;\n%38sviele Registries verbergen das)\n' \
|
|
"$C_YEL" "$code" "$C_RST" "-" "$cand" "" ;;
|
|
404) printf ' %-6s %-8s %s (gibt es nicht)\n' "$code" "-" "$cand" ;;
|
|
*) printf ' %-6s %-8s %s\n' "${code:-000}" "-" "$cand" ;;
|
|
esac
|
|
done < <(registry_repo_candidates "$edition" | awk '!seen[$0]++')
|
|
|
|
echo
|
|
if [ -n "$ok" ]; then
|
|
ok "Nutzbar: $reg/$ok"
|
|
cat <<EOF
|
|
|
|
${C_BLD}Weiter${C_RST}
|
|
Tags ansehen : $SELF image versions --source registry --edition $edition
|
|
Image setzen : $SELF image use $reg/$ok:<version>
|
|
EOF
|
|
else
|
|
err "Unter '$reg' wurde kein nutzbares Repository fuer '$edition' gefunden."
|
|
cat <<EOF
|
|
|
|
Moegliche Gruende:
|
|
* Die Subskription schliesst den Registry-Zugang nicht ein.
|
|
Dann fuehrt der Tarball-Weg zum Ziel:
|
|
$SELF image download --edition $edition --version <version>
|
|
* Der Pfad heisst anders. Steht er im Kundenportal, laesst er sich
|
|
direkt setzen:
|
|
$SELF config set CMK_REGISTRY <registry>
|
|
$SELF image use <registry>/<pfad>:<version>
|
|
EOF
|
|
fi
|
|
echo
|
|
}
|
|
|
|
image_cmd_versions() {
|
|
local edition="${CMK_EDITION:-raw}" source="all" filter="" limit=25
|
|
local ruser="${CMK_REGISTRY_USER:-}" rpass="${CMK_REGISTRY_PASSWORD:-}"
|
|
local duser="${CMK_DOWNLOAD_USER:-}" dpass="${CMK_DOWNLOAD_PASSWORD:-}"
|
|
local reg="${CMK_REGISTRY:-registry.checkmk.com}" raw=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--edition) edition="$2"; shift 2 ;;
|
|
--source) source="$2"; shift 2 ;;
|
|
--filter) filter="$2"; shift 2 ;;
|
|
--limit) limit="$2"; shift 2 ;;
|
|
--all) limit=100000; shift ;;
|
|
--registry) reg="$2"; shift 2 ;;
|
|
--user) ruser="$2"; duser="$2"; shift 2 ;;
|
|
--password) rpass="$2"; dpass="$2"; shift 2 ;;
|
|
--bare) raw=1; shift ;;
|
|
*) die "Unbekannte Option fuer 'image versions': $1" ;;
|
|
esac
|
|
done
|
|
edition_known "$edition" || die "Unbekannte Edition '$edition'.
|
|
Moeglich: raw community free cloud managed pro ultimate ultimatemt enterprise"
|
|
case "$source" in registry|download|web|both|all) : ;;
|
|
*) die "--source: registry | download | web | all" ;; esac
|
|
[ "$source" = both ] && source=all
|
|
case "$limit" in ''|*[!0-9]*) die "--limit braucht eine Zahl." ;; esac
|
|
|
|
command -v curl >/dev/null 2>&1 || die "Fuer 'image versions' wird curl benoetigt."
|
|
|
|
CMK_REGISTRY="$reg"
|
|
local full repo api
|
|
full="$(edition_repo "$edition")"
|
|
repo="${full#*/}"
|
|
api="$(registry_api_host "${full%%/*}")"
|
|
|
|
local found=0
|
|
|
|
if [ "$source" = web ] || [ "$source" = all ]; then
|
|
[ -n "$raw" ] || printf '\n%sVersionsarchiv %s%s\n' "$C_BLD" \
|
|
"${CMK_ARCHIVE_URL:-https://checkmk.com/download/archive}" "$C_RST"
|
|
[ -n "$raw" ] || printf ' %s(oeffentlich, keine Zugangsdaten noetig)%s\n' "$C_BLU" "$C_RST"
|
|
local -a wv=()
|
|
mapfile -t wv < <(archive_versions 2>/dev/null \
|
|
| { [ -n "$filter" ] && grep -F "$filter" || cat; } \
|
|
| sort -rV | head -n "$limit")
|
|
if [ ${#wv[@]} -eq 0 ]; then
|
|
[ -n "$raw" ] || test_result warn "Archiv nicht lesbar" "Netzzugang zu checkmk.com pruefen"
|
|
else
|
|
found=1
|
|
printf '%s\n' "${wv[@]}" | { [ -n "$raw" ] && cat || sed 's/^/ /'; }
|
|
fi
|
|
fi
|
|
|
|
if [ "$source" = registry ] || [ "$source" = all ]; then
|
|
[ -n "$raw" ] || printf '\n%sRegistry %s%s\n' "$C_BLD" "$full" "$C_RST"
|
|
local -a tags=()
|
|
mapfile -t tags < <(registry_tags "$api" "$repo" "$ruser" "$rpass" \
|
|
| { [ -n "$filter" ] && grep -F "$filter" || cat; } \
|
|
| sort -rV | head -n "$limit")
|
|
if [ ${#tags[@]} -eq 0 ]; then
|
|
if [ -z "$raw" ]; then
|
|
if edition_needs_login "$edition" && [ -z "$ruser" ]; then
|
|
test_result warn "keine Zugangsdaten - Registry nicht lesbar" \
|
|
"CMK_REGISTRY_USER/-PASSWORD setzen oder --user/--password angeben"
|
|
else
|
|
test_result warn "keine Tags erhalten" "Zugangsdaten und Repository pruefen"
|
|
fi
|
|
fi
|
|
else
|
|
found=1
|
|
printf '%s\n' "${tags[@]}" | { [ -n "$raw" ] && cat || sed 's/^/ /'; }
|
|
fi
|
|
fi
|
|
|
|
if [ "$source" = download ] || [ "$source" = all ]; then
|
|
[ -n "$raw" ] || printf '\n%sDownloadportal %s%s\n' "$C_BLD" \
|
|
"${CMK_DOWNLOAD_BASEURL:-https://download.checkmk.com/checkmk}" "$C_RST"
|
|
local -a vers=()
|
|
mapfile -t vers < <(download_versions "$duser" "$dpass" \
|
|
| { [ -n "$filter" ] && grep -F "$filter" || cat; } \
|
|
| sort -rV | head -n "$limit")
|
|
if [ ${#vers[@]} -eq 0 ]; then
|
|
if [ -z "$raw" ]; then
|
|
if [ -z "$duser" ]; then
|
|
test_result warn "keine Zugangsdaten - das Verzeichnis ist geschuetzt (HTTP 401)" \
|
|
"CMK_DOWNLOAD_USER/-PASSWORD setzen oder --user/--password angeben"
|
|
else
|
|
test_result warn "keine Versionen gelesen" \
|
|
"Zugangsdaten pruefen: $SELF image test --only download"
|
|
fi
|
|
fi
|
|
else
|
|
found=1
|
|
printf '%s\n' "${vers[@]}" | { [ -n "$raw" ] && cat || sed 's/^/ /'; }
|
|
fi
|
|
fi
|
|
|
|
[ -n "$raw" ] && return 0
|
|
echo
|
|
if [ "$found" = 1 ]; then
|
|
cat <<EOF
|
|
${C_BLD}Uebernehmen${C_RST}
|
|
aus der Registry : $SELF image pull --edition $edition --version <version>
|
|
als Tarball : $SELF image download --edition $edition --version <version>
|
|
nur eintragen : $SELF config set CMK_VERSION <version>
|
|
|
|
Nur die nackte Liste (zum Weiterverarbeiten): $SELF image versions --bare
|
|
EOF
|
|
else
|
|
err "Es konnte keine Versionsliste gelesen werden."
|
|
log "Zugangsdaten pruefen mit: $SELF image test"
|
|
fi
|
|
echo
|
|
[ "$found" = 1 ]
|
|
}
|
|
|
|
cmd_image() {
|
|
local sub="${1:-status}"; [ $# -gt 0 ] && shift || true
|
|
case "$sub" in
|
|
status) image_cmd_status ;;
|
|
list|ls) image_cmd_list ;;
|
|
pull) image_cmd_pull "$@" ;;
|
|
download) image_cmd_download "$@" ;;
|
|
load) image_cmd_load "$@" ;;
|
|
use|set) image_cmd_use "$@" ;;
|
|
test|check) image_cmd_test "$@" ;;
|
|
versions|tags) image_cmd_versions "$@" ;;
|
|
repos|repo) image_cmd_repos "$@" ;;
|
|
*) die "Unbekanntes Unterkommando 'image $sub'.
|
|
Moeglich: status | list | versions | repos | test | pull | download | load | use" ;;
|
|
esac
|
|
}
|
|
|
|
# =====================================================================
|
|
# Stack-Kommandos
|
|
# =====================================================================
|
|
# Welche compose-Dienste sollen laufen? Der Proxy Manager ist abschaltbar,
|
|
# damit ein bereits vorhandener NGINX auf einem anderen System genutzt
|
|
# werden kann. Abgeschaltet wird sein Container gar nicht erst angelegt.
|
|
compose_services() {
|
|
if npm_enabled; then
|
|
printf '%s\n' checkmk nginx-proxy-manager
|
|
else
|
|
printf '%s\n' checkmk
|
|
fi
|
|
}
|
|
|
|
cmd_up() {
|
|
[ -f "$PROJECT_DIR/.env" ] || die "Keine .env vorhanden. Bitte: cp .env.example .env && \$EDITOR .env"
|
|
local -a svc=(); mapfile -t svc < <(compose_services)
|
|
if npm_enabled; then
|
|
log "Starte Stack (${RUNTIME}) ..."
|
|
else
|
|
log "Starte Stack (${RUNTIME}) - ohne NGINX Proxy Manager (NPM_ENABLED=off) ..."
|
|
fi
|
|
compose up -d "${svc[@]}"
|
|
npm_enabled || npm_teardown_container
|
|
ok "Stack gestartet."
|
|
cmd_status
|
|
}
|
|
|
|
cmd_down() { log "Stoppe Stack ..."; compose down; ok "Stack gestoppt."; }
|
|
cmd_restart() {
|
|
compose down || true
|
|
local -a svc=(); mapfile -t svc < <(compose_services)
|
|
compose up -d "${svc[@]}"
|
|
npm_enabled || npm_teardown_container
|
|
ok "Stack neu gestartet."
|
|
}
|
|
cmd_pull() {
|
|
local -a svc=(); mapfile -t svc < <(compose_services)
|
|
compose pull "${svc[@]}"
|
|
ok "Images aktualisiert. Danach: $SELF up"
|
|
}
|
|
cmd_logs() { compose logs "$@"; }
|
|
cmd_shell() { require_cmk_running; $RUNTIME exec -it "$CMK_CONTAINER_NAME" bash; }
|
|
cmd_omd() { require_cmk_running; $RUNTIME exec -it "$CMK_CONTAINER_NAME" omd "$@"; }
|
|
|
|
cmd_status() {
|
|
local cmsg
|
|
printf '\n%sImage%s\n' "$C_BLD" "$C_RST"
|
|
printf ' %-24s %s\n' "${CMK_IMAGE:-<nicht gesetzt>}" \
|
|
"$(image_exists_local "${CMK_IMAGE:-}" && echo "lokal vorhanden" || echo "${C_YEL}nicht lokal vorhanden${C_RST}")"
|
|
|
|
if ! cmsg="$(image_consistency_check)"; then
|
|
printf ' %sAchtung%s: CMK_IMAGE passt nicht zu CMK_EDITION/CMK_VERSION.\n' "$C_YEL" "$C_RST"
|
|
printf ' Einzelheiten: %s image status\n' "$SELF"
|
|
fi
|
|
|
|
printf '\n%sContainer%s\n' "$C_BLD" "$C_RST"
|
|
local c
|
|
for c in "$CMK_CONTAINER_NAME" "$NPM_CONTAINER_NAME"; do
|
|
if [ "$c" = "$NPM_CONTAINER_NAME" ] && ! npm_enabled; then
|
|
printf ' %-24s %s\n' "$c" "${C_YEL}abgeschaltet (NPM_ENABLED=off)${C_RST}"
|
|
continue
|
|
fi
|
|
if container_exists "$c"; then
|
|
printf ' %-24s %s\n' "$c" \
|
|
"$(container_running "$c" && echo "${C_GRN}laeuft${C_RST}" || echo "${C_RED}gestoppt${C_RST}")"
|
|
else
|
|
printf ' %-24s %s\n' "$c" "${C_YEL}nicht vorhanden${C_RST}"
|
|
fi
|
|
done
|
|
|
|
if container_running "$CMK_CONTAINER_NAME"; then
|
|
printf '\n%sSites im Container%s\n' "$C_BLD" "$C_RST"
|
|
printf ' %-20s %-18s %s\n' "SITE" "VERSION" "STATUS"
|
|
local s
|
|
while IFS= read -r s; do
|
|
[ -n "$s" ] || continue
|
|
printf ' %-20s %-18s %s\n' "$s" "$(cmk_site_version "$s")" "$(cmk_site_state_text "$s")"
|
|
done < <(cmk_sites)
|
|
printf '\n Standard-Site laut .env : %s\n' "$CMK_SITE_ID"
|
|
fi
|
|
|
|
printf '\n%sZugang%s\n' "$C_BLD" "$C_RST"
|
|
local ip; ip="$(primary_ip)"
|
|
printf ' Checkmk (direkt) : http://%s:5000/%s/\n' "$ip" "$CMK_SITE_ID"
|
|
printf ' Agent-Receiver : %s:8000\n' "$ip"
|
|
if npm_enabled; then
|
|
printf ' Checkmk (ueber Proxy) : https://<FQDN>/%s/\n' "$CMK_SITE_ID"
|
|
printf ' NGINX Proxy Manager : http://%s:%s/ (Erstlogin: admin@example.com / changeme)\n\n' "$ip" "${NPM_ADMIN_PORT:-81}"
|
|
else
|
|
printf ' NGINX Proxy Manager : abgeschaltet\n'
|
|
printf ' Externer Proxy weiterleiten auf http://%s:5000\n\n' "$ip"
|
|
fi
|
|
}
|
|
|
|
# =====================================================================
|
|
# Site-Kommandos
|
|
# =====================================================================
|
|
cmd_sites() {
|
|
require_cmk_running
|
|
printf '%-20s %-18s %s\n' "SITE" "VERSION" "STATUS"
|
|
local s
|
|
while IFS= read -r s; do
|
|
[ -n "$s" ] || continue
|
|
printf '%-20s %-18s %s\n' "$s" "$(cmk_site_version "$s")" "$(cmk_site_state_text "$s")"
|
|
done < <(cmk_sites)
|
|
}
|
|
|
|
cmd_site_start() {
|
|
require_cmk_running
|
|
[ $# -ge 1 ] || die "Site-Name fehlt."
|
|
ct omd start "$1" && ok "Site '$1' gestartet."
|
|
}
|
|
|
|
cmd_site_stop() {
|
|
require_cmk_running
|
|
[ $# -ge 1 ] || die "Site-Name fehlt."
|
|
ct omd stop "$1" && ok "Site '$1' gestoppt."
|
|
}
|
|
|
|
# Wird vom systemd-Unit nach dem Start aufgerufen: alle Sites hochfahren.
|
|
# (Der Container-Entrypoint startet nur CMK_SITE_ID.)
|
|
cmd_start_all_sites() {
|
|
local tries=0
|
|
while ! container_running "$CMK_CONTAINER_NAME"; do
|
|
tries=$((tries+1)); [ "$tries" -gt 60 ] && die "Container '$CMK_CONTAINER_NAME' startet nicht."
|
|
sleep 2
|
|
done
|
|
# kurz warten, bis der Entrypoint durch ist
|
|
sleep 5
|
|
local s
|
|
while IFS= read -r s; do
|
|
[ -n "$s" ] || continue
|
|
if [ "$(cmk_site_state "$s")" != "0" ]; then
|
|
log "Starte Site '$s' ..."
|
|
ct omd start "$s" || warn "Site '$s' konnte nicht gestartet werden."
|
|
else
|
|
debug "Site '$s' laeuft bereits."
|
|
fi
|
|
done < <(cmk_sites)
|
|
ok "Alle Sites geprueft/gestartet."
|
|
}
|
|
|
|
# =====================================================================
|
|
# Backup
|
|
# =====================================================================
|
|
cmd_backup() {
|
|
local -a sites=() opts=()
|
|
local all="" dir="" label="" stop_site=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--site) sites+=("$2"); shift 2 ;;
|
|
--all) all=1; shift ;;
|
|
--dir) dir="$2"; shift 2 ;;
|
|
--label) label="$2"; shift 2 ;;
|
|
--no-rrds) opts+=(--no-rrds); shift ;;
|
|
--no-logs) opts+=(--no-logs); shift ;;
|
|
--stop) stop_site=1; shift ;;
|
|
*) die "Unbekannte Option fuer backup: $1" ;;
|
|
esac
|
|
done
|
|
require_cmk_running
|
|
dir="${dir:-$BACKUP_DIR}"
|
|
mkdir -p "$dir"
|
|
|
|
if [ -n "$all" ]; then
|
|
mapfile -t sites < <(cmk_sites)
|
|
fi
|
|
[ ${#sites[@]} -gt 0 ] || die "Keine Site angegeben (--site SITE oder --all)."
|
|
|
|
local s
|
|
for s in "${sites[@]}"; do
|
|
cmk_site_exists "$s" || die "Site '$s' existiert im Container nicht."
|
|
backup_one_site "$s" "$dir" "$label" "$stop_site" "${opts[@]+"${opts[@]}"}"
|
|
done
|
|
}
|
|
|
|
backup_one_site() {
|
|
local site="$1" dir="$2" label="$3" stop_site="$4"; shift 4
|
|
local -a opts=("$@")
|
|
local ts version outfile tmp restarted=""
|
|
ts="$(date +%Y%m%d-%H%M%S)"
|
|
version="$(cmk_site_version "$site")"
|
|
outfile="$dir/cmkbackup_${site}_${version:-unknown}${label:+_$label}_${ts}.tar.gz"
|
|
|
|
if [ -n "$stop_site" ] && [ "$(cmk_site_state "$site")" = "0" ]; then
|
|
log "Stoppe Site '$site' fuer ein konsistentes Backup ..."
|
|
ct omd stop "$site" >/dev/null
|
|
restarted=1
|
|
fi
|
|
|
|
tmp="$(mktemp -d)"
|
|
mkdir -p "$tmp/$BUNDLE_DIRNAME"
|
|
|
|
log "Erzeuge omd-Backup der Site '$site' ${opts[*]+(${opts[*]})} ..."
|
|
if ! $RUNTIME exec "$CMK_CONTAINER_NAME" omd backup ${opts[@]+"${opts[@]}"} "$site" - \
|
|
>"$tmp/$BUNDLE_DIRNAME/site.tar.gz" 2>"$tmp/err.log" \
|
|
|| [ ! -s "$tmp/$BUNDLE_DIRNAME/site.tar.gz" ]; then
|
|
warn "Streaming-Backup nicht moeglich - weiche auf Zwischendatei im Container aus."
|
|
debug "$(cat "$tmp/err.log" 2>/dev/null)"
|
|
ct sh -c "rm -f /var/tmp/cmkbackup.tar.gz"
|
|
ct omd backup ${opts[@]+"${opts[@]}"} "$site" /var/tmp/cmkbackup.tar.gz \
|
|
|| { rm -rf "$tmp"; die "omd backup fehlgeschlagen."; }
|
|
$RUNTIME cp "$CMK_CONTAINER_NAME:/var/tmp/cmkbackup.tar.gz" "$tmp/$BUNDLE_DIRNAME/site.tar.gz"
|
|
ct sh -c "rm -f /var/tmp/cmkbackup.tar.gz"
|
|
fi
|
|
|
|
[ -n "$restarted" ] && { log "Starte Site '$site' wieder ..."; ct omd start "$site" >/dev/null; }
|
|
|
|
local sum size
|
|
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=backup
|
|
SITE=$site
|
|
CMK_VERSION=$version
|
|
SOURCE_KIND=container
|
|
SOURCE_HOST=$(hostname -f 2>/dev/null || hostname)
|
|
SOURCE_IP=$(primary_ip)
|
|
SOURCE_OS=$(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME" || echo unbekannt)
|
|
CREATED=$(date -Is)
|
|
CREATED_BY=cmk-manage.sh
|
|
ARCHIVE=site.tar.gz
|
|
ARCHIVE_SHA256=$sum
|
|
ARCHIVE_SIZE=$size
|
|
OMD_BACKUP_OPTS=${opts[*]-}
|
|
LABEL=$label
|
|
AGENT_RECEIVER_PORT=8000
|
|
EOF
|
|
|
|
cat >"$tmp/$BUNDLE_DIRNAME/INFO.txt" <<EOF
|
|
Checkmk Backup-Bundle
|
|
=====================
|
|
Site : $site
|
|
Checkmk-Version : $version ($(edition_from_version "$version"))
|
|
Erstellt : $(date -Is)
|
|
Quelle : Container '$CMK_CONTAINER_NAME' auf $(hostname)
|
|
Nutzdaten : site.tar.gz ($(human_size "$size"))
|
|
|
|
Wiederherstellen:
|
|
./scripts/cmk-manage.sh restore --file <diese-datei> --reuse
|
|
EOF
|
|
|
|
log "Verpacke Bundle ..."
|
|
tar -czf "$outfile" -C "$tmp" "$BUNDLE_DIRNAME"
|
|
rm -rf "$tmp"
|
|
ok "Backup erstellt: $outfile ($(human_size "$(file_size "$outfile")"))"
|
|
LAST_BACKUP_FILE="$outfile"
|
|
}
|
|
|
|
# =====================================================================
|
|
# Backup-Jobs (geplante Backups)
|
|
#
|
|
# Manuelle Backups nehmen immer BACKUP_DIR aus der .env. Geplante Jobs
|
|
# bringen ihr eigenes Zielverzeichnis mit - so kann z.B. taeglich auf
|
|
# eine NAS-Freigabe und woechentlich woanders hin gesichert werden.
|
|
#
|
|
# Ablage: backup-jobs.conf im Projektverzeichnis, eine Zeile je Job:
|
|
# name|sites|cron|verzeichnis|optionen|aufbewahren|aktiv
|
|
# Ausgefuehrt werden sie ueber /etc/cron.d/checkmk-backup, das aus
|
|
# dieser Datei erzeugt wird ("job auto on").
|
|
# =====================================================================
|
|
|
|
JOBS_FILE_NAME="backup-jobs.conf"
|
|
CRON_FILE="/etc/cron.d/checkmk-backup"
|
|
|
|
jobs_file() { printf '%s/%s\n' "$PROJECT_DIR" "$JOBS_FILE_NAME"; }
|
|
|
|
jobs_init() {
|
|
local f; f="$(jobs_file)"
|
|
[ -f "$f" ] && return 0
|
|
cat >"$f" <<'EOF'
|
|
# Backup-Jobs fuer cmk-manage.sh - eine Zeile je Job:
|
|
# name|sites|cron|verzeichnis|optionen|aufbewahren|aktiv
|
|
#
|
|
# name nur Buchstaben, Ziffern, _ und -
|
|
# sites ALL oder Site-Namen durch Komma getrennt
|
|
# cron fuenf Cron-Felder, z.B. "15 2 * * *"
|
|
# verzeichnis Zielverzeichnis (absolut oder relativ zum Projekt)
|
|
# optionen leer oder --no-rrds / --no-logs / --stop (mit Leerzeichen)
|
|
# aufbewahren Anzahl der Backups, die im Zielverzeichnis bleiben (0 = alle)
|
|
# aktiv on oder off
|
|
#
|
|
# Bearbeiten moeglichst ueber: cmk-manage.sh job ... bzw. das Menue.
|
|
EOF
|
|
ok "Neue Jobdatei angelegt: $f"
|
|
}
|
|
|
|
jobs_read() { # nur Datenzeilen
|
|
local f; f="$(jobs_file)"
|
|
[ -f "$f" ] || return 0
|
|
grep -vE '^[[:space:]]*(#|$)' "$f" || true
|
|
}
|
|
|
|
job_line() { jobs_read | awk -F'|' -v n="$1" '$1==n {print; exit}'; }
|
|
job_exists() { [ -n "$(job_line "$1")" ]; }
|
|
job_f() { printf '%s' "$1" | cut -d'|' -f"$2"; }
|
|
|
|
job_dir_abs() { # Verzeichnis eines Jobs absolut
|
|
local d="$1"
|
|
case "$d" in /*) printf '%s\n' "$d" ;; *) printf '%s\n' "$PROJECT_DIR/${d#./}" ;; esac
|
|
}
|
|
|
|
job_validate_name() {
|
|
case "$1" in
|
|
''|*[!A-Za-z0-9_-]*) echo "Der Name darf nur Buchstaben, Ziffern, _ und - enthalten."; return 1 ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
job_validate_cron() {
|
|
local n; n="$(printf '%s' "$1" | awk '{print NF}')"
|
|
[ "$n" = 5 ] || { echo "Ein Cron-Ausdruck besteht aus genau fuenf Feldern, z.B. '15 2 * * *'."; return 1; }
|
|
return 0
|
|
}
|
|
|
|
job_validate_field() { # keine Trennzeichen in Feldern
|
|
case "$1" in *'|'*) echo "Das Zeichen | ist in diesem Feld nicht erlaubt."; return 1 ;; esac
|
|
return 0
|
|
}
|
|
|
|
# Job schreiben (anlegen oder ersetzen)
|
|
job_put() { # name sites cron dir opts keep enabled
|
|
jobs_init
|
|
local f tmp; f="$(jobs_file)"
|
|
tmp="$(mktemp)"
|
|
awk -F'|' -v n="$1" '$1!=n' "$f" >"$tmp"
|
|
printf '%s|%s|%s|%s|%s|%s|%s\n' "$1" "$2" "$3" "$4" "$5" "$6" "$7" >>"$tmp"
|
|
# Kommentarkopf erhalten
|
|
{ grep -E '^[[:space:]]*#' "$f" 2>/dev/null || true; grep -vE '^[[:space:]]*(#|$)' "$tmp" ; } >"$f"
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
job_remove() {
|
|
local f tmp; f="$(jobs_file)"
|
|
[ -f "$f" ] || return 0
|
|
tmp="$(mktemp)"
|
|
awk -F'|' -v n="$1" '$1!=n' "$f" >"$tmp" && mv "$tmp" "$f"
|
|
}
|
|
|
|
# --- Aufraeumen im Zielverzeichnis eines Jobs -------------------------
|
|
prune_in_dir() { # verzeichnis anzahl
|
|
local dir="$1" keep="$2" i=0
|
|
[ -n "$keep" ] && [ "$keep" -gt 0 ] 2>/dev/null || return 0
|
|
local -a files=()
|
|
mapfile -t files < <(find "$dir" -maxdepth 1 -type f -name '*.tar.gz' -printf '%T@ %p\n' 2>/dev/null \
|
|
| sort -rn | cut -d' ' -f2-)
|
|
[ ${#files[@]} -le "$keep" ] && return 0
|
|
for ((i=keep; i<${#files[@]}; i++)); do
|
|
log "Aufbewahrung: entferne $(basename "${files[$i]}")"
|
|
rm -f "${files[$i]}"
|
|
done
|
|
}
|
|
|
|
# --- Job ausfuehren ---------------------------------------------------
|
|
job_run_one() {
|
|
local name="$1" line
|
|
line="$(job_line "$name")"
|
|
[ -n "$line" ] || die "Job '$name' ist nicht vorhanden. Uebersicht: $SELF job list"
|
|
|
|
local sites cron dir opts keep enabled
|
|
sites="$(job_f "$line" 2)"; cron="$(job_f "$line" 3)"; dir="$(job_f "$line" 4)"
|
|
opts="$(job_f "$line" 5)"; keep="$(job_f "$line" 6)"; enabled="$(job_f "$line" 7)"
|
|
|
|
dir="$(job_dir_abs "$dir")"
|
|
mkdir -p "$dir" || die "Zielverzeichnis nicht anlegbar: $dir"
|
|
|
|
log "Job '$name' - Ziel $dir, Sites ${sites}${opts:+, Optionen $opts}"
|
|
require_cmk_running
|
|
|
|
local -a args=(--dir "$dir" --label "$name")
|
|
local o
|
|
for o in $opts; do
|
|
case "$o" in
|
|
--no-rrds|--no-logs|--stop) args+=("$o") ;;
|
|
*) warn "Unbekannte Option '$o' im Job '$name' - wird uebergangen." ;;
|
|
esac
|
|
done
|
|
if [ "$sites" = ALL ]; then
|
|
args+=(--all)
|
|
else
|
|
local sname
|
|
for sname in $(printf '%s' "$sites" | tr ',' ' '); do args+=(--site "$sname"); done
|
|
fi
|
|
|
|
ASSUME_YES=1 cmd_backup "${args[@]}"
|
|
prune_in_dir "$dir" "$keep"
|
|
ok "Job '$name' abgeschlossen."
|
|
}
|
|
|
|
# --- Cron ------------------------------------------------------------
|
|
cron_write() {
|
|
jobs_init
|
|
local n=0 line name cron
|
|
local tmp; tmp="$(mktemp)"
|
|
{
|
|
printf '# Automatische Checkmk-Backups - erzeugt von cmk-manage.sh\n'
|
|
printf '# Nicht von Hand aendern, Quelle ist %s\n' "$(jobs_file)"
|
|
printf 'SHELL=/bin/sh\n'
|
|
printf 'PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin\n\n'
|
|
while IFS= read -r line; do
|
|
[ -n "$line" ] || continue
|
|
[ "$(job_f "$line" 7)" = on ] || continue
|
|
name="$(job_f "$line" 1)"; cron="$(job_f "$line" 3)"
|
|
printf '%s root %s -y job run %s >>/var/log/cmk-backup.log 2>&1\n' \
|
|
"$cron" "$SELF" "$name"
|
|
n=$((n+1))
|
|
done < <(jobs_read)
|
|
} >"$tmp"
|
|
|
|
if [ "$n" -eq 0 ]; then
|
|
rm -f "$tmp"
|
|
warn "Kein aktiver Job vorhanden - es wird nichts eingeplant."
|
|
[ -f "$CRON_FILE" ] && { rm -f "$CRON_FILE"; log "Vorhandene Cron-Datei entfernt."; }
|
|
return 0
|
|
fi
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
err "Zum Schreiben von $CRON_FILE werden root-Rechte benoetigt."
|
|
echo
|
|
cat "$tmp"
|
|
echo
|
|
log "Die Zeilen oben von Hand nach $CRON_FILE uebernehmen, oder erneut mit sudo aufrufen."
|
|
rm -f "$tmp"
|
|
return 1
|
|
fi
|
|
install -m 0644 "$tmp" "$CRON_FILE"
|
|
rm -f "$tmp"
|
|
ok "$n Job(s) eingeplant in $CRON_FILE"
|
|
}
|
|
|
|
cron_remove_file() {
|
|
if [ -f "$CRON_FILE" ]; then
|
|
[ "$(id -u)" -eq 0 ] || die "Zum Entfernen von $CRON_FILE werden root-Rechte benoetigt."
|
|
rm -f "$CRON_FILE"
|
|
ok "Automatische Backups abgeschaltet ($CRON_FILE entfernt)."
|
|
else
|
|
ok "Es war nichts eingeplant."
|
|
fi
|
|
}
|
|
|
|
backup_auto_enabled() {
|
|
case "$(printf '%s' "${BACKUP_AUTO:-off}" | tr 'A-Z' 'a-z')" in
|
|
on|true|1|yes|ja) return 0 ;; *) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# --- Anzeige ----------------------------------------------------------
|
|
jobs_list_table() {
|
|
printf '%-14s %-12s %-14s %-28s %-18s %5s %s\n' \
|
|
"NAME" "SITES" "ZEITPLAN" "ZIEL" "OPTIONEN" "HALT" "AKTIV"
|
|
local line n=0
|
|
while IFS= read -r line; do
|
|
[ -n "$line" ] || continue
|
|
printf '%-14s %-12s %-14s %-28s %-18s %5s %s\n' \
|
|
"$(job_f "$line" 1)" "$(job_f "$line" 2)" "$(job_f "$line" 3)" \
|
|
"$(job_f "$line" 4)" "$(job_f "$line" 5)" "$(job_f "$line" 6)" "$(job_f "$line" 7)"
|
|
n=$((n+1))
|
|
done < <(jobs_read)
|
|
[ "$n" -eq 0 ] && echo "(noch keine Jobs angelegt)"
|
|
return 0
|
|
}
|
|
|
|
cmd_job() {
|
|
local sub="${1:-list}"; [ $# -gt 0 ] && shift || true
|
|
case "$sub" in
|
|
list|ls) jobs_list_table ;;
|
|
|
|
status)
|
|
printf '\n%sAutomatische Backups%s\n' "$C_BLD" "$C_RST"
|
|
printf ' Schalter : %s\n' "$(backup_auto_enabled && echo 'ein (BACKUP_AUTO=on)' || echo 'aus (BACKUP_AUTO=off)')"
|
|
printf ' Cron-Datei : %s\n' "$([ -f "$CRON_FILE" ] && echo "$CRON_FILE" || echo 'nicht vorhanden')"
|
|
printf ' Jobdatei : %s\n' "$(jobs_file)"
|
|
printf ' Manuell nach: %s (BACKUP_DIR)\n' "$BACKUP_DIR"
|
|
printf '\n%sJobs%s\n' "$C_BLD" "$C_RST"
|
|
jobs_list_table
|
|
if [ -f "$CRON_FILE" ]; then
|
|
printf '\n%sEingeplant%s\n' "$C_BLD" "$C_RST"
|
|
grep -vE '^[[:space:]]*(#|SHELL|PATH|$)' "$CRON_FILE" | sed 's/^/ /'
|
|
fi
|
|
echo ;;
|
|
|
|
add|set)
|
|
local name="" sites="ALL" cron="" dir="" keep="7" enabled="on" opts=""
|
|
local is_set=""; [ "$sub" = set ] && is_set=1
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--name) name="$2"; shift 2 ;;
|
|
--sites) sites="$2"; shift 2 ;;
|
|
--cron) cron="$2"; shift 2 ;;
|
|
--dir) dir="$2"; shift 2 ;;
|
|
--keep) keep="$2"; shift 2 ;;
|
|
--no-rrds) opts="$opts --no-rrds"; shift ;;
|
|
--no-logs) opts="$opts --no-logs"; shift ;;
|
|
--stop) opts="$opts --stop"; shift ;;
|
|
--enabled) enabled=on; shift ;;
|
|
--disabled) enabled=off; shift ;;
|
|
*) [ -z "$name" ] && { name="$1"; shift; } || die "Unbekannte Option fuer 'job $sub': $1" ;;
|
|
esac
|
|
done
|
|
[ -n "$name" ] || die "--name fehlt."
|
|
local msg
|
|
msg="$(job_validate_name "$name")" || die "$msg"
|
|
|
|
local old=""; old="$(job_line "$name")"
|
|
if [ -n "$is_set" ]; then
|
|
[ -n "$old" ] || die "Job '$name' ist nicht vorhanden."
|
|
# nicht angegebene Felder aus dem Bestand uebernehmen
|
|
[ "$sites" = ALL ] && sites="$(job_f "$old" 2)"
|
|
[ -z "$cron" ] && cron="$(job_f "$old" 3)"
|
|
[ -z "$dir" ] && dir="$(job_f "$old" 4)"
|
|
[ -z "$opts" ] && opts="$(job_f "$old" 5)"
|
|
[ "$keep" = 7 ] && keep="$(job_f "$old" 6)"
|
|
enabled="$(job_f "$old" 7)"
|
|
else
|
|
[ -z "$old" ] || die "Job '$name' existiert bereits. Aendern mit: $SELF job set $name ..."
|
|
fi
|
|
dir="${dir:-$BACKUP_DIR}"
|
|
[ -n "$cron" ] || die "--cron fehlt, z.B. --cron '15 2 * * *'."
|
|
msg="$(job_validate_cron "$cron")" || die "$msg"
|
|
msg="$(job_validate_field "$dir")" || die "$msg"
|
|
case "$keep" in ''|*[!0-9]*) die "--keep braucht eine Zahl (0 = alles behalten)." ;; esac
|
|
|
|
opts="$(printf '%s' "$opts" | sed 's/^ *//')"
|
|
job_put "$name" "$sites" "$cron" "$dir" "$opts" "$keep" "$enabled"
|
|
ok "Job '$name' gespeichert."
|
|
jobs_list_table | sed -n "1p;/^$name /p"
|
|
backup_auto_enabled && cron_write || log "Einplanen mit: $SELF job auto on"
|
|
;;
|
|
|
|
del|rm|delete)
|
|
[ $# -ge 1 ] || die "Name fehlt: $SELF job del NAME"
|
|
job_exists "$1" || die "Job '$1' ist nicht vorhanden."
|
|
confirm "Job '$1' wirklich loeschen?" || die "Abgebrochen."
|
|
job_remove "$1"; ok "Job '$1' geloescht."
|
|
backup_auto_enabled && cron_write || true
|
|
;;
|
|
|
|
enable|disable)
|
|
[ $# -ge 1 ] || die "Name fehlt: $SELF job $sub NAME"
|
|
local l; l="$(job_line "$1")"
|
|
[ -n "$l" ] || die "Job '$1' ist nicht vorhanden."
|
|
local st=on; [ "$sub" = disable ] && st=off
|
|
job_put "$1" "$(job_f "$l" 2)" "$(job_f "$l" 3)" "$(job_f "$l" 4)" \
|
|
"$(job_f "$l" 5)" "$(job_f "$l" 6)" "$st"
|
|
ok "Job '$1' ist jetzt $st."
|
|
backup_auto_enabled && cron_write || true
|
|
;;
|
|
|
|
run)
|
|
[ $# -ge 1 ] || die "Name fehlt: $SELF job run NAME"
|
|
job_run_one "$1" ;;
|
|
|
|
run-all)
|
|
local line n=0
|
|
while IFS= read -r line; do
|
|
[ -n "$line" ] || continue
|
|
[ "$(job_f "$line" 7)" = on ] || continue
|
|
job_run_one "$(job_f "$line" 1)" || warn "Job fehlgeschlagen: $(job_f "$line" 1)"
|
|
n=$((n+1))
|
|
done < <(jobs_read)
|
|
[ "$n" -eq 0 ] && warn "Kein aktiver Job vorhanden."
|
|
return 0 ;;
|
|
|
|
auto)
|
|
case "${1:-}" in
|
|
on) env_set BACKUP_AUTO on; BACKUP_AUTO=on; cron_write ;;
|
|
off) env_set BACKUP_AUTO off; BACKUP_AUTO=off; cron_remove_file ;;
|
|
'') printf ' Automatische Backups: %s\n' \
|
|
"$(backup_auto_enabled && echo ein || echo aus)" ;;
|
|
*) die "Aufruf: $SELF job auto [on|off]" ;;
|
|
esac ;;
|
|
|
|
*) die "Unbekanntes Unterkommando 'job $sub'.
|
|
Moeglich: list | status | add | set | del | enable | disable | run | run-all | auto" ;;
|
|
esac
|
|
}
|
|
|
|
# =====================================================================
|
|
# Auflisten / Aufraeumen
|
|
# =====================================================================
|
|
list_backup_files() {
|
|
find "$BACKUP_DIR" -maxdepth 1 -type f \( -name '*.tar.gz' -o -name '*.tgz' \) -printf '%T@ %p\n' 2>/dev/null \
|
|
| sort -rn | cut -d' ' -f2-
|
|
}
|
|
|
|
cmd_list() {
|
|
local f n=0
|
|
printf '%-58s %10s %s\n' "DATEI" "GROESSE" "SITE / VERSION"
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] || continue
|
|
bundle_read "$f" 2>/dev/null || continue
|
|
printf '%-58s %10s %s / %s\n' "$(basename "$f")" \
|
|
"$(human_size "$(file_size "$f")")" "$B_SITE" "${B_CMK_VERSION:-?}"
|
|
n=$((n+1))
|
|
done < <(list_backup_files)
|
|
[ "$n" -eq 0 ] && echo "(keine Backups in $BACKUP_DIR)"
|
|
return 0
|
|
}
|
|
|
|
cmd_prune() {
|
|
local keep=5
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--keep) keep="$2"; shift 2 ;;
|
|
*) die "Unbekannte Option fuer prune: $1" ;;
|
|
esac
|
|
done
|
|
local -a files=(); mapfile -t files < <(list_backup_files)
|
|
if [ ${#files[@]} -le "$keep" ]; then
|
|
ok "Nichts zu loeschen (${#files[@]} Backups, behalte $keep)."
|
|
return 0
|
|
fi
|
|
local i
|
|
for ((i=keep; i<${#files[@]}; i++)); do
|
|
if confirm "Backup '$(basename "${files[$i]}")' loeschen?"; then
|
|
rm -f "${files[$i]}" && log "geloescht: $(basename "${files[$i]}")"
|
|
fi
|
|
done
|
|
ok "Aufraeumen beendet."
|
|
}
|
|
|
|
# =====================================================================
|
|
# Restore / Import (gemeinsame Implementierung)
|
|
# =====================================================================
|
|
do_restore() {
|
|
local file="$1" as_site="$2" reuse="$3" kill_opt="$4" nostart="$5" mode="$6"
|
|
|
|
[ -f "$file" ] || die "Datei nicht gefunden: $file"
|
|
require_cmk_running
|
|
bundle_read "$file" || die "'$file' ist kein Checkmk-Backup (weder Bundle noch omd-Archiv)."
|
|
|
|
local target="${as_site:-$B_SITE}"
|
|
|
|
echo
|
|
bundle_show "$file"
|
|
echo
|
|
printf '%sZiel-Site im Container : %s%s\n' "$C_BLD" "$target" "$C_RST"
|
|
|
|
# --- Pruefsumme -------------------------------------------------
|
|
if [ "$B_IS_BUNDLE" = 1 ] && [ -n "$B_ARCHIVE_SHA256" ]; then
|
|
log "Pruefe Pruefsumme ..."
|
|
local sum
|
|
sum="$(bundle_extract_payload "$file" | sha256sum | awk '{print $1}')"
|
|
[ "$sum" = "$B_ARCHIVE_SHA256" ] || die "Pruefsumme stimmt nicht - Bundle beschaedigt."
|
|
ok "Pruefsumme korrekt."
|
|
fi
|
|
|
|
# --- Versionspruefung -------------------------------------------
|
|
if [ -n "$B_CMK_VERSION" ]; then
|
|
local installed
|
|
installed="$(cmk_versions_installed)"
|
|
if ! printf '%s\n' "$installed" | grep -qxF "$B_CMK_VERSION"; then
|
|
err "Die Checkmk-Version des Backups ist im Container nicht installiert."
|
|
printf ' benoetigt : %s\n' "$B_CMK_VERSION"
|
|
printf ' installiert : %s\n' "$(printf '%s' "$installed" | tr '\n' ' ')"
|
|
local want_ver want_ed
|
|
want_ver="${B_CMK_VERSION%.*}"
|
|
case "$B_CMK_VERSION" in
|
|
*.cre) want_ed=raw ;;
|
|
*.cee) want_ed=enterprise ;;
|
|
*.cce) want_ed=cloud ;;
|
|
*.cme) want_ed=managed ;;
|
|
*) want_ed="${CMK_EDITION:-raw}" ;;
|
|
esac
|
|
cat <<EOF
|
|
|
|
"omd restore" kann eine Site nur wiederherstellen, wenn genau ihre Version
|
|
im Zielsystem installiert ist. Im Container kommt sie aus dem Image, also:
|
|
|
|
$SELF image pull --edition $want_ed --version $want_ver
|
|
$SELF up
|
|
$SELF $mode --file "$file"
|
|
|
|
Bei der Enterprise-Edition zusaetzlich Zugangsdaten angeben (--user/--password)
|
|
oder den Tarball verwenden:
|
|
$SELF image download --edition $want_ed --version $want_ver --user <benutzer> --password <kennwort>
|
|
EOF
|
|
die "Abbruch wegen Versionskonflikt."
|
|
fi
|
|
ok "Version $B_CMK_VERSION ist im Container vorhanden."
|
|
fi
|
|
|
|
# --- Editionspruefung -------------------------------------------
|
|
case "$B_CMK_VERSION" in
|
|
*.cee|*.cme|*.cce)
|
|
warn "Das Backup stammt aus einer $(edition_from_version "$B_CMK_VERSION").
|
|
Dafuer wird das passende Image benoetigt - die Raw-Edition genuegt nicht.
|
|
Aktuell konfiguriert: ${CMK_IMAGE:-<nicht gesetzt>}" ;;
|
|
esac
|
|
|
|
# --- vorhandene Site --------------------------------------------
|
|
local -a rargs=()
|
|
if cmk_site_exists "$target"; then
|
|
if [ -z "$reuse" ]; then
|
|
die "Site '$target' existiert bereits. Mit --reuse ueberschreiben (Daten gehen verloren!)."
|
|
fi
|
|
warn "Die vorhandene Site '$target' wird UEBERSCHRIEBEN."
|
|
confirm "Wirklich fortfahren?" || die "Abgebrochen."
|
|
rargs+=(--reuse)
|
|
[ -n "$kill_opt" ] && rargs+=(--kill)
|
|
if [ "$(cmk_site_state "$target")" != "2" ]; then
|
|
log "Stoppe Site '$target' ..."
|
|
ct omd stop "$target" >/dev/null 2>&1 || true
|
|
fi
|
|
else
|
|
confirm "Site '$target' jetzt aus dem Backup wiederherstellen?" || die "Abgebrochen."
|
|
fi
|
|
|
|
if [ -n "$as_site" ] && [ "$as_site" != "$B_SITE" ]; then
|
|
warn "Die Site wird von '$B_SITE' nach '$as_site' umbenannt.
|
|
Registrierte Agents verweisen auf '$B_SITE' und muessen neu registriert werden!"
|
|
confirm "Trotzdem umbenennen?" || die "Abgebrochen."
|
|
rargs+=("$as_site")
|
|
fi
|
|
|
|
# --- Nutzdaten in den Container schieben ------------------------
|
|
local tmp; tmp="$(mktemp -d)"
|
|
log "Bereite Nutzdaten vor ..."
|
|
bundle_extract_payload "$file" >"$tmp/payload.tar.gz"
|
|
log "Kopiere Archiv in den Container ($(human_size "$(file_size "$tmp/payload.tar.gz")")) ..."
|
|
ct sh -c 'rm -f /var/tmp/cmkrestore.tar.gz'
|
|
$RUNTIME cp "$tmp/payload.tar.gz" "$CMK_CONTAINER_NAME:/var/tmp/cmkrestore.tar.gz"
|
|
rm -rf "$tmp"
|
|
|
|
log "Fuehre 'omd restore ${rargs[*]-} ' aus ..."
|
|
if ! ct omd restore ${rargs[@]+"${rargs[@]}"} /var/tmp/cmkrestore.tar.gz; then
|
|
ct sh -c 'rm -f /var/tmp/cmkrestore.tar.gz' || true
|
|
die "omd restore ist fehlgeschlagen."
|
|
fi
|
|
ct sh -c 'rm -f /var/tmp/cmkrestore.tar.gz' || true
|
|
ok "Site '$target' wurde wiederhergestellt."
|
|
|
|
# --- Nachbereitung ----------------------------------------------
|
|
# TMPFS im Container abschalten (Mount im Container nicht moeglich/noetig)
|
|
ct omd config "$target" set TMPFS off >/dev/null 2>&1 || true
|
|
|
|
if [ -z "$nostart" ]; then
|
|
log "Starte Site '$target' ..."
|
|
ct omd start "$target" || warn "Site konnte nicht vollstaendig gestartet werden - bitte 'omd status $target' pruefen."
|
|
fi
|
|
|
|
post_restore_hints "$target" "$mode"
|
|
}
|
|
|
|
post_restore_hints() {
|
|
local target="$1" mode="$2" ip; ip="$(primary_ip)"
|
|
cat <<EOF
|
|
|
|
${C_BLD}Naechste Schritte${C_RST}
|
|
1) Standard-Site im Container:
|
|
In der .env CMK_SITE_ID=$target setzen, sonst startet der Container
|
|
nach einem Reboot nur die urspruengliche Site.
|
|
(automatisch: $0 $mode --file ... --set-default-site)
|
|
2) Erreichbarkeit pruefen:
|
|
http://$ip:5000/$target/
|
|
3) Im NGINX Proxy Manager einen Proxy-Host anlegen:
|
|
Domain -> Forward to http://127.0.0.1:5000
|
|
4) Agents: solange Site-Name (${target}) und IP-Adresse gleich bleiben und
|
|
Port 8000 (Agent-Receiver) offen ist, muessen die Agents NICHT neu
|
|
registriert werden - die Site-Zertifikate sind im Backup enthalten.
|
|
Pruefen: $0 omd su $target -> cmk -O ; danach im GUI die
|
|
Host-Status/Agent-Verbindungen kontrollieren.
|
|
|
|
EOF
|
|
}
|
|
|
|
cmd_restore() {
|
|
local file="" as_site="" reuse="" kill_opt="" nostart=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--file) file="$2"; shift 2 ;;
|
|
--as-site) as_site="$2"; shift 2 ;;
|
|
--reuse) reuse=1; shift ;;
|
|
--kill) kill_opt=1; shift ;;
|
|
--no-start) nostart=1; shift ;;
|
|
*) die "Unbekannte Option fuer restore: $1" ;;
|
|
esac
|
|
done
|
|
[ -n "$file" ] || die "--file fehlt."
|
|
do_restore "$file" "$as_site" "$reuse" "$kill_opt" "$nostart" "restore"
|
|
}
|
|
|
|
cmd_import() {
|
|
local file="" reuse="" kill_opt="" nostart="" setdefault=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--file) file="$2"; shift 2 ;;
|
|
--reuse) reuse=1; shift ;;
|
|
--kill) kill_opt=1; shift ;;
|
|
--no-start) nostart=1; shift ;;
|
|
--set-default-site) setdefault=1; shift ;;
|
|
*) die "Unbekannte Option fuer import: $1" ;;
|
|
esac
|
|
done
|
|
[ -n "$file" ] || die "--file fehlt."
|
|
|
|
bundle_read "$file" || die "'$file' ist kein gueltiges Migrations-Bundle."
|
|
if [ "$B_IS_BUNDLE" = 1 ] && [ "$B_BUNDLE_TYPE" = "migrate" ]; then
|
|
log "Migrations-Bundle erkannt (Quelle: ${B_SOURCE_HOST:-?} / ${B_SOURCE_IP:-?})."
|
|
local myip; myip="$(primary_ip)"
|
|
if [ -n "$B_SOURCE_IP" ] && [ "$B_SOURCE_IP" != "$myip" ]; then
|
|
warn "Die IP dieses Hosts ($myip) weicht von der Quell-IP ($B_SOURCE_IP) ab.
|
|
Damit registrierte Agents ohne Neuregistrierung weiterarbeiten, muss dieser
|
|
Host die IP $B_SOURCE_IP uebernehmen (bzw. der DNS-Name darauf zeigen)."
|
|
confirm "Trotzdem importieren?" || die "Abgebrochen."
|
|
fi
|
|
fi
|
|
|
|
do_restore "$file" "" "$reuse" "$kill_opt" "$nostart" "import"
|
|
|
|
if [ -n "$setdefault" ]; then
|
|
env_set CMK_SITE_ID "$B_SITE"
|
|
ok "CMK_SITE_ID=$B_SITE in der .env gesetzt."
|
|
warn "Damit die Aenderung wirkt, den Container neu erstellen: $0 up"
|
|
fi
|
|
}
|
|
|
|
# =====================================================================
|
|
# NGINX Proxy Manager ein-/ausschalten
|
|
#
|
|
# Wer bereits einen NGINX auf einem anderen System betreibt, braucht den
|
|
# Container nicht. Abgeschaltet wird er weder gebaut noch gestartet -
|
|
# "compose up" bekommt dann nur noch den Checkmk-Dienst genannt.
|
|
#
|
|
# Die Daten unter ./data/npm bleiben in jedem Fall erhalten: sie liegen
|
|
# in Bind-Mounts, nicht im Container. Ein spaeteres Einschalten nimmt
|
|
# die vorhandene Konfiguration samt Zertifikaten wieder auf.
|
|
# =====================================================================
|
|
|
|
# Container entfernen (nicht die Daten!), damit ihn keine Restart-Regel
|
|
# und kein Reboot wieder hochzieht.
|
|
npm_teardown_container() {
|
|
container_exists "$NPM_CONTAINER_NAME" || return 0
|
|
if container_running "$NPM_CONTAINER_NAME"; then
|
|
log "Stoppe '$NPM_CONTAINER_NAME' ..."
|
|
$RUNTIME stop "$NPM_CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
fi
|
|
log "Entferne den Container '$NPM_CONTAINER_NAME' (die Daten bleiben)."
|
|
$RUNTIME rm -f "$NPM_CONTAINER_NAME" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
npm_data_summary() {
|
|
local d="${NPM_DATA_DIR:-$PROJECT_DIR/data/npm/data}"
|
|
case "$d" in /*) : ;; *) d="$PROJECT_DIR/${d#./}" ;; esac
|
|
local le="${NPM_LE_DIR:-$PROJECT_DIR/data/npm/letsencrypt}"
|
|
case "$le" in /*) : ;; *) le="$PROJECT_DIR/${le#./}" ;; esac
|
|
printf ' Konfiguration/DB : %s (%s)\n' "$d" "$([ -d "$d" ] && du -sh "$d" 2>/dev/null | cut -f1 || echo 'leer')"
|
|
printf ' Zertifikate : %s (%s)\n' "$le" "$([ -d "$le" ] && du -sh "$le" 2>/dev/null | cut -f1 || echo 'leer')"
|
|
}
|
|
|
|
cmd_npm_enable() {
|
|
local want=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
on|an|true|1|yes|ja) want=on; shift ;;
|
|
off|aus|false|0|no|nein) want=off; shift ;;
|
|
-h|--help)
|
|
cat <<'EOF'
|
|
cmk-manage.sh npm-enable [on|off]
|
|
|
|
Schaltet den NGINX Proxy Manager ein oder aus. Ohne Angabe wird der
|
|
aktuelle Stand gezeigt.
|
|
|
|
off Container wird gestoppt und entfernt und bei "up" nicht mehr
|
|
angelegt. Die Daten unter ./data/npm bleiben erhalten.
|
|
Sinnvoll, wenn die HTTPS-Terminierung ein anderer NGINX uebernimmt -
|
|
der leitet dann auf http://<dieser-host>:5000 weiter.
|
|
on Container wird wieder gebaut und gestartet, mit der vorhandenen
|
|
Konfiguration und den vorhandenen Zertifikaten.
|
|
EOF
|
|
return 0 ;;
|
|
*) die "Unbekanntes Argument fuer npm-enable: $1 (on|off)" ;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$want" ]; then
|
|
printf '\n%sNGINX Proxy Manager%s\n' "$C_BLD" "$C_RST"
|
|
printf ' Schalter : %s\n' "$(npm_enabled && echo 'ein (NPM_ENABLED=on)' || echo 'aus (NPM_ENABLED=off)')"
|
|
if container_exists "$NPM_CONTAINER_NAME"; then
|
|
printf ' Container : %s\n' "$(container_running "$NPM_CONTAINER_NAME" && echo laeuft || echo gestoppt)"
|
|
else
|
|
printf ' Container : nicht vorhanden\n'
|
|
fi
|
|
printf '\n%sVorhandene Daten%s\n' "$C_BLD" "$C_RST"
|
|
npm_data_summary
|
|
echo
|
|
return 0
|
|
fi
|
|
|
|
if [ "$want" = off ]; then
|
|
npm_enabled || { ok "Der Proxy Manager ist bereits abgeschaltet."; return 0; }
|
|
printf '\n%sDiese Daten bleiben erhalten%s\n' "$C_BLD" "$C_RST"
|
|
npm_data_summary
|
|
echo
|
|
confirm "Den NGINX Proxy Manager jetzt abschalten?" || die "Abgebrochen."
|
|
env_set NPM_ENABLED off
|
|
NPM_ENABLED=off
|
|
npm_teardown_container
|
|
ok "Abgeschaltet. Bei 'up' wird der Container nicht mehr angelegt."
|
|
local ip; ip="$(primary_ip)"
|
|
cat <<EOF
|
|
|
|
${C_BLD}Weiterleitung vom externen NGINX${C_RST}
|
|
Ziel : http://$ip:5000
|
|
Agent-Receiver : $ip:8000 (muss fuer die Agents erreichbar bleiben)
|
|
|
|
Port 5000 ist bisher nur fuer localhost gedacht. Er muss jetzt fuer den
|
|
externen Proxy erreichbar sein - moeglichst eng begrenzt:
|
|
firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=<proxy-ip> port port=5000 protocol=tcp accept'
|
|
ufw allow from <proxy-ip> to any port 5000 proto tcp
|
|
|
|
Im externen NGINX (Beispiel):
|
|
location / {
|
|
proxy_pass http://$ip:5000;
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
client_max_body_size 0;
|
|
proxy_read_timeout 300s;
|
|
}
|
|
|
|
EOF
|
|
return 0
|
|
fi
|
|
|
|
# --- einschalten ---
|
|
if npm_enabled && container_running "$NPM_CONTAINER_NAME"; then
|
|
ok "Der Proxy Manager laeuft bereits."
|
|
return 0
|
|
fi
|
|
env_set NPM_ENABLED on
|
|
NPM_ENABLED=on
|
|
log "Starte den NGINX Proxy Manager ..."
|
|
if compose up -d nginx-proxy-manager; then
|
|
ok "Eingeschaltet."
|
|
printf '\n%sVorhandene Daten wurden uebernommen%s\n' "$C_BLD" "$C_RST"
|
|
npm_data_summary
|
|
printf '\n Oberflaeche: http://%s:%s/\n\n' "$(primary_ip)" "${NPM_ADMIN_PORT:-81}"
|
|
else
|
|
err "Der Container konnte nicht gestartet werden."
|
|
log "Pruefen: $SELF logs nginx-proxy-manager"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# =====================================================================
|
|
# Admin-Port des NGINX Proxy Managers
|
|
#
|
|
# Die Adminoberflaeche des Proxy Managers lauscht im Image fest auf Port 81
|
|
# ("listen 81 default;" in /etc/nginx/conf.d/production.conf). Eine
|
|
# Umgebungsvariable dafuer gibt es in den veroeffentlichten 2.x-Versionen
|
|
# noch nicht. Da beide Container im Host-Netzwerk laufen, laesst sich der
|
|
# Port auch nicht per Port-Mapping verschieben.
|
|
#
|
|
# Loesung: nginx bindet in der Datei
|
|
# /data/nginx/custom/http.conf (= ./data/npm/data/nginx/custom/http.conf)
|
|
# einen offiziellen Erweiterungspunkt innerhalb des http{}-Blocks ein.
|
|
# Dort legen wir einen zusaetzlichen Server an, der auf dem gewuenschten
|
|
# Port lauscht und an die Oberflaeche auf 127.0.0.1:81 weiterreicht.
|
|
#
|
|
# Port 81 bleibt dabei lokal bestehen - er wird per Firewall dichtgehalten.
|
|
# =====================================================================
|
|
|
|
NPM_CUSTOM_CONF_REL="nginx/custom/http.conf"
|
|
NPM_BLOCK_START="# >>> cmk-manage.sh: Admin-Port - nicht von Hand aendern >>>"
|
|
NPM_BLOCK_END="# <<< cmk-manage.sh: Admin-Port <<<"
|
|
|
|
npm_custom_conf_path() {
|
|
local base="${NPM_DATA_DIR:-$PROJECT_DIR/data/npm/data}"
|
|
case "$base" in /*) : ;; *) base="$PROJECT_DIR/${base#./}" ;; esac
|
|
printf '%s/%s\n' "$base" "$NPM_CUSTOM_CONF_REL"
|
|
}
|
|
|
|
# Vorhandenen verwalteten Block entfernen, Fremdinhalte bleiben erhalten
|
|
npm_conf_strip_block() {
|
|
local f="$1"
|
|
[ -f "$f" ] || return 0
|
|
awk -v s="$NPM_BLOCK_START" -v e="$NPM_BLOCK_END" '
|
|
$0 == s { skip=1; next }
|
|
$0 == e { skip=0; next }
|
|
!skip { print }
|
|
' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
|
|
}
|
|
|
|
npm_conf_write_block() {
|
|
local f="$1" port="$2"
|
|
mkdir -p "$(dirname "$f")"
|
|
touch "$f"
|
|
npm_conf_strip_block "$f"
|
|
{
|
|
printf '%s\n' "$NPM_BLOCK_START"
|
|
cat <<EOF
|
|
# Zusaetzlicher Zugang zur Adminoberflaeche auf Port $port.
|
|
# Erzeugt von scripts/cmk-manage.sh npm-port $port
|
|
server {
|
|
listen $port;
|
|
listen [::]:$port;
|
|
server_name _;
|
|
|
|
client_max_body_size 0;
|
|
access_log /data/logs/admin_${port}_access.log standard;
|
|
error_log /data/logs/admin_${port}_error.log warn;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:81;
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_read_timeout 15m;
|
|
proxy_send_timeout 15m;
|
|
}
|
|
}
|
|
EOF
|
|
printf '%s\n' "$NPM_BLOCK_END"
|
|
} >> "$f"
|
|
# Leerzeilen am Anfang aufraeumen
|
|
sed -i '/./,$!d' "$f"
|
|
}
|
|
|
|
port_in_use() {
|
|
command -v ss >/dev/null 2>&1 || return 1
|
|
ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$1\$"
|
|
}
|
|
|
|
valid_port() {
|
|
case "$1" in
|
|
''|*[!0-9]*) return 1 ;;
|
|
esac
|
|
[ "$1" -ge 1 ] && [ "$1" -le 65535 ]
|
|
}
|
|
|
|
cmd_npm_port() {
|
|
local port="" restart=1
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--no-restart) restart=""; shift ;;
|
|
-h|--help)
|
|
cat <<'EOF'
|
|
cmk-manage.sh npm-port [PORT] [--no-restart]
|
|
|
|
Legt fest, auf welchem Port die Adminoberflaeche des NGINX Proxy Managers
|
|
erreichbar ist. Ohne PORT wird der aktuelle Stand angezeigt.
|
|
|
|
81 = Vorgabe, kein Zusatzeintrag noetig
|
|
sonst = zusaetzlicher nginx-Server auf diesem Port (Port 81 bleibt lokal
|
|
bestehen und sollte per Firewall dichtgehalten werden)
|
|
EOF
|
|
return 0 ;;
|
|
*) port="$1"; shift ;;
|
|
esac
|
|
done
|
|
|
|
local conf; conf="$(npm_custom_conf_path)"
|
|
|
|
if [ -z "$port" ]; then
|
|
printf '\n%sAdminoberflaeche des NGINX Proxy Managers%s\n' "$C_BLD" "$C_RST"
|
|
printf ' Konfigurierter Port : %s\n' "${NPM_ADMIN_PORT:-81}"
|
|
printf ' Adresse : %s\n' "${NPM_ADMIN_URL:-http://127.0.0.1:81}"
|
|
printf ' Zusatzkonfiguration : %s\n' \
|
|
"$(grep -qsF "$NPM_BLOCK_START" "$conf" && echo "$conf" || echo 'keine (Vorgabe 81)')"
|
|
if [ "${NPM_ADMIN_PORT:-81}" != 81 ]; then
|
|
printf ' Port 81 lokal : %s\n' "$(port_in_use 81 && echo 'lauscht (per Firewall sperren)' || echo 'lauscht nicht')"
|
|
fi
|
|
echo
|
|
return 0
|
|
fi
|
|
|
|
valid_port "$port" || die "'$port' ist keine gueltige Portnummer (1-65535)."
|
|
|
|
npm_enabled || warn "Der NGINX Proxy Manager ist abgeschaltet (NPM_ENABLED=off).
|
|
Die Einstellung wird gespeichert, wirkt aber erst nach dem Einschalten:
|
|
$SELF npm-enable on"
|
|
|
|
case "$port" in
|
|
80|443) die "Port $port wird vom Proxy Manager bereits fuer HTTP/HTTPS genutzt." ;;
|
|
5000) die "Port 5000 gehoert der Checkmk-Oberflaeche." ;;
|
|
8000) die "Port 8000 gehoert dem Checkmk-Agent-Receiver." ;;
|
|
6557) die "Port 6557 gehoert Livestatus." ;;
|
|
esac
|
|
|
|
if [ "$port" != 81 ] && port_in_use "$port"; then
|
|
warn "Auf Port $port lauscht bereits ein Dienst."
|
|
confirm "Trotzdem fortfahren?" || die "Abgebrochen."
|
|
fi
|
|
|
|
if [ "$port" = 81 ]; then
|
|
npm_conf_strip_block "$conf"
|
|
[ -s "$conf" ] || rm -f "$conf"
|
|
ok "Zusatzkonfiguration entfernt - es gilt wieder die Vorgabe (Port 81)."
|
|
else
|
|
npm_conf_write_block "$conf" "$port"
|
|
ok "Zusatzkonfiguration geschrieben: $conf"
|
|
fi
|
|
|
|
env_set NPM_ADMIN_PORT "$port"
|
|
env_set NPM_ADMIN_URL "http://127.0.0.1:$port"
|
|
NPM_ADMIN_PORT="$port"; NPM_ADMIN_URL="http://127.0.0.1:$port"
|
|
|
|
if [ -n "$restart" ] && container_running "$NPM_CONTAINER_NAME"; then
|
|
log "Starte '$NPM_CONTAINER_NAME' neu, damit nginx die Konfiguration liest ..."
|
|
if $RUNTIME restart "$NPM_CONTAINER_NAME" >/dev/null; then
|
|
ok "Proxy Manager neu gestartet."
|
|
else
|
|
warn "Neustart fehlgeschlagen - bitte manuell: $SELF restart"
|
|
fi
|
|
else
|
|
log "Wirksam nach: $SELF up (bzw. Neustart des Proxy Managers)"
|
|
fi
|
|
|
|
local ip; ip="$(primary_ip)"
|
|
cat <<EOF
|
|
|
|
${C_BLD}Erreichbar unter${C_RST}
|
|
lokal : http://127.0.0.1:$port
|
|
aus dem LAN: http://$ip:$port
|
|
|
|
${C_BLD}Firewall${C_RST}
|
|
firewalld : firewall-cmd --permanent --add-port=$port/tcp && firewall-cmd --reload
|
|
ufw : ufw allow $port/tcp
|
|
$([ "$port" != 81 ] && printf ' Port 81 sollte weiterhin gesperrt bleiben - er lauscht lokal weiter.\n')
|
|
EOF
|
|
}
|
|
|
|
# =====================================================================
|
|
# Einstellungen (.env) ueber Menue bzw. Kommandozeile
|
|
#
|
|
# Tabelle aller pflegbaren Schluessel:
|
|
# kategorie|schluessel|typ|beschriftung|auswahl|leer_erlaubt
|
|
# Typen: text int port bool choice password path url
|
|
# =====================================================================
|
|
settings_defs() {
|
|
cat <<'EOF'
|
|
runtime|CMK_RUNTIME|choice|Container-Runtime|auto:docker:podman|1
|
|
runtime|TZ|text|Zeitzone||
|
|
image|CMK_IMAGE|info|VERWENDETES Image (setzt der Assistent)||1
|
|
image|CMK_EDITION|info|Edition (setzt der Assistent)||1
|
|
image|CMK_VERSION|info|Version (setzt der Assistent)||1
|
|
image|CMK_REGISTRY|text|Registry fuer Enterprise||1
|
|
image|CMK_REGISTRY_USER|text|Registry-Benutzer||1
|
|
image|CMK_REGISTRY_PASSWORD|password|Registry-Kennwort||1
|
|
image|CMK_DOWNLOAD_BASEURL|url|Basis-URL des Downloadportals||1
|
|
image|CMK_DOWNLOAD_URL|url|Feste Download-URL (optional)||1
|
|
image|CMK_DOWNLOAD_USER|text|Downloadportal-Benutzer||1
|
|
image|CMK_DOWNLOAD_PASSWORD|password|Downloadportal-Kennwort||1
|
|
checkmk|CMK_SITE_ID|text|Site-Name (Standard-Site)||
|
|
checkmk|CMK_PASSWORD|password|Startkennwort fuer cmkadmin||1
|
|
checkmk|CMK_CONTAINER_NAME|text|Containername Checkmk||
|
|
checkmk|CMK_HOSTNAME|text|Hostname im Container||
|
|
checkmk|CMK_LIVESTATUS_TCP|choice|Livestatus ueber TCP (Port 6557)|off:on|
|
|
checkmk|MAIL_RELAY_HOST|text|SMTP-Relay fuer Benachrichtigungen||1
|
|
npm|NPM_ENABLED|choice|Proxy Manager betreiben|on:off|
|
|
npm|NPM_ADMIN_PORT|port|Port der Adminoberflaeche||
|
|
npm|NPM_IMAGE|text|Image des Proxy Managers||
|
|
npm|NPM_CONTAINER_NAME|text|Containername Proxy Manager||
|
|
npm|NPM_DISABLE_IPV6|choice|IPv6 abschalten|true:false|
|
|
browser|BROWSER_ENGINE|choice|Vorgabe-Engine|browsh:carbonyl:links:w3m:lynx:shell|
|
|
browser|NPM_ADMIN_URL|url|Startseite des Browsers||1
|
|
browser|BROWSH_IMAGE|text|Image fuer browsh||
|
|
browser|CARBONYL_IMAGE|text|Image fuer carbonyl||
|
|
browser|BROWSER_TOOLS_IMAGE|text|Werkzeug-Image (leer = automatisch)||1
|
|
paths|CMK_DATA_DIR|path|Checkmk-Sites (Bind-Mount)||
|
|
paths|NPM_DATA_DIR|path|Proxy-Manager-Daten||
|
|
paths|NPM_LE_DIR|path|Let's-Encrypt-Zertifikate||
|
|
paths|BACKUP_DIR|path|Backups und Bundles (manuell)||
|
|
backup|BACKUP_AUTO|choice|Automatische Backups per cron|off:on|
|
|
paths|IMAGE_DIR|path|Heruntergeladene Image-Tarballs||
|
|
cert|CERT_DOMAIN|text|FQDN fuer selbstsignierte Zertifikate||
|
|
cert|CERT_DAYS|int|Gueltigkeit in Tagen||
|
|
cert|CERT_COUNTRY|text|Laenderkuerzel||
|
|
cert|CERT_ORG|text|Organisation||
|
|
EOF
|
|
}
|
|
|
|
settings_categories() {
|
|
cat <<'EOF'
|
|
runtime|Runtime und Zeitzone
|
|
image|Checkmk-Image und Zugangsdaten
|
|
checkmk|Checkmk-Site und Container
|
|
npm|NGINX Proxy Manager
|
|
browser|Terminal-Browser
|
|
paths|Verzeichnisse
|
|
cert|Selbstsignierte Zertifikate
|
|
backup|Backup-Automatik
|
|
EOF
|
|
}
|
|
|
|
settings_def_for() { # KEY -> "kategorie|key|typ|label|auswahl|leer"
|
|
settings_defs | awk -F'|' -v k="$1" '$2==k {print; exit}'
|
|
}
|
|
|
|
settings_value() { # KEY -> aktueller Wert
|
|
eval "printf '%s' \"\${$1-}\""
|
|
}
|
|
|
|
settings_display() { # KEY TYP -> Anzeigewert (Kennwoerter maskiert)
|
|
local v; v="$(settings_value "$1")"
|
|
if [ "$2" = password ] && [ -n "$v" ]; then
|
|
printf '********'
|
|
elif [ -z "$v" ]; then
|
|
printf '<leer>'
|
|
else
|
|
printf '%s' "$v"
|
|
fi
|
|
}
|
|
|
|
settings_validate() { # TYP WERT LEER_ERLAUBT -> 0/1, Meldung auf stdout
|
|
local typ="$1" val="$2" opt="$3"
|
|
if [ -z "$val" ]; then
|
|
[ -n "$opt" ] && return 0
|
|
echo "Der Wert darf nicht leer sein."; return 1
|
|
fi
|
|
case "$typ" in
|
|
port) valid_port "$val" || { echo "Keine gueltige Portnummer (1-65535)."; return 1; } ;;
|
|
int) case "$val" in ''|*[!0-9]*) echo "Bitte eine Zahl angeben."; return 1 ;; esac ;;
|
|
url) case "$val" in http://*|https://*) : ;; *) echo "Bitte mit http:// oder https:// beginnen."; return 1 ;; esac ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
# Zusaetzliche, schluesselbezogene Pruefung (ueber den Typ hinaus)
|
|
settings_validate_key() {
|
|
local key="$1" val="$2"
|
|
case "$key" in
|
|
CMK_RUNTIME)
|
|
[ -z "$val" ] && return 0
|
|
command -v "$val" >/dev/null 2>&1 \
|
|
|| { echo "'$val' ist auf diesem System nicht installiert."; return 1; } ;;
|
|
CMK_SITE_ID)
|
|
case "$val" in
|
|
*[!a-zA-Z0-9_]*) echo "Ein Site-Name darf nur Buchstaben, Ziffern und _ enthalten."; return 1 ;;
|
|
esac
|
|
[ "${#val}" -le 16 ] || { echo "Ein Site-Name darf hoechstens 16 Zeichen lang sein."; return 1; } ;;
|
|
CMK_IMAGE|NPM_IMAGE|BROWSH_IMAGE|CARBONYL_IMAGE)
|
|
if [ -n "$val" ] && image_is_short_name "$val"; then
|
|
echo "Bitte vollqualifiziert angeben (z.B. docker.io/...) - Podman
|
|
findet kurze Namen sonst nicht."; return 1
|
|
fi ;;
|
|
esac
|
|
return 0
|
|
}
|
|
|
|
# Nacharbeit nach dem Setzen bestimmter Schluessel
|
|
settings_post_set() {
|
|
local key="$1" val="$2"
|
|
case "$key" in
|
|
NPM_ADMIN_PORT)
|
|
# Der nginx-Zusatzblock muss mitgezogen werden
|
|
cmd_npm_port "$val" ;;
|
|
NPM_ENABLED)
|
|
# Container entsprechend entfernen bzw. starten
|
|
cmd_npm_enable "$val" ;;
|
|
BACKUP_AUTO)
|
|
cmd_job auto "$val" ;;
|
|
CMK_IMAGE|CMK_SITE_ID|CMK_CONTAINER_NAME|NPM_IMAGE|NPM_CONTAINER_NAME|TZ|CMK_LIVESTATUS_TCP|NPM_DISABLE_IPV6|CMK_DATA_DIR|NPM_DATA_DIR|NPM_LE_DIR)
|
|
log "Wirksam nach: $SELF up (Container werden neu erstellt)" ;;
|
|
esac
|
|
}
|
|
|
|
cmd_config() {
|
|
local sub="${1:-list}"; [ $# -gt 0 ] && shift || true
|
|
case "$sub" in
|
|
list|ls)
|
|
local cat_filter=""
|
|
[ "${1:-}" = "--category" ] && cat_filter="$2"
|
|
local line c k t l
|
|
while IFS='|' read -r c k t l _ _; do
|
|
[ -n "$k" ] || continue
|
|
[ -n "$cat_filter" ] && [ "$c" != "$cat_filter" ] && continue
|
|
printf '%-24s %-10s %s\n' "$k" "$c" "$(settings_display "$k" "$t")"
|
|
done < <(settings_defs)
|
|
;;
|
|
get)
|
|
[ $# -ge 1 ] || die "Schluessel fehlt: $SELF config get KEY"
|
|
[ -n "$(settings_def_for "$1")" ] || die "Unbekannter Schluessel '$1'. Uebersicht: $SELF config list"
|
|
settings_value "$1"; echo
|
|
;;
|
|
set)
|
|
[ $# -ge 2 ] || die "Aufruf: $SELF config set KEY WERT"
|
|
local key="$1" val="$2" def typ opt msg
|
|
def="$(settings_def_for "$key")"
|
|
[ -n "$def" ] || die "Unbekannter Schluessel '$key'. Uebersicht: $SELF config list"
|
|
typ="$(printf '%s' "$def" | cut -d'|' -f3)"
|
|
opt="$(printf '%s' "$def" | cut -d'|' -f6)"
|
|
[ "$val" = "auto" ] && [ "$key" = CMK_RUNTIME ] && val=""
|
|
if ! msg="$(settings_validate "$typ" "$val" "$opt")"; then
|
|
die "$msg"
|
|
fi
|
|
if ! msg="$(settings_validate_key "$key" "$val")"; then
|
|
die "$msg"
|
|
fi
|
|
if [ "$key" = NPM_ADMIN_PORT ]; then
|
|
cmd_npm_port "$val"
|
|
elif [ "$key" = NPM_ENABLED ]; then
|
|
cmd_npm_enable "$val"
|
|
elif [ "$key" = BACKUP_AUTO ]; then
|
|
env_set "$key" "$val"; BACKUP_AUTO="$val"; cmd_job auto "$val"
|
|
else
|
|
env_set "$key" "$val" "$opt"
|
|
ok "$key=$val"
|
|
settings_post_set "$key" "$val"
|
|
fi
|
|
;;
|
|
edit)
|
|
local f="${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
"${EDITOR:-nano}" "$f"; load_env "$f"; ok "Konfiguration neu eingelesen." ;;
|
|
*) die "Unbekanntes Unterkommando 'config $sub'.
|
|
Moeglich: list [--category KAT] | get KEY | set KEY WERT | edit" ;;
|
|
esac
|
|
}
|
|
|
|
# =====================================================================
|
|
# Terminal-Browser im Container
|
|
#
|
|
# Zweck: den NGINX Proxy Manager direkt auf der Maschine einrichten,
|
|
# wenn dessen Adminoberflaeche (Port 81) von aussen nicht erreichbar ist.
|
|
#
|
|
# WICHTIG: Die Adminoberflaeche des Proxy Managers ist eine JavaScript-
|
|
# Anwendung. Klassische Textbrowser (links2, w3m, lynx) koennen sie NICHT
|
|
# darstellen. Dafuer gibt es zwei Engines, die einen echten Browser im
|
|
# Terminal rendern:
|
|
# browsh - Firefox im Terminal (gepflegt)
|
|
# carbonyl - Chromium im Terminal (schoenere Darstellung, Stand 2023)
|
|
# Die Textbrowser bleiben fuer schnelle Pruefungen nuetzlich.
|
|
#
|
|
# Der Container laeuft im Host-Netzwerk und erreicht damit 127.0.0.1:81
|
|
# genau wie der Proxy Manager selbst.
|
|
# =====================================================================
|
|
|
|
browser_tools_image() {
|
|
if [ -n "${BROWSER_TOOLS_IMAGE:-}" ]; then
|
|
printf '%s\n' "$BROWSER_TOOLS_IMAGE"
|
|
elif [ "$RUNTIME" = podman ]; then
|
|
# Podman legt selbst gebaute Images unter localhost/ ab
|
|
printf '%s\n' "localhost/checkmk-stack/browser-tools:1"
|
|
else
|
|
# Docker wuerde ein "localhost/"-Praefix als echte Registry deuten
|
|
printf '%s\n' "checkmk-stack/browser-tools:1"
|
|
fi
|
|
}
|
|
|
|
browser_build_tools() {
|
|
local img; img="$(browser_tools_image)"
|
|
local ctx="$PROJECT_DIR/containers/browser"
|
|
[ -f "$ctx/Containerfile" ] || die "Containerfile fehlt: $ctx/Containerfile"
|
|
log "Baue Werkzeug-Image '$img' ..."
|
|
# -f explizit: Docker sucht sonst nach "Dockerfile", Podman nimmt beides
|
|
$RUNTIME build -f "$ctx/Containerfile" -t "$img" "$ctx" \
|
|
|| die "Bau des Werkzeug-Images fehlgeschlagen."
|
|
ok "Image gebaut: $img"
|
|
}
|
|
|
|
browser_ensure_tools() {
|
|
local img; img="$(browser_tools_image)"
|
|
image_exists_local "$img" || browser_build_tools
|
|
}
|
|
|
|
# Antwortet die Startseite ueberhaupt? Ein Terminal-Browser zeigt sonst
|
|
# nur eine schwarze Seite - ohne jede Fehlermeldung.
|
|
browser_url_reachable() {
|
|
command -v curl >/dev/null 2>&1 || return 0 # ohne curl nicht pruefbar
|
|
local code
|
|
# ACHTUNG: curl gibt bei einem Fehler selbst schon "000" aus. Ein
|
|
# zusaetzliches "|| echo 000" wuerde einen zweiten Wert anhaengen und
|
|
# jeden Vergleich verfaelschen - deshalb den Zuweisungsfehler abfangen.
|
|
code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 6 -L "$1" 2>/dev/null)" || code=000
|
|
[ -n "$code" ] && [ "$code" != "000" ]
|
|
}
|
|
|
|
# Erklaert, warum nichts antwortet, und nennt den passenden naechsten Schritt
|
|
browser_diagnose_url() { # url -> mehrzeiliger Text auf stdout
|
|
local url="$1" host port
|
|
host="$(printf '%s' "$url" | sed -E 's|^[a-z]+://([^/:]+).*|\1|')"
|
|
port="$(printf '%s' "$url" | sed -nE 's|^[a-z]+://[^/:]+:([0-9]+).*|\1|p')"
|
|
|
|
printf 'Auf %s antwortet nichts.\n' "$url"
|
|
printf 'Ein Terminal-Browser zeigt dann nur eine schwarze Seite.\n\n'
|
|
|
|
case "$host" in
|
|
127.0.0.1|localhost|::1)
|
|
if [ "${port:-81}" = "${NPM_ADMIN_PORT:-81}" ]; then
|
|
printf 'Das ist die Oberflaeche des NGINX Proxy Managers.\n'
|
|
if ! npm_enabled; then
|
|
printf 'Er ist abgeschaltet (NPM_ENABLED=off):\n'
|
|
printf ' %s npm-enable on\n' "$SELF"
|
|
elif ! container_exists "$NPM_CONTAINER_NAME"; then
|
|
printf 'Sein Container existiert nicht:\n'
|
|
printf ' %s up\n' "$SELF"
|
|
elif ! container_running "$NPM_CONTAINER_NAME"; then
|
|
printf 'Sein Container ist gestoppt:\n'
|
|
printf ' %s up\n' "$SELF"
|
|
else
|
|
printf 'Der Container laeuft, antwortet aber nicht auf Port %s.\n' "${port:-81}"
|
|
printf ' %s logs %s\n' "$SELF" "$NPM_CONTAINER_NAME"
|
|
printf ' %s npm-port (eingestellten Port pruefen)\n' "$SELF"
|
|
fi
|
|
elif [ "${port:-}" = 5000 ]; then
|
|
printf 'Das ist die Checkmk-Oberflaeche.\n'
|
|
if ! container_running "$CMK_CONTAINER_NAME"; then
|
|
printf 'Der Checkmk-Container laeuft nicht:\n %s up\n' "$SELF"
|
|
else
|
|
printf 'Der Container laeuft - Site gestartet?\n %s sites\n' "$SELF"
|
|
fi
|
|
else
|
|
printf 'Auf diesem Host lauscht nichts auf Port %s.\n' "${port:-80}"
|
|
printf ' ss -tlnp | grep %s\n' "${port:-80}"
|
|
fi ;;
|
|
*)
|
|
printf 'Externe Adressen brauchen Netzzugang vom Host aus\n'
|
|
printf '(der Container nutzt dessen Netzwerk direkt).\n'
|
|
printf ' curl -I %s\n' "$url" ;;
|
|
esac
|
|
}
|
|
|
|
cmd_browser() {
|
|
local engine="${BROWSER_ENGINE:-browsh}"
|
|
local url="" build="" build_only="" pull="" mono="" nocheck=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-e|--engine) engine="$2"; shift 2 ;;
|
|
-u|--url) url="$2"; shift 2 ;;
|
|
--build) build=1; shift ;;
|
|
--build-only) build=1; build_only=1; shift ;;
|
|
--pull) pull=1; shift ;;
|
|
--monochrome) mono=1; shift ;;
|
|
--no-check) nocheck=1; shift ;;
|
|
-h|--help)
|
|
cat <<'EOF'
|
|
cmk-manage.sh browser - Terminal-Browser im Container
|
|
|
|
-e, --engine ENGINE browsh Firefox im Terminal (Vorgabe, kann JavaScript)
|
|
carbonyl Chromium im Terminal (kann JavaScript)
|
|
links links2 (Textbrowser, KEIN JavaScript)
|
|
w3m w3m (Textbrowser, KEIN JavaScript)
|
|
lynx lynx (Textbrowser, KEIN JavaScript)
|
|
shell Bash mit curl/jq/openssl im Host-Netzwerk
|
|
-u, --url URL Startseite (Vorgabe: $NPM_ADMIN_URL, sonst
|
|
http://127.0.0.1:81 = Proxy-Manager-Oberflaeche)
|
|
--build Werkzeug-Image neu bauen und danach starten
|
|
--build-only Werkzeug-Image nur bauen (ohne Terminal, z.B. im Skript)
|
|
--pull Image der gewaehlten Engine neu laden
|
|
--monochrome nur bei browsh: einfarbige Darstellung
|
|
--no-check Startseite nicht vorab auf Erreichbarkeit pruefen
|
|
|
|
Bedienung browsh: Strg+L Adresszeile, Strg+T neuer Tab,
|
|
Strg+W Tab schliessen, Strg+Q beenden
|
|
EOF
|
|
return 0 ;;
|
|
*) die "Unbekannte Option fuer browser: $1" ;;
|
|
esac
|
|
done
|
|
|
|
url="${url:-${NPM_ADMIN_URL:-http://127.0.0.1:81}}"
|
|
|
|
# Engine-Namen zuerst pruefen, damit ein Tippfehler auch ohne
|
|
# Terminal eine brauchbare Meldung ergibt
|
|
case "$engine" in
|
|
browsh|carbonyl|links|links2|w3m|lynx|shell) : ;;
|
|
*) die "Unbekannte Engine '$engine'.
|
|
Moeglich: browsh | carbonyl | links | w3m | lynx | shell" ;;
|
|
esac
|
|
|
|
# Bauen braucht kein Terminal - deshalb vor der TTY-Pruefung
|
|
if [ -n "$build" ]; then
|
|
browser_build_tools
|
|
[ -n "$build_only" ] && return 0
|
|
fi
|
|
|
|
if [ ! -t 0 ] || [ ! -t 1 ]; then
|
|
die "Ein Terminal-Browser braucht ein echtes Terminal (TTY).
|
|
Bitte direkt auf der Konsole bzw. in einer SSH-Sitzung aufrufen,
|
|
nicht in einer Pipe oder aus einem Skript heraus."
|
|
fi
|
|
|
|
local image
|
|
local -a args=() runopts=()
|
|
case "$engine" in
|
|
browsh)
|
|
image="${BROWSH_IMAGE:-docker.io/browsh/browsh:v1.8.3}"
|
|
args=(--startup-url "$url")
|
|
[ -n "$mono" ] && args+=(--monochrome)
|
|
;;
|
|
carbonyl)
|
|
image="${CARBONYL_IMAGE:-docker.io/fathyb/carbonyl:latest}"
|
|
args=("$url")
|
|
;;
|
|
links|links2)
|
|
browser_ensure_tools; image="$(browser_tools_image)"; args=(links2 "$url") ;;
|
|
w3m)
|
|
browser_ensure_tools; image="$(browser_tools_image)"; args=(w3m "$url") ;;
|
|
lynx)
|
|
browser_ensure_tools; image="$(browser_tools_image)"; args=(lynx "$url") ;;
|
|
shell)
|
|
browser_ensure_tools; image="$(browser_tools_image)"; args=(/bin/bash) ;;
|
|
esac
|
|
|
|
if [ -n "$pull" ]; then
|
|
case "$engine" in
|
|
browsh|carbonyl) log "Lade $image ..."; $RUNTIME pull "$image" || die "Konnte '$image' nicht laden." ;;
|
|
*) browser_build_tools ;;
|
|
esac
|
|
fi
|
|
|
|
case "$engine" in
|
|
links|w3m|lynx)
|
|
warn "Textbrowser koennen kein JavaScript - die Oberflaeche des
|
|
NGINX Proxy Managers laesst sich damit NICHT bedienen.
|
|
Dafuer: $SELF browser --engine browsh" ;;
|
|
esac
|
|
|
|
# Nicht erreichbare Startseite ist die haeufigste Ursache fuer eine
|
|
# scheinbar haengende, schwarze Anzeige.
|
|
if [ -z "$nocheck" ] && ! browser_url_reachable "$url"; then
|
|
echo
|
|
warn "$(browser_diagnose_url "$url")"
|
|
echo
|
|
confirm "Trotzdem starten?" || die "Abgebrochen."
|
|
fi
|
|
|
|
log "Starte $engine im Host-Netzwerk -> $url"
|
|
[ "$engine" = browsh ] && log "Beenden mit Strg+Q, Adresszeile mit Strg+L."
|
|
|
|
runopts=(--rm -it --network host
|
|
-e "TERM=${TERM:-xterm-256color}"
|
|
-e "COLORTERM=${COLORTERM:-truecolor}")
|
|
|
|
$RUNTIME run "${runopts[@]}" "$image" "${args[@]}"
|
|
}
|
|
|
|
# =====================================================================
|
|
# Selbstsigniertes Zertifikat fuer den NGINX Proxy Manager
|
|
# =====================================================================
|
|
cmd_cert() {
|
|
local domain="${CERT_DOMAIN:-checkmk.example.local}" days="${CERT_DAYS:-3650}"
|
|
local -a alts=()
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--domain) domain="$2"; shift 2 ;;
|
|
--alt) alts+=("$2"); shift 2 ;;
|
|
--days) days="$2"; shift 2 ;;
|
|
*) die "Unbekannte Option fuer cert: $1" ;;
|
|
esac
|
|
done
|
|
command -v openssl >/dev/null 2>&1 || die "openssl ist nicht installiert."
|
|
|
|
local outdir="$PROJECT_DIR/data/npm/custom-certs/$domain"
|
|
mkdir -p "$outdir"
|
|
|
|
local san="DNS:$domain"
|
|
local a
|
|
for a in ${alts[@]+"${alts[@]}"}; do
|
|
case "$a" in
|
|
[0-9]*.[0-9]*.[0-9]*.[0-9]*) san="$san,IP:$a" ;;
|
|
*) san="$san,DNS:$a" ;;
|
|
esac
|
|
done
|
|
local myip; myip="$(primary_ip)"
|
|
[ -n "$myip" ] && san="$san,IP:$myip"
|
|
|
|
log "Erzeuge selbstsigniertes Zertifikat fuer '$domain' ($days Tage) ..."
|
|
openssl req -x509 -nodes -newkey rsa:4096 -sha256 -days "$days" \
|
|
-keyout "$outdir/privkey.pem" -out "$outdir/fullchain.pem" \
|
|
-subj "/C=${CERT_COUNTRY:-DE}/O=${CERT_ORG:-Selfsigned}/CN=$domain" \
|
|
-addext "subjectAltName=$san" \
|
|
-addext "basicConstraints=critical,CA:FALSE" \
|
|
-addext "keyUsage=critical,digitalSignature,keyEncipherment" \
|
|
-addext "extendedKeyUsage=serverAuth" 2>/dev/null \
|
|
|| die "Zertifikatserstellung fehlgeschlagen."
|
|
chmod 600 "$outdir/privkey.pem"
|
|
|
|
ok "Zertifikat erstellt:"
|
|
printf ' Schluessel : %s\n' "$outdir/privkey.pem"
|
|
printf ' Zertifikat : %s\n' "$outdir/fullchain.pem"
|
|
printf ' SAN : %s\n\n' "$san"
|
|
cat <<EOF
|
|
Einbinden im NGINX Proxy Manager:
|
|
1) http://$(primary_ip):81 -> SSL Certificates -> Add SSL Certificate -> Custom
|
|
2) Name z.B. "$domain"
|
|
Certificate Key : privkey.pem
|
|
Certificate : fullchain.pem
|
|
3) Im Proxy-Host unter "SSL" dieses Zertifikat auswaehlen,
|
|
"Force SSL" und "HTTP/2 Support" aktivieren.
|
|
EOF
|
|
}
|
|
|
|
# =====================================================================
|
|
# ncurses-Oberflaeche
|
|
# =====================================================================
|
|
# ---------------------------------------------------------------------
|
|
# GUI-sichere Vorbedingungen
|
|
#
|
|
# In der Oberflaeche darf nichts "die" aufrufen - das wuerde die ganze TUI
|
|
# beenden. Diese Helfer melden das Problem im Dialog und geben 1 zurueck.
|
|
# ---------------------------------------------------------------------
|
|
gui_need_container() {
|
|
if ! container_exists "$CMK_CONTAINER_NAME"; then
|
|
ui_msg "Checkmk-Container fehlt" \
|
|
"Der Container '$CMK_CONTAINER_NAME' existiert nicht.
|
|
|
|
Dafuer wird er zuerst gebraucht. Im Hauptmenue:
|
|
stack -> Stack starten
|
|
|
|
Auf der Kommandozeile:
|
|
$SELF up"
|
|
return 1
|
|
fi
|
|
if ! container_running "$CMK_CONTAINER_NAME"; then
|
|
ui_msg "Checkmk-Container gestoppt" \
|
|
"Der Container '$CMK_CONTAINER_NAME' existiert, laeuft aber nicht.
|
|
|
|
Im Hauptmenue: stack -> Stack starten
|
|
Kommandozeile: $SELF up"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
gui_need_sites() {
|
|
gui_need_container || return 1
|
|
local n; n="$(cmk_sites | sed '/^$/d' | wc -l)"
|
|
if [ "$n" -eq 0 ]; then
|
|
ui_msg "Keine Sites" \
|
|
"Im Container ist noch keine Site vorhanden.
|
|
|
|
Der Container legt die Site aus CMK_SITE_ID beim ersten Start selbst an.
|
|
Alternativ eine Site importieren: Hauptmenue -> import"
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
# Interaktives Kommando ausfuehren, ohne dass ein Fehler die TUI beendet
|
|
gui_run_interactive() {
|
|
clear 2>/dev/null || true
|
|
if ! ( "$@" ); then
|
|
printf '\n%sDas Kommando wurde mit einem Fehler beendet.%s\n' "$C_YEL" "$C_RST"
|
|
fi
|
|
printf '\nWeiter mit ENTER ...'
|
|
read -r _ </dev/tty || true
|
|
}
|
|
|
|
gui_pick_site() { # -> Site auf stdout
|
|
gui_need_container || return 1
|
|
local -a items=() s
|
|
while IFS= read -r s; do
|
|
[ -n "$s" ] || continue
|
|
items+=("$s" "$(cmk_site_version "$s") / $(cmk_site_state_text "$s")")
|
|
done < <(cmk_sites)
|
|
[ ${#items[@]} -gt 0 ] || { ui_msg "Sites" "Im Container ist keine Site vorhanden."; return 1; }
|
|
ui_menu "Site waehlen" "Vorhandene Sites im Container:" "${items[@]}"
|
|
}
|
|
|
|
gui_pick_backup() { # -> Datei auf stdout
|
|
local -a items=() f
|
|
while IFS= read -r f; do
|
|
[ -n "$f" ] || continue
|
|
bundle_read "$f" 2>/dev/null || continue
|
|
items+=("$f" "$B_SITE ${B_CMK_VERSION:-?} $(human_size "$(file_size "$f")")")
|
|
done < <(list_backup_files)
|
|
if [ ${#items[@]} -eq 0 ]; then
|
|
ui_msg "Backups" "In $BACKUP_DIR wurde kein Backup gefunden.\n\nAlternativ kann im naechsten Schritt frei im Dateisystem gesucht werden."
|
|
ui_pick_file "$PROJECT_DIR" '*.tar.gz'
|
|
return
|
|
fi
|
|
items+=("__browse__" "... anderes Verzeichnis durchsuchen ...")
|
|
local sel
|
|
sel="$(ui_menu "Backup waehlen" "Backups in $BACKUP_DIR:" "${items[@]}")" || return 1
|
|
if [ "$sel" = "__browse__" ]; then
|
|
ui_pick_file "$PROJECT_DIR" '*.tar.gz'
|
|
else
|
|
printf '%s\n' "$sel"
|
|
fi
|
|
}
|
|
|
|
gui_status() {
|
|
local f; f="$(mktemp)"
|
|
NO_COLOR=1 cmd_status >"$f" 2>&1 || true
|
|
ui_text "Status" "$f"
|
|
rm -f "$f"
|
|
}
|
|
|
|
gui_stack_menu() {
|
|
while :; do
|
|
local sel
|
|
sel="$(ui_menu "Stack steuern" "Container-Runtime: $RUNTIME" \
|
|
up "Stack starten (compose up -d)" \
|
|
restart "Stack neu starten" \
|
|
down "Stack stoppen und entfernen" \
|
|
pull "Images aktualisieren" \
|
|
logs "Letzte 200 Logzeilen ansehen" \
|
|
back "zurueck")" || return 0
|
|
case "$sel" in
|
|
up) ui_run "Stack starten" bash -c "cd '$PROJECT_DIR' && '$SELF' up" ;;
|
|
restart) ui_run "Neu starten" bash -c "cd '$PROJECT_DIR' && '$SELF' restart" ;;
|
|
down) ui_yesno "Stack stoppen" "Container wirklich stoppen und entfernen?\n(Die Daten unter ./data bleiben erhalten.)" \
|
|
&& ui_run "Stack stoppen" bash -c "cd '$PROJECT_DIR' && '$SELF' down" ;;
|
|
pull) ui_run "Images laden" bash -c "cd '$PROJECT_DIR' && '$SELF' pull" ;;
|
|
logs) local lf; lf="$(mktemp)"; compose logs --tail=200 >"$lf" 2>&1 || true
|
|
ui_text "Logs (letzte 200 Zeilen)" "$lf"; rm -f "$lf" ;;
|
|
back) return 0 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
gui_backup() {
|
|
gui_need_sites || return 0
|
|
local -a items=() s
|
|
while IFS= read -r s; do
|
|
[ -n "$s" ] || continue
|
|
items+=("$s" "$(cmk_site_version "$s")" off)
|
|
done < <(cmk_sites)
|
|
[ ${#items[@]} -gt 0 ] || { ui_msg "Backup" "Keine Sites vorhanden."; return; }
|
|
|
|
local chosen
|
|
chosen="$(ui_checklist "Backup" "Zu sichernde Site(s) mit LEERTASTE markieren:" "${items[@]}")" || return 0
|
|
[ -n "$chosen" ] || { ui_msg "Backup" "Keine Site ausgewaehlt."; return; }
|
|
|
|
local optsel
|
|
optsel="$(ui_checklist "Backup-Optionen" "Optionen waehlen:" \
|
|
no-rrds "Performance-Daten (RRD) auslassen - kleineres Archiv" off \
|
|
no-logs "Logdateien auslassen" off \
|
|
stop "Site waehrend des Backups stoppen (konsistenter)" off)" || return 0
|
|
|
|
local label
|
|
label="$(ui_input "Kennzeichnung" "Optionaler Text fuer den Dateinamen (leer = keiner):" "")" || label=""
|
|
label="$(printf '%s' "$label" | tr -cd 'A-Za-z0-9._-')"
|
|
|
|
local -a args=(backup --dir "$BACKUP_DIR")
|
|
[ -n "$label" ] && args+=(--label "$label")
|
|
case "$optsel" in *no-rrds*) args+=(--no-rrds);; esac
|
|
case "$optsel" in *no-logs*) args+=(--no-logs);; esac
|
|
case "$optsel" in *stop*) args+=(--stop);; esac
|
|
local sitename
|
|
for sitename in $(printf '%s' "$chosen" | tr -d '"'); do
|
|
args+=(--site "$sitename")
|
|
done
|
|
|
|
ui_run "Backup laeuft" bash -c "cd '$PROJECT_DIR' && '$SELF' -y ${args[*]}"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Backup: erstellen und planen
|
|
# ---------------------------------------------------------------------
|
|
gui_backup_menu() {
|
|
while :; do
|
|
local auto sel
|
|
auto="$(backup_auto_enabled && echo 'ein' || echo 'aus')"
|
|
sel="$(ui_menu "Backup erstellen / planen" \
|
|
"Manuelle Backups gehen immer nach:
|
|
$BACKUP_DIR (Einstellung BACKUP_DIR)
|
|
|
|
Geplante Jobs haben ihr eigenes Zielverzeichnis.
|
|
Automatische Ausfuehrung: $auto" \
|
|
manuell "Backup jetzt erstellen" \
|
|
jobs "Backup-Jobs verwalten (anlegen/aendern/loeschen)" \
|
|
auto "Automatische Ausfuehrung ein/aus (derzeit: $auto)" \
|
|
status "Zeitplan und Jobs anzeigen" \
|
|
jetzt "Einen Job sofort ausfuehren" \
|
|
back "zurueck")" || return 0
|
|
case "$sel" in
|
|
manuell) gui_backup || true ;;
|
|
jobs) gui_jobs_menu || true ;;
|
|
auto) gui_backup_auto || true ;;
|
|
status) local f; f="$(mktemp)"; NO_COLOR=1 cmd_job status >"$f" 2>&1 || true
|
|
ui_text "Backup-Zeitplan" "$f"; rm -f "$f" ;;
|
|
jetzt) gui_job_run_now || true ;;
|
|
back) return 0 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
gui_backup_auto() {
|
|
if backup_auto_enabled; then
|
|
ui_yesno "Automatische Backups" \
|
|
"Automatische Backups sind eingeschaltet.
|
|
|
|
Abschalten? Die Jobs bleiben erhalten, es wird nur
|
|
nichts mehr automatisch ausgefuehrt." || return 0
|
|
ui_run "Automatik abschalten" bash -c "cd '$PROJECT_DIR' && '$SELF' -y job auto off"
|
|
else
|
|
local n; n="$(jobs_read | awk -F'|' '$7=="on"' | wc -l)"
|
|
if [ "$n" -eq 0 ]; then
|
|
ui_msg "Keine aktiven Jobs" \
|
|
"Es ist noch kein aktiver Backup-Job angelegt.
|
|
|
|
Zuerst unter 'Backup-Jobs verwalten' einen Job anlegen -
|
|
dort wird auch der Zeitplan festgelegt."
|
|
return
|
|
fi
|
|
ui_yesno "Automatische Backups" \
|
|
"$n aktive(r) Job(s) werden in die System-Cron eingetragen
|
|
($CRON_FILE).
|
|
|
|
Dafuer werden root-Rechte benoetigt. Einschalten?" || return 0
|
|
ui_run "Automatik einschalten" bash -c "cd '$PROJECT_DIR' && '$SELF' -y job auto on"
|
|
fi
|
|
load_env "${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
}
|
|
|
|
gui_pick_job() { # -> Jobname auf stdout
|
|
local -a items=() line
|
|
while IFS= read -r line; do
|
|
[ -n "$line" ] || continue
|
|
items+=("$(job_f "$line" 1)" \
|
|
"$(job_f "$line" 3) -> $(job_f "$line" 4) [$(job_f "$line" 7)]")
|
|
done < <(jobs_read)
|
|
if [ ${#items[@]} -eq 0 ]; then
|
|
ui_msg "Keine Jobs" "Es ist noch kein Backup-Job angelegt."
|
|
return 1
|
|
fi
|
|
ui_menu "Job waehlen" "Vorhandene Backup-Jobs:" "${items[@]}"
|
|
}
|
|
|
|
gui_job_run_now() {
|
|
gui_need_sites || return 0
|
|
local j; j="$(gui_pick_job)" || return 0
|
|
[ -n "$j" ] || return 0
|
|
ui_yesno "Job ausfuehren" "Job '$j' jetzt ausfuehren?" || return 0
|
|
ui_run "Job '$j'" bash -c "cd '$PROJECT_DIR' && '$SELF' -y job run '$j'"
|
|
}
|
|
|
|
gui_jobs_menu() {
|
|
while :; do
|
|
local sel
|
|
sel="$(ui_menu "Backup-Jobs" \
|
|
"Jeder Job hat eigenen Zeitplan, eigenes Zielverzeichnis
|
|
und eigene Aufbewahrung.
|
|
|
|
Jobdatei: $(jobs_file)" \
|
|
list "Jobs anzeigen" \
|
|
neu "Neuen Job anlegen" \
|
|
edit "Job aendern" \
|
|
toggle "Job ein-/ausschalten" \
|
|
del "Job loeschen" \
|
|
back "zurueck")" || return 0
|
|
case "$sel" in
|
|
list) local f; f="$(mktemp)"; NO_COLOR=1 jobs_list_table >"$f" 2>&1 || true
|
|
ui_text "Backup-Jobs" "$f"; rm -f "$f" ;;
|
|
neu) gui_job_edit "" || true ;;
|
|
edit) local j; j="$(gui_pick_job)" || continue; [ -n "$j" ] && gui_job_edit "$j" ;;
|
|
toggle) local j2 l st
|
|
j2="$(gui_pick_job)" || continue; [ -n "$j2" ] || continue
|
|
l="$(job_line "$j2")"; st=disable
|
|
[ "$(job_f "$l" 7)" = off ] && st=enable
|
|
ui_run "Job $st" bash -c "cd '$PROJECT_DIR' && '$SELF' -y job $st '$j2'" ;;
|
|
del) local j3; j3="$(gui_pick_job)" || continue; [ -n "$j3" ] || continue
|
|
ui_yesno "Loeschen" "Job '$j3' wirklich loeschen?\n\n(Bereits erstellte Backups bleiben erhalten.)" \
|
|
&& ui_run "Job loeschen" bash -c "cd '$PROJECT_DIR' && '$SELF' -y job del '$j3'" ;;
|
|
back) return 0 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Zeitplan bequem zusammenklicken
|
|
gui_pick_cron() { # vorgabe -> cron-ausdruck auf stdout
|
|
local cur="${1:-15 2 * * *}" kind time hh mm dow dom
|
|
kind="$(ui_menu "Zeitplan" "Wie oft soll gesichert werden?" \
|
|
taeglich "jeden Tag" \
|
|
woechentlich "einmal pro Woche" \
|
|
monatlich "einmal pro Monat" \
|
|
frei "Cron-Ausdruck selbst eingeben")" || return 1
|
|
if [ "$kind" = frei ]; then
|
|
ui_input "Cron-Ausdruck" "Fuenf Felder: Minute Stunde Tag Monat Wochentag" "$cur"
|
|
return
|
|
fi
|
|
time="$(ui_input "Uhrzeit" "Uhrzeit im Format HH:MM" "02:15")" || return 1
|
|
case "$time" in
|
|
[0-9][0-9]:[0-9][0-9]) : ;;
|
|
*) ui_msg "Ungueltig" "Bitte im Format HH:MM angeben, z.B. 02:15."; return 1 ;;
|
|
esac
|
|
hh="${time%%:*}"; mm="${time##*:}"
|
|
hh="$((10#$hh))"; mm="$((10#$mm))"
|
|
[ "$hh" -le 23 ] && [ "$mm" -le 59 ] || { ui_msg "Ungueltig" "Stunde 0-23, Minute 0-59."; return 1; }
|
|
case "$kind" in
|
|
taeglich) printf '%s %s * * *\n' "$mm" "$hh" ;;
|
|
woechentlich) dow="$(ui_menu "Wochentag" "An welchem Tag?" \
|
|
1 Montag 2 Dienstag 3 Mittwoch 4 Donnerstag \
|
|
5 Freitag 6 Samstag 0 Sonntag)" || return 1
|
|
printf '%s %s * * %s\n' "$mm" "$hh" "$dow" ;;
|
|
monatlich) dom="$(ui_input "Tag im Monat" "Welcher Tag (1-28)?" "1")" || return 1
|
|
case "$dom" in ''|*[!0-9]*) ui_msg "Ungueltig" "Bitte eine Zahl."; return 1 ;; esac
|
|
printf '%s %s %s * *\n' "$mm" "$hh" "$dom" ;;
|
|
esac
|
|
}
|
|
|
|
gui_job_edit() { # name (leer = neu)
|
|
local name="$1" line="" sites="ALL" cron="15 2 * * *" dir="$BACKUP_DIR" opts="" keep="7"
|
|
if [ -n "$name" ]; then
|
|
line="$(job_line "$name")"
|
|
sites="$(job_f "$line" 2)"; cron="$(job_f "$line" 3)"; dir="$(job_f "$line" 4)"
|
|
opts="$(job_f "$line" 5)"; keep="$(job_f "$line" 6)"
|
|
else
|
|
name="$(ui_input "Neuer Job" "Name des Jobs (Buchstaben, Ziffern, _ und -):" "nachts")" || return 0
|
|
local msg
|
|
if ! msg="$(job_validate_name "$name")"; then ui_msg "Ungueltiger Name" "$msg"; return; fi
|
|
if job_exists "$name"; then ui_msg "Schon vorhanden" "Ein Job namens '$name' existiert bereits."; return; fi
|
|
fi
|
|
|
|
# Sites auswaehlen
|
|
if gui_need_sites; then
|
|
local -a items=() sname
|
|
items+=("ALL" "alle Sites (auch spaeter neu angelegte)" "$([ "$sites" = ALL ] && echo on || echo off)")
|
|
while IFS= read -r sname; do
|
|
[ -n "$sname" ] || continue
|
|
items+=("$sname" "einzelne Site" \
|
|
"$(printf '%s' ",$sites," | grep -q ",$sname," && echo on || echo off)")
|
|
done < <(cmk_sites)
|
|
local chosen
|
|
chosen="$(ui_checklist "Sites" "Was soll dieser Job sichern?\n(ALL schliesst alle anderen ein)" "${items[@]}")" || return 0
|
|
chosen="$(printf '%s' "$chosen" | tr -d '"')"
|
|
[ -n "$chosen" ] || { ui_msg "Keine Auswahl" "Es wurde keine Site gewaehlt."; return; }
|
|
case " $chosen " in
|
|
*" ALL "*) sites=ALL ;;
|
|
*) sites="$(printf '%s' "$chosen" | tr ' ' ',')" ;;
|
|
esac
|
|
fi
|
|
|
|
cron="$(gui_pick_cron "$cron")" || return 0
|
|
[ -n "$cron" ] || return 0
|
|
|
|
dir="$(ui_input "Zielverzeichnis" \
|
|
"Wohin soll dieser Job sichern?
|
|
(absolut oder relativ zum Projekt; wird bei Bedarf angelegt)" "$dir")" || return 0
|
|
[ -n "$dir" ] || return 0
|
|
|
|
keep="$(ui_input "Aufbewahrung" \
|
|
"Wie viele Backups sollen in diesem Verzeichnis bleiben?
|
|
0 = alle behalten" "$keep")" || return 0
|
|
case "$keep" in ''|*[!0-9]*) ui_msg "Ungueltig" "Bitte eine Zahl angeben."; return ;; esac
|
|
|
|
local optsel
|
|
optsel="$(ui_checklist "Optionen" "Optionen fuer diesen Job:" \
|
|
no-rrds "Performance-Daten (RRD) auslassen" "$(case "$opts" in *no-rrds*) echo on ;; *) echo off ;; esac)" \
|
|
no-logs "Logdateien auslassen" "$(case "$opts" in *no-logs*) echo on ;; *) echo off ;; esac)" \
|
|
stop "Site waehrend des Backups stoppen" "$(case "$opts" in *stop*) echo on ;; *) echo off ;; esac)")" || return 0
|
|
opts=""
|
|
case "$optsel" in *no-rrds*) opts="$opts --no-rrds" ;; esac
|
|
case "$optsel" in *no-logs*) opts="$opts --no-logs" ;; esac
|
|
case "$optsel" in *stop*) opts="$opts --stop" ;; esac
|
|
opts="$(printf '%s' "$opts" | sed 's/^ *//')"
|
|
|
|
local action=add; job_exists "$name" && action=set
|
|
ui_run "Job speichern" bash -c \
|
|
"cd '$PROJECT_DIR' && '$SELF' -y job $action --name '$name' --sites '$sites' \
|
|
--cron '$cron' --dir '$dir' --keep '$keep' $opts"
|
|
}
|
|
|
|
gui_restore() {
|
|
gui_need_container || return 0
|
|
local file; file="$(gui_pick_backup)" || return 0
|
|
[ -n "$file" ] || return 0
|
|
|
|
local f; f="$(mktemp)"
|
|
bundle_show "$file" >"$f" 2>&1 || true
|
|
ui_text "Backup-Informationen" "$f"; rm -f "$f"
|
|
|
|
bundle_read "$file" || { ui_msg "Fehler" "Diese Datei ist kein Checkmk-Backup."; return; }
|
|
local reuse=""
|
|
if cmk_site_exists "$B_SITE"; then
|
|
ui_yesno "Site existiert" "Die Site '$B_SITE' ist bereits vorhanden.\n\nSoll sie UEBERSCHRIEBEN werden?\nAlle aktuellen Daten dieser Site gehen verloren!" || return 0
|
|
reuse="--reuse --kill"
|
|
fi
|
|
ui_yesno "Restore starten" "Site '$B_SITE' aus\n$(basename "$file")\nwiederherstellen?" || return 0
|
|
ui_run "Restore laeuft" bash -c "cd '$PROJECT_DIR' && '$SELF' -y restore --file '$file' $reuse"
|
|
}
|
|
|
|
gui_import() {
|
|
gui_need_container || return 0
|
|
ui_msg "Site importieren" \
|
|
"Import einer Site von einer Direktinstallation (Bare-Metal/VM).
|
|
|
|
Voraussetzung: auf dem Quellserver wurde mit 'migrate_creator.sh'
|
|
ein Migrations-Bundle (.tar.gz) erzeugt und hierher kopiert.
|
|
|
|
Wichtig fuer die Agents:
|
|
- Site-Name bleibt unveraendert
|
|
- dieser Host uebernimmt die IP/den DNS-Namen des Quellservers
|
|
- Port 8000 (Agent-Receiver) ist erreichbar
|
|
Dann muessen die Agents NICHT neu eingerichtet werden."
|
|
|
|
local file; file="$(gui_pick_backup)" || return 0
|
|
[ -n "$file" ] || return 0
|
|
|
|
local f; f="$(mktemp)"
|
|
bundle_show "$file" >"$f" 2>&1 || true
|
|
ui_text "Bundle-Informationen" "$f"; rm -f "$f"
|
|
|
|
bundle_read "$file" || { ui_msg "Fehler" "Diese Datei ist kein Migrations-Bundle."; return; }
|
|
local reuse=""
|
|
if cmk_site_exists "$B_SITE"; then
|
|
ui_yesno "Site existiert" "Die Site '$B_SITE' existiert bereits im Container.\n\nUEBERSCHREIBEN?" || return 0
|
|
reuse="--reuse --kill"
|
|
fi
|
|
local setdef=""
|
|
ui_yesno "Standard-Site" "Soll CMK_SITE_ID in der .env auf '$B_SITE' gesetzt werden?\n\n(Empfohlen - sonst startet der Container nach einem Reboot\ndie importierte Site nicht automatisch.)" \
|
|
&& setdef="--set-default-site"
|
|
|
|
ui_run "Import laeuft" bash -c "cd '$PROJECT_DIR' && '$SELF' -y import --file '$file' $reuse $setdef"
|
|
}
|
|
|
|
gui_backups_menu() {
|
|
while :; do
|
|
local sel
|
|
sel="$(ui_menu "Backups verwalten" "Verzeichnis: $BACKUP_DIR" \
|
|
list "Backups auflisten" \
|
|
info "Details eines Backups anzeigen" \
|
|
verify "Backup pruefen (Struktur + Pruefsumme)" \
|
|
del "Backup loeschen" \
|
|
back "zurueck")" || return 0
|
|
case "$sel" in
|
|
list) local f; f="$(mktemp)"; NO_COLOR=1 cmd_list >"$f" 2>&1 || true
|
|
ui_text "Backups" "$f"; rm -f "$f" ;;
|
|
info) local b; b="$(gui_pick_backup)" || continue; [ -n "$b" ] || continue
|
|
local f2; f2="$(mktemp)"; bundle_show "$b" >"$f2" 2>&1 || true
|
|
ui_text "Details" "$f2"; rm -f "$f2" ;;
|
|
verify) local b2; b2="$(gui_pick_backup)" || continue; [ -n "$b2" ] || continue
|
|
ui_run "Pruefe Bundle" bash -c "cd '$PROJECT_DIR' && '$SELF' verify --file '$b2'" ;;
|
|
del) local b3; b3="$(gui_pick_backup)" || continue; [ -n "$b3" ] || continue
|
|
ui_yesno "Loeschen" "Datei wirklich loeschen?\n\n$b3" && rm -f "$b3" \
|
|
&& ui_msg "Geloescht" "Die Datei wurde entfernt." ;;
|
|
back) return 0 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
gui_sites_menu() {
|
|
gui_need_container || return 0
|
|
while :; do
|
|
local sel
|
|
sel="$(ui_menu "Sites" "Sites im Container '$CMK_CONTAINER_NAME'" \
|
|
list "Sites anzeigen" \
|
|
start "Site starten" \
|
|
stop "Site stoppen" \
|
|
all "alle Sites starten" \
|
|
back "zurueck")" || return 0
|
|
case "$sel" in
|
|
list) local f; f="$(mktemp)"; cmd_sites >"$f" 2>&1 || true; ui_text "Sites" "$f"; rm -f "$f" ;;
|
|
start) local s; s="$(gui_pick_site)" || continue; [ -n "$s" ] || continue
|
|
ui_run "Site starten" bash -c "cd '$PROJECT_DIR' && '$SELF' site-start '$s'" ;;
|
|
stop) local s2; s2="$(gui_pick_site)" || continue; [ -n "$s2" ] || continue
|
|
ui_run "Site stoppen" bash -c "cd '$PROJECT_DIR' && '$SELF' site-stop '$s2'" ;;
|
|
all) ui_run "Alle Sites starten" bash -c "cd '$PROJECT_DIR' && '$SELF' start-all-sites" ;;
|
|
back) return 0 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
gui_image_menu() {
|
|
while :; do
|
|
local sel
|
|
sel="$(ui_menu "Checkmk-Image" \
|
|
"Aktuell (.env): ${CMK_IMAGE:-<nicht gesetzt>}
|
|
Edition/Version: ${CMK_EDITION:-raw} / ${CMK_VERSION:-?}" \
|
|
status "Status und lokale Images anzeigen" \
|
|
versions "Verfuegbare Versionen auflisten" \
|
|
holen "Image beschaffen (Assistent: Edition, Version, Bezugsweg)" \
|
|
test "Zugangsdaten pruefen (Registry und Downloadportal)" \
|
|
use "Vorhandenes lokales Image auswaehlen" \
|
|
back "zurueck")" || return 0
|
|
case "$sel" in
|
|
status) local f; f="$(mktemp)"; NO_COLOR=1 cmd_image status >"$f" 2>&1 || true
|
|
ui_text "Image-Status" "$f"; rm -f "$f" ;;
|
|
versions) gui_image_versions || true ;;
|
|
holen) gui_image_wizard || true ;;
|
|
test) gui_image_test || true ;;
|
|
use) gui_image_use || true ;;
|
|
back) return 0 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
gui_image_pick_edition() {
|
|
# Checkmk hat die Editionen umbenannt: die Website zeigt die neuen
|
|
# Namen, Dateien und Repositories tragen bis 2.4 die alten. Deshalb
|
|
# hier beide, damit die Zuordnung eindeutig ist.
|
|
ui_menu "Edition" \
|
|
"Links der Name, den dieses Werkzeug verwendet,
|
|
rechts der Name auf checkmk.com." \
|
|
raw "= Community auf der Website (frei, bis 2.4)" \
|
|
community "= Community (frei, ab 2.5)" \
|
|
enterprise "= Pro auf der Website (Login noetig, bis 2.4)" \
|
|
pro "= Pro (ab 2.5)" \
|
|
cloud "= Ultimate auf der Website (bis 2.4)" \
|
|
ultimate "= Ultimate (ab 2.5)" \
|
|
managed "= Ultimate MT auf der Website (bis 2.4)" \
|
|
ultimatemt "= Ultimate MT (ab 2.5)"
|
|
}
|
|
|
|
# Version aus dem oeffentlichen Archiv auswaehlen, statt sie abzutippen
|
|
gui_image_pick_version() { # edition [vorgabe] -> Version auf stdout
|
|
local edition="$1" cur="${2:-}"
|
|
local filt
|
|
filt="$(ui_input "Versionen filtern" \
|
|
"Nach welchem Zweig suchen? (leer = alle)
|
|
|
|
Beispiele: 2.3.0p nur Patch-Staende von 2.3.0
|
|
2.4 alles aus 2.4" "${cur%%p*}")" || return 1
|
|
|
|
local -a items=() v
|
|
while IFS= read -r v; do
|
|
[ -n "$v" ] || continue
|
|
items+=("$v" " ")
|
|
done < <(archive_versions 2>/dev/null \
|
|
| { [ -n "$filt" ] && grep -F "$filt" || cat; } | sort -rV | head -n 40)
|
|
|
|
if [ ${#items[@]} -eq 0 ]; then
|
|
ui_msg "Keine Versionen" \
|
|
"Aus dem Versionsarchiv von checkmk.com konnte nichts gelesen werden.
|
|
|
|
Entweder passt der Filter nicht, oder der Host hat keinen
|
|
Zugang zu checkmk.com. Die Version laesst sich im naechsten
|
|
Schritt auch von Hand eingeben."
|
|
ui_input "Version" "Version von Hand eingeben:" "$cur"
|
|
return
|
|
fi
|
|
items+=("__manuell__" "... von Hand eingeben ...")
|
|
local sel
|
|
sel="$(ui_menu "Version waehlen" \
|
|
"Edition: $edition
|
|
Quelle: oeffentliches Versionsarchiv von checkmk.com" "${items[@]}")" || return 1
|
|
if [ "$sel" = "__manuell__" ]; then
|
|
ui_input "Version" "Version von Hand eingeben:" "$cur"
|
|
else
|
|
printf '%s\n' "$sel"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Assistent: Image beschaffen
|
|
#
|
|
# Fuehrt in einem Durchgang durch Edition, Version und Bezugsweg und
|
|
# schreibt anschliessend CMK_EDITION, CMK_VERSION und CMK_IMAGE.
|
|
# Bereits hinterlegte Zugangsdaten werden verwendet - danach wird nur
|
|
# gefragt, wenn sie fuer den gewaehlten Weg fehlen.
|
|
# ---------------------------------------------------------------------
|
|
gui_image_wizard() {
|
|
local ed ver src
|
|
local cur_img="${CMK_IMAGE:-<nicht gesetzt>}"
|
|
|
|
ed="$(ui_menu "Image beschaffen (1/3) - Edition" \
|
|
"Derzeit verwendet: $cur_img
|
|
Eingestellt : ${CMK_EDITION:-?} / ${CMK_VERSION:-?}
|
|
|
|
Links der Name, den dieses Werkzeug verwendet,
|
|
rechts der Name auf checkmk.com." \
|
|
raw "= Community auf der Website (frei, bis 2.4)" \
|
|
community "= Community (frei, ab 2.5)" \
|
|
enterprise "= Pro auf der Website (Login noetig, bis 2.4)" \
|
|
pro "= Pro (ab 2.5)" \
|
|
cloud "= Ultimate auf der Website (bis 2.4)" \
|
|
ultimate "= Ultimate (ab 2.5)" \
|
|
managed "= Ultimate MT auf der Website (bis 2.4)" \
|
|
ultimatemt "= Ultimate MT (ab 2.5)")" || return 0
|
|
|
|
ver="$(gui_image_pick_version "$ed" "${CMK_VERSION:-}")" || return 0
|
|
[ -n "$ver" ] || return 0
|
|
|
|
# --- Bezugsweg -----------------------------------------------------
|
|
local reg_state dl_state
|
|
reg_state="$([ -n "${CMK_REGISTRY_USER:-}" ] && echo "Zugangsdaten hinterlegt" || echo "ohne Anmeldung")"
|
|
dl_state="$([ -n "${CMK_DOWNLOAD_USER:-}" ] && echo "Zugangsdaten hinterlegt" || echo "ohne Anmeldung")"
|
|
src="$(ui_menu "Image beschaffen (3/3) - Bezugsweg" \
|
|
"$ed $ver
|
|
|
|
Woher soll das Image kommen?" \
|
|
registry "Registry holen ($reg_state)" \
|
|
download "Tarball herunterladen ($dl_state)" \
|
|
file "Vorhandene Tarball-Datei einspielen" \
|
|
setonly "Nichts holen, nur Edition/Version eintragen")" || return 0
|
|
|
|
# --- Zugangsdaten nur erfragen, wenn sie wirklich fehlen -----------
|
|
local -a extra=()
|
|
local need_user="" u p
|
|
case "$src" in
|
|
registry) edition_needs_login "$ed" && [ -z "${CMK_REGISTRY_USER:-}" ] && need_user=registry ;;
|
|
download) case "$ed" in raw|community|free) : ;; *) [ -z "${CMK_DOWNLOAD_USER:-}" ] && need_user=download ;; esac ;;
|
|
esac
|
|
if [ -n "$need_user" ]; then
|
|
u="$(ui_input "Zugangsdaten fehlen" \
|
|
"Fuer diesen Weg werden Zugangsdaten des Checkmk-Kundenportals
|
|
gebraucht. Sie sind noch nicht hinterlegt.
|
|
|
|
Benutzer:" "")" || return 0
|
|
[ -n "$u" ] || { ui_msg "Abgebrochen" "Ohne Benutzer geht es nicht weiter."; return 0; }
|
|
p="$(ui_password "Zugangsdaten fehlen" "Kennwort:")" || return 0
|
|
extra+=(--user "$u" --password "$p")
|
|
if ui_yesno "Merken?" "Die Zugangsdaten fuer kuenftige Aufrufe in der .env speichern?"; then
|
|
if [ "$need_user" = registry ]; then
|
|
env_set CMK_REGISTRY_USER "$u"; env_set CMK_REGISTRY_PASSWORD "$p"
|
|
else
|
|
env_set CMK_DOWNLOAD_USER "$u"; env_set CMK_DOWNLOAD_PASSWORD "$p"
|
|
fi
|
|
load_env "${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
fi
|
|
fi
|
|
|
|
# --- ausfuehren ----------------------------------------------------
|
|
case "$src" in
|
|
registry)
|
|
ui_run "Image holen: $ed $ver" bash -c \
|
|
"cd '$PROJECT_DIR' && '$SELF' -y image pull --edition '$ed' --version '$ver' --remember ${extra[*]+${extra[*]}}" ;;
|
|
download)
|
|
case "$ver" in
|
|
*-latest|latest|*-daily)
|
|
ui_msg "Nicht moeglich" \
|
|
"Fuer den Tarball-Download wird eine exakte Version gebraucht,
|
|
z.B. 2.3.0p23 - '$ver' gibt es dort nicht."
|
|
return 0 ;;
|
|
esac
|
|
ui_run "Tarball holen: $ed $ver" bash -c \
|
|
"cd '$PROJECT_DIR' && '$SELF' -y image download --edition '$ed' --version '$ver' --remember ${extra[*]+${extra[*]}}" ;;
|
|
file)
|
|
local lf
|
|
lf="$(ui_pick_file "${IMAGE_DIR:-$PROJECT_DIR/images}" '*.tar.gz')" || return 0
|
|
[ -n "$lf" ] || return 0
|
|
ui_run "Image einspielen" bash -c \
|
|
"cd '$PROJECT_DIR' && '$SELF' -y image load --file '$lf'"
|
|
ui_run "Edition/Version eintragen" bash -c \
|
|
"cd '$PROJECT_DIR' && '$SELF' -y config set CMK_EDITION '$ed' && '$SELF' -y config set CMK_VERSION '$ver'" ;;
|
|
setonly)
|
|
ui_run "Eintragen" bash -c \
|
|
"cd '$PROJECT_DIR' && '$SELF' -y config set CMK_EDITION '$ed' && '$SELF' -y config set CMK_VERSION '$ver'" ;;
|
|
esac
|
|
|
|
load_env "${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
|
|
# --- Ergebnis ------------------------------------------------------
|
|
local cm
|
|
if cm="$(image_consistency_check)"; then
|
|
ui_msg "Fertig" \
|
|
"Verwendet wird jetzt:
|
|
${CMK_IMAGE:-<nicht gesetzt>}
|
|
|
|
Eingestellt: ${CMK_EDITION:-?} / ${CMK_VERSION:-?}
|
|
|
|
Wirksam wird das mit 'Stack steuern -> Stack starten'
|
|
(die Container werden dabei neu erstellt)."
|
|
else
|
|
ui_msg "Hinweis" "$cm"
|
|
fi
|
|
}
|
|
|
|
gui_image_versions() {
|
|
local ed
|
|
ed="$(gui_image_pick_edition)" || return 0
|
|
local f; f="$(mktemp)"
|
|
ui_info "Versionen abrufen" "Frage Versionsarchiv, Registry und Downloadportal ab ...\n\nDas kann einen Moment dauern."
|
|
NO_COLOR=1 cmd_image versions --edition "$ed" --limit 30 >"$f" 2>&1 || true
|
|
ui_text "Verfuegbare Versionen - $ed" "$f"
|
|
rm -f "$f"
|
|
}
|
|
|
|
gui_image_test() {
|
|
local ed ver user pass
|
|
ed="$(gui_image_pick_edition)" || return 0
|
|
ver="$(ui_input "Version" "Version (fuer die Pruefung des Downloadportals):" "${CMK_VERSION:-}")" || return 0
|
|
local -a extra=()
|
|
if ui_yesno "Zugangsdaten" "Zugangsdaten aus der .env verwenden?\n\nRegistry : ${CMK_REGISTRY_USER:-<leer>}\nDownload : ${CMK_DOWNLOAD_USER:-<leer>}\n\n<Nein> = jetzt eingeben"; then
|
|
:
|
|
else
|
|
user="$(ui_input "Zugangsdaten" "Benutzer (Checkmk-Kundenportal):" "${CMK_REGISTRY_USER:-}")" || return 0
|
|
pass="$(ui_password "Zugangsdaten" "Kennwort:")" || return 0
|
|
[ -n "$user" ] && extra+=(--user "$user" --password "$pass")
|
|
fi
|
|
ui_run "Zugangsdaten pruefen" bash -c \
|
|
"cd '$PROJECT_DIR' && NO_COLOR=1 '$SELF' image test --edition '$ed' --version '$ver' ${extra[*]+${extra[*]}}"
|
|
}
|
|
|
|
gui_image_pull() {
|
|
local ed ver user pass
|
|
ed="$(gui_image_pick_edition)" || return 0
|
|
ver="$(gui_image_pick_version "$ed" "${CMK_VERSION:-}")" || return 0
|
|
[ -n "$ver" ] || return 0
|
|
local -a extra=()
|
|
if [ "$ed" = enterprise ] || [ -n "${CMK_REGISTRY_USER:-}" ]; then
|
|
user="$(ui_input "Registry-Anmeldung" "Benutzer (Checkmk-Kundenportal):" "${CMK_REGISTRY_USER:-}")" || return 0
|
|
pass="$(ui_password "Registry-Anmeldung" "Kennwort:")" || return 0
|
|
[ -n "$user" ] && extra+=(--user "$user" --password "$pass")
|
|
fi
|
|
ui_run "Image holen" bash -c "cd '$PROJECT_DIR' && '$SELF' -y image pull --edition '$ed' --version '$ver' ${extra[*]+${extra[*]}}"
|
|
load_env "${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
}
|
|
|
|
gui_image_download() {
|
|
local ed ver user pass
|
|
ed="$(gui_image_pick_edition)" || return 0
|
|
ver="$(gui_image_pick_version "$ed" "${CMK_VERSION:-}")" || return 0
|
|
[ -n "$ver" ] || return 0
|
|
case "$ver" in
|
|
*-latest|latest|*-daily)
|
|
ui_msg "Nicht moeglich" "Fuer den Tarball-Download wird eine exakte Version gebraucht,\nz.B. 2.3.0p23 - '$ver' gibt es dort nicht."
|
|
return 0 ;;
|
|
esac
|
|
user="$(ui_input "Downloadportal" "Benutzer:" "${CMK_DOWNLOAD_USER:-}")" || return 0
|
|
pass="$(ui_password "Downloadportal" "Kennwort:")" || return 0
|
|
ui_run "Image herunterladen" bash -c "cd '$PROJECT_DIR' && '$SELF' -y image download --edition '$ed' --version '$ver' --user '$user' --password '$pass'"
|
|
load_env "${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
}
|
|
|
|
gui_image_use() {
|
|
local -a items=() l
|
|
while IFS= read -r l; do
|
|
[ -n "$l" ] || continue
|
|
items+=("$l" "$([ "$l" = "${CMK_IMAGE:-}" ] && echo '(aktuell)' || echo ' ')")
|
|
done < <(image_local_list)
|
|
[ ${#items[@]} -gt 0 ] || { ui_msg "Images" "Es ist kein Checkmk-Image lokal vorhanden.\n\nZuerst 'pull', 'download' oder 'load' verwenden."; return; }
|
|
local sel
|
|
sel="$(ui_menu "Image auswaehlen" "Lokal vorhandene Checkmk-Images:" "${items[@]}")" || return 0
|
|
ui_run "Image setzen" bash -c "cd '$PROJECT_DIR' && '$SELF' -y image use '$sel'"
|
|
load_env "${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Einstellungen im Menue
|
|
# ---------------------------------------------------------------------
|
|
settings_needs_restart() {
|
|
case "$1" in
|
|
CMK_IMAGE|CMK_SITE_ID|CMK_CONTAINER_NAME|CMK_HOSTNAME|CMK_PASSWORD|\
|
|
CMK_LIVESTATUS_TCP|MAIL_RELAY_HOST|TZ|NPM_IMAGE|NPM_CONTAINER_NAME|\
|
|
NPM_DISABLE_IPV6|CMK_DATA_DIR|NPM_DATA_DIR|NPM_LE_DIR) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
gui_settings_edit() {
|
|
local key="$1" def typ label choices opt cur new msg
|
|
def="$(settings_def_for "$key")"
|
|
[ -n "$def" ] || { ui_msg "Fehler" "Unbekannter Schluessel '$key'."; return; }
|
|
typ="$(printf '%s' "$def" | cut -d'|' -f3)"
|
|
label="$(printf '%s' "$def" | cut -d'|' -f4)"
|
|
choices="$(printf '%s' "$def"| cut -d'|' -f5)"
|
|
opt="$(printf '%s' "$def" | cut -d'|' -f6)"
|
|
cur="$(settings_value "$key")"
|
|
|
|
if [ "$typ" = info ]; then
|
|
if ui_yesno "$key" \
|
|
"Aktuell: ${cur:-<leer>}
|
|
|
|
Dieser Wert wird vom Assistenten gesetzt, damit Edition,
|
|
Version und das tatsaechlich verwendete Image zusammenpassen.
|
|
|
|
Assistent jetzt starten?"; then
|
|
gui_image_wizard || true
|
|
fi
|
|
load_env "${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
return 0
|
|
fi
|
|
|
|
while :; do
|
|
case "$typ" in
|
|
choice)
|
|
local -a ropts=() c
|
|
[ -z "$cur" ] && case ":$choices:" in *:auto:*) cur=auto ;; esac
|
|
local IFS_BAK="$IFS"; IFS=':'
|
|
for c in $choices; do
|
|
IFS="$IFS_BAK"
|
|
ropts+=("$c" " " "$([ "$c" = "$cur" ] && echo on || echo off)")
|
|
IFS=':'
|
|
done
|
|
IFS="$IFS_BAK"
|
|
new="$(ui_radiolist "$key" "$label\n\nAktuell: ${cur:-<leer>}" "${ropts[@]}")" || return 0
|
|
[ "$new" = auto ] && new=""
|
|
;;
|
|
password)
|
|
new="$(ui_password "$key" "$label\n\n(leer lassen = unveraendert)")" || return 0
|
|
[ -z "$new" ] && return
|
|
;;
|
|
*)
|
|
new="$(ui_input "$key" "$label\n\nTyp: $typ$([ -n "$opt" ] && echo ' (darf leer bleiben)')" "$cur")" || return 0
|
|
;;
|
|
esac
|
|
|
|
if msg="$(settings_validate "$typ" "$new" "$opt")" \
|
|
&& msg="$(settings_validate_key "$key" "$new")"; then
|
|
break
|
|
else
|
|
ui_msg "Ungueltige Eingabe" "$msg"
|
|
cur="$new"
|
|
fi
|
|
done
|
|
|
|
if [ "$key" = NPM_ADMIN_PORT ]; then
|
|
ui_run "Admin-Port setzen" bash -c "cd '$PROJECT_DIR' && '$SELF' -y npm-port '$new'"
|
|
elif [ "$key" = NPM_ENABLED ]; then
|
|
ui_run "Proxy Manager ${new}schalten" bash -c "cd '$PROJECT_DIR' && '$SELF' -y npm-enable '$new'"
|
|
else
|
|
env_set "$key" "$new" "$opt"
|
|
local hint=""
|
|
settings_needs_restart "$key" && hint="\n\nWirksam nach 'Stack steuern -> Stack starten'\n(die Container werden dabei neu erstellt)."
|
|
ui_msg "Gespeichert" "$key = $([ "$typ" = password ] && echo '********' || echo "${new:-<leer>}")$hint"
|
|
# Zugangsdaten lassen sich sofort pruefen - das erspart den Umweg
|
|
case "$key" in
|
|
CMK_REGISTRY_USER|CMK_REGISTRY_PASSWORD|CMK_DOWNLOAD_USER|CMK_DOWNLOAD_PASSWORD|CMK_REGISTRY|CMK_DOWNLOAD_BASEURL)
|
|
load_env "${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
if ui_yesno "Zugangsdaten pruefen" \
|
|
"Sollen die Zugangsdaten jetzt geprueft werden?
|
|
|
|
Getestet wird die Anmeldung an Registry und Downloadportal -
|
|
es wird nichts heruntergeladen."; then
|
|
ui_run "Zugangsdaten pruefen" bash -c \
|
|
"cd '$PROJECT_DIR' && NO_COLOR=1 '$SELF' image test"
|
|
fi ;;
|
|
esac
|
|
fi
|
|
load_env "${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
}
|
|
|
|
gui_settings_category() {
|
|
local cat="$1" title="$2"
|
|
while :; do
|
|
local -a items=() c k t l ch o
|
|
while IFS='|' read -r c k t l ch o; do
|
|
[ "$c" = "$cat" ] || continue
|
|
items+=("$k" "$l = $(settings_display "$k" "$t")")
|
|
done < <(settings_defs)
|
|
# Zusatzaktionen, die inhaltlich zur Kategorie gehoeren
|
|
case "$cat" in
|
|
image) items+=("__wizard__" ">> Image beschaffen (Edition, Version, Bezugsweg)")
|
|
items+=("__test__" ">> Zugangsdaten jetzt pruefen (Registry + Downloadportal)") ;;
|
|
npm) items+=("__npm__" ">> Proxy Manager: Stand und Admin-Port anzeigen") ;;
|
|
backup) items+=("__jobs__" ">> Backup-Jobs verwalten") ;;
|
|
esac
|
|
items+=("back" "zurueck")
|
|
local sel
|
|
sel="$(ui_menu "$title" "Eintrag waehlen und mit ENTER aendern:" "${items[@]}")" || return 0
|
|
case "$sel" in
|
|
back) return 0 ;;
|
|
__wizard__) gui_image_wizard || true; continue ;;
|
|
__test__) gui_image_test || true; continue ;;
|
|
__jobs__) gui_jobs_menu || true; continue ;;
|
|
__npm__) local f; f="$(mktemp)"
|
|
{ NO_COLOR=1 cmd_npm_enable; NO_COLOR=1 cmd_npm_port; } >"$f" 2>&1 || true
|
|
ui_text "NGINX Proxy Manager" "$f"; rm -f "$f"; continue ;;
|
|
esac
|
|
gui_settings_edit "$sel"
|
|
done
|
|
}
|
|
|
|
gui_settings_menu() {
|
|
while :; do
|
|
local -a items=() c l
|
|
while IFS='|' read -r c l; do
|
|
[ -n "$c" ] && items+=("$c" "$l")
|
|
done < <(settings_categories)
|
|
items+=("jobs" "Backup-Jobs (Zeitplan, Zielpfade, Aufbewahrung)")
|
|
items+=("show" "Alle Werte auflisten")
|
|
items+=("editor" "Datei .env im Editor oeffnen")
|
|
items+=("back" "zurueck")
|
|
local sel
|
|
sel="$(ui_menu "Einstellungen" \
|
|
"Alle Werte der Datei .env - direkt hier aenderbar.
|
|
Datei: ${ENV_FILE:-$PROJECT_DIR/.env}" "${items[@]}")" || return 0
|
|
case "$sel" in
|
|
back) return 0 ;;
|
|
jobs) gui_jobs_menu || true ;;
|
|
show) local f; f="$(mktemp)"
|
|
{ printf '%-24s %-10s %s\n' "SCHLUESSEL" "BEREICH" "WERT"
|
|
NO_COLOR=1 cmd_config list; } >"$f" 2>&1
|
|
ui_text "Alle Einstellungen" "$f"; rm -f "$f" ;;
|
|
editor) local ef="${ENV_FILE:-$PROJECT_DIR/.env}"
|
|
if [ ! -f "$ef" ]; then ui_msg "Keine .env" "Die Datei $ef existiert nicht.\n\nAnlegen mit: cp .env.example .env"; continue; fi
|
|
clear 2>/dev/null || true; "${EDITOR:-nano}" "$ef"
|
|
load_env "$ef"; ui_msg "Konfiguration" "Die .env wurde neu eingelesen." ;;
|
|
*) local title
|
|
title="$(settings_categories | awk -F'|' -v c="$sel" '$1==c{print $2}')"
|
|
gui_settings_category "$sel" "${title:-$sel}" ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
gui_browser_menu() {
|
|
while :; do
|
|
local sel
|
|
sel="$(ui_menu "Terminal-Browser" \
|
|
"Bedienung des NGINX Proxy Managers direkt auf dieser Maschine.
|
|
Startseite: ${NPM_ADMIN_URL:-http://127.0.0.1:81}
|
|
|
|
Nur browsh und carbonyl koennen JavaScript und damit die
|
|
Oberflaeche des Proxy Managers darstellen." \
|
|
browsh "Firefox im Terminal (empfohlen)" \
|
|
carbonyl "Chromium im Terminal" \
|
|
links "links2 - Textbrowser, KEIN JavaScript" \
|
|
w3m "w3m - Textbrowser, KEIN JavaScript" \
|
|
lynx "lynx - Textbrowser, KEIN JavaScript" \
|
|
shell "Shell mit curl/jq/openssl im Host-Netzwerk" \
|
|
url "Andere Startseite eingeben" \
|
|
build "Werkzeug-Image neu bauen" \
|
|
back "zurueck")" || return 0
|
|
case "$sel" in
|
|
url) local u
|
|
u="$(ui_input "Startseite" "URL:" "${NPM_ADMIN_URL:-http://127.0.0.1:81}")" || continue
|
|
[ -n "$u" ] && NPM_ADMIN_URL="$u" ;;
|
|
build) ui_run "Werkzeug-Image bauen" bash -c "cd '$PROJECT_DIR' && '$SELF' browser --build-only" || true ;;
|
|
back) return 0 ;;
|
|
*) local burl="${NPM_ADMIN_URL:-http://127.0.0.1:81}"
|
|
if ! browser_url_reachable "$burl"; then
|
|
if ui_yesno "Startseite antwortet nicht" \
|
|
"$(browser_diagnose_url "$burl")
|
|
|
|
Trotzdem starten?
|
|
|
|
<Nein> kehrt ins Menue zurueck - dort laesst sich unter
|
|
'Andere Startseite eingeben' auch eine andere Adresse waehlen."; then
|
|
gui_run_interactive cmd_browser --engine "$sel" --url "$burl" --no-check
|
|
fi
|
|
continue
|
|
fi
|
|
gui_run_interactive cmd_browser --engine "$sel" --url "$burl" ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
gui_cert() {
|
|
local domain alt days
|
|
domain="$(ui_input "Zertifikat" "FQDN / Common Name:" "${CERT_DOMAIN:-checkmk.example.local}")" || return 0
|
|
[ -n "$domain" ] || return 0
|
|
alt="$(ui_input "Zertifikat" "Weitere Namen/IPs (durch Leerzeichen getrennt, optional):" "$(primary_ip)")" || alt=""
|
|
days="$(ui_input "Zertifikat" "Gueltigkeit in Tagen:" "${CERT_DAYS:-3650}")" || days=3650
|
|
local -a a=(); local x
|
|
for x in $alt; do a+=(--alt "$x"); done
|
|
ui_run "Zertifikat erzeugen" bash -c "cd '$PROJECT_DIR' && '$SELF' cert --domain '$domain' --days '$days' ${a[*]+${a[*]}}"
|
|
}
|
|
|
|
gui_main() {
|
|
ui_require
|
|
while :; do
|
|
local sel
|
|
sel="$(ui_menu "Checkmk Stack Manager" \
|
|
"Projekt : $PROJECT_DIR
|
|
Runtime : $RUNTIME Standard-Site: $CMK_SITE_ID
|
|
Backups : $BACKUP_DIR" \
|
|
status "Status von Containern und Sites" \
|
|
stack "Stack steuern (Start/Stop/Logs/Update)" \
|
|
backup "Backup erstellen / planen" \
|
|
restore "Backup wiederherstellen" \
|
|
import "Site von Direktinstallation importieren" \
|
|
manage "Backups verwalten (Liste/Details/Loeschen)" \
|
|
sites "Sites im Container verwalten" \
|
|
image "Checkmk-Image beschaffen/auswaehlen" \
|
|
browser "Terminal-Browser (Proxy Manager lokal bedienen)" \
|
|
cert "Selbstsigniertes Zertifikat erzeugen" \
|
|
config "Einstellungen (.env) - menuegefuehrt" \
|
|
shell "Root-Shell im Checkmk-Container" \
|
|
quit "Beenden")" || break
|
|
case "$sel" in
|
|
status) gui_status || true ;;
|
|
stack) gui_stack_menu || true ;;
|
|
backup) gui_backup_menu || true ;;
|
|
restore) gui_restore || true ;;
|
|
import) gui_import || true ;;
|
|
manage) gui_backups_menu || true ;;
|
|
sites) gui_sites_menu || true ;;
|
|
image) gui_image_menu || true ;;
|
|
browser) gui_browser_menu || true ;;
|
|
cert) gui_cert || true ;;
|
|
config) gui_settings_menu || true ;;
|
|
shell) gui_need_container || continue
|
|
gui_run_interactive cmd_shell ;;
|
|
quit) break ;;
|
|
esac
|
|
done
|
|
clear 2>/dev/null || true
|
|
ok "Beendet."
|
|
}
|
|
|
|
# =====================================================================
|
|
# Einstiegspunkt
|
|
# =====================================================================
|
|
main() {
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--env-file) ENVFILE_OPT="$2"; shift 2 ;;
|
|
-y|--yes) ASSUME_YES=1; shift ;;
|
|
-v|--verbose) VERBOSE=1; shift ;;
|
|
-h|--help) usage; exit 0 ;;
|
|
*) break ;;
|
|
esac
|
|
done
|
|
|
|
load_env "${ENVFILE_OPT:-$PROJECT_DIR/.env}"
|
|
detect_runtime
|
|
UI_BACKTITLE="Checkmk Stack Manager - Runtime: $RUNTIME"
|
|
|
|
local cmd="${1:-gui}"; [ $# -gt 0 ] && shift || true
|
|
case "$cmd" in
|
|
gui|menu) gui_main ;;
|
|
up|start) cmd_up ;;
|
|
down|stop) cmd_down ;;
|
|
restart) cmd_restart || true ;;
|
|
pull) cmd_pull || true ;;
|
|
status) cmd_status || true ;;
|
|
logs) cmd_logs "$@" || true ;;
|
|
shell) cmd_shell || true ;;
|
|
omd) cmd_omd "$@" || true ;;
|
|
sites) cmd_sites || true ;;
|
|
site-start) cmd_site_start "$@" ;;
|
|
site-stop) cmd_site_stop "$@" ;;
|
|
start-all-sites) cmd_start_all_sites ;;
|
|
backup) cmd_backup "$@" || true ;;
|
|
list) cmd_list || true ;;
|
|
prune) cmd_prune "$@" || true ;;
|
|
job|jobs) cmd_job "$@" ;;
|
|
verify) local vf=""; [ "${1:-}" = "--file" ] && vf="$2"; [ -n "$vf" ] || vf="${1:-}"
|
|
[ -n "$vf" ] || die "--file fehlt."; bundle_verify "$vf" ;;
|
|
restore) cmd_restore "$@" || true ;;
|
|
import) cmd_import "$@" || true ;;
|
|
cert) cmd_cert "$@" || true ;;
|
|
image) cmd_image "$@" || true ;;
|
|
browser) cmd_browser "$@" || true ;;
|
|
npm-port) cmd_npm_port "$@" ;;
|
|
npm-enable) cmd_npm_enable "$@" ;;
|
|
config) cmd_config "$@" || true ;;
|
|
help|-h|--help) usage ;;
|
|
*) err "Unbekanntes Kommando: $cmd"; echo; usage; exit 1 ;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|