diff --git a/README.md b/README.md index 2974672..9dc0211 100644 --- a/README.md +++ b/README.md @@ -1928,7 +1928,7 @@ Eine Dateiauswahl mit Verzeichnisnavigation ist eingebaut. | `job list\|add\|set\|del\|run\|auto` | geplante Backups (eigene Zielpfade je Job) | | `restore …` | Backup zurückspielen | | `import …` | Migrations-Bundle einspielen | -| `image status\|list\|versions\|test\|pull\|download\|load\|use` | Checkmk-Image beschaffen und auswaehlen | +| `image status\|list\|versions\|repos\|why\|test\|pull\|download\|load\|use` | Checkmk-Image beschaffen, auswählen und Fehler erklären | | `browser [--engine …]` | Terminal-Browser im Container – Proxy Manager lokal bedienen | | `config list\|get\|set\|edit` | Einstellungen der `.env` anzeigen und ändern | | `npm-port [PORT]` | Port der Proxy-Manager-Oberfläche setzen/anzeigen | @@ -2419,7 +2419,8 @@ Checkmk-Werksnotizen (Werks/Release Notes) lesen. | Nach Reboot laufen die Container nicht (Podman) | systemd-Unit fehlt: `./install.sh --with-systemd` bzw. `--quadlet`, danach `systemctl is-enabled checkmk-stack` | | `omd restore`: *version is not installed* | passendes Image holen: `cmk-manage.sh image pull --edition --version `, dann `up` | | Import bricht mit Editions-Warnung ab | Enterprise-Backup benötigt ein Enterprise-Image, siehe [Kapitel 8](#8-checkmk-image-beschaffen-raw-enterprise--co) | -| `Login Succeeded`, aber Pull scheitert mit `unable to copy from source` | Der Repository-Pfad oder das Tag stimmt nicht. `cmk-manage.sh image repos --edition enterprise` zeigt, was die Registry anbietet; sonst über den Tarball gehen | +| `manifest unknown` beim Pull | Das Repository ist da, **das Tag nicht**. `cmk-manage.sh image why` nennt die vorhandenen Tags des Zweigs | +| `repository name unknown` / `Login Succeeded`, aber Pull scheitert | Der Repository-Pfad stimmt nicht. `cmk-manage.sh image repos --edition enterprise` zeigt, was die Registry anbietet; sonst über den Tarball gehen | | `image not known` / Podman sucht bei `registry.access.redhat.com` | kurzer Image-Name. `CMK_IMAGE` vollqualifiziert setzen: `cmk-manage.sh image use docker.io/…` bzw. `registry.checkmk.com/enterprise/…` | | `checkmk/check-mk-enterprise` nicht auf Docker Hub gefunden | richtig so – Enterprise liegt nur auf `registry.checkmk.com` oder als Tarball vor; ab 2.5 heißt die Edition `pro` und liegt frei auf Docker Hub | | Tarball mit `podman import` eingespielt, Image „unbekannt“ | `import` erzeugt ein Image ohne Metadaten. Fehl-Image mit `podman rmi ` entfernen und `cmk-manage.sh image load --file …` verwenden (nutzt `load`) | diff --git a/README_en.md b/README_en.md index d417d6d..1b1037a 100644 --- a/README_en.md +++ b/README_en.md @@ -1927,7 +1927,7 @@ A file chooser with directory navigation is built in. | `job list\|add\|set\|del\|run\|auto` | scheduled backups (own target path per job) | | `restore …` | restore a backup | | `import …` | import a migration bundle | -| `image status\|list\|versions\|test\|pull\|download\|load\|use` | obtain and select the Checkmk image | +| `image status\|list\|versions\|repos\|why\|test\|pull\|download\|load\|use` | obtain and select the Checkmk image, and explain failures | | `browser [--engine …]` | terminal browser in a container – operate the Proxy Manager locally | | `config list\|get\|set\|edit` | show and change settings in `.env` | | `npm-port [PORT]` | set/show the port of the Proxy Manager interface | @@ -2421,7 +2421,8 @@ release notes beforehand. | Containers do not run after a reboot (Podman) | systemd unit missing: `./install.sh --with-systemd` or `--quadlet`, then `systemctl is-enabled checkmk-stack` | | `omd restore`: *version is not installed* | fetch the matching image: `cmk-manage.sh image pull --edition --version `, then `up` | | Import aborts with an edition warning | an Enterprise backup needs an Enterprise image, see [chapter 8](#8-obtaining-the-checkmk-image-raw-enterprise--co) | -| `Login Succeeded` but the pull fails with `unable to copy from source` | The repository path or the tag is wrong. `cmk-manage.sh image repos --edition enterprise` shows what the registry offers; otherwise use the tarball | +| `manifest unknown` on pull | The repository is there, **the tag is not**. `cmk-manage.sh image why` lists the tags that do exist in that branch | +| `repository name unknown` / `Login Succeeded` but the pull fails | The repository path is wrong. `cmk-manage.sh image repos --edition enterprise` shows what the registry offers; otherwise use the tarball | | `image not known` / Podman searches `registry.access.redhat.com` | short image name. Set `CMK_IMAGE` fully qualified: `cmk-manage.sh image use docker.io/…` or `registry.checkmk.com/enterprise/…` | | `checkmk/check-mk-enterprise` not found on Docker Hub | correct – Enterprise is only on `registry.checkmk.com` or available as a tarball; from 2.5 the edition is called `pro` and is public on Docker Hub | | Tarball installed with `podman import`, image "unknown" | `import` produces an image without metadata. Remove the broken image with `podman rmi ` and use `cmk-manage.sh image load --file …` (which uses `load`) | diff --git a/scripts/cmk-manage.sh b/scripts/cmk-manage.sh index c0feccb..e92ee39 100755 --- a/scripts/cmk-manage.sh +++ b/scripts/cmk-manage.sh @@ -119,6 +119,8 @@ KOMMANDOS - Checkmk-Image beschaffen --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 why [REF] erklaeren, warum sich eine Referenz nicht holen + laesst (ohne REF: die aus CMK_IMAGE) image repos [Opt.] herausfinden, welche Repositories die Registry wirklich anbietet (Katalog + uebliche Pfade) image test [Opt.] Zugangsdaten pruefen, ohne etwas herunterzuladen @@ -244,7 +246,13 @@ bundle_read() { 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 + # ACHTUNG: "grep -q" beendet sich beim ersten Treffer. Der Erzeuger + # bekommt dann SIGPIPE, und unter "set -o pipefail" gilt die ganze + # Pipe als fehlgeschlagen - aber nur, wenn er noch am Schreiben war, + # also abhaengig von der Datenmenge. Bei einem grossen Archiv wuerde + # ein vorhandenes Bundle dadurch als "kein Bundle" gelten. Deshalb + # solche Pipes immer in einer Subshell ohne pipefail ausfuehren. + if ( set +o pipefail; 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" @@ -324,7 +332,7 @@ bundle_verify() { # 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 + if ( set +o pipefail; 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" @@ -413,8 +421,8 @@ image_load_file() { [ -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 + if ! ( set +o pipefail; tar -tzf "$file" 2>/dev/null | grep -qE '^(manifest\.json|oci-layout|index\.json)$' ); then + if ( set +o pipefail; tar -tf "$file" 2>/dev/null | grep -qE '^(manifest\.json|oci-layout|index\.json)$' ); then : else err "'$file' enthaelt kein manifest.json / oci-layout." @@ -547,7 +555,7 @@ archive_docker_file() { # edition version -> Dateiname auf stdout [ -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 + if ( set +o pipefail; 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 @@ -648,11 +656,8 @@ EOF 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 + if [ "$reg" != "docker.io" ] || [ -n "$user" ]; then + image_diagnose_ref "$ref" || true return 1 fi if [ "$edition" = enterprise ]; then @@ -1215,6 +1220,88 @@ image_cmd_test() { return 1 } +# Warum laesst sich eine Image-Referenz nicht holen? +# +# Die Fehlermeldungen der Runtime sind eindeutig, aber unerklaert: +# "manifest unknown" -> Repository ist da, das TAG nicht +# "repository name unknown" -> Repository gibt es nicht +# "unauthorized" -> Anmeldung oder Berechtigung +# Diese Funktion fragt die Registry direkt und sagt, welcher Fall vorliegt. +image_diagnose_ref() { # ref + local ref="$1" + command -v curl >/dev/null 2>&1 || return 0 + + local tag regpart reg repo api + tag="${ref##*:}"; regpart="${ref%:*}" + case "$regpart" in + */*) reg="${regpart%%/*}"; repo="${regpart#*/}" ;; + *) reg="docker.io"; repo="$regpart" ;; + esac + [ "$tag" = "$ref" ] && tag="" + api="$(registry_api_host "$reg")" + + local u="" p="" + case "$reg" in + docker.io|index.docker.io|registry-1.docker.io) : ;; + *) u="${CMK_REGISTRY_USER:-}"; p="${CMK_REGISTRY_PASSWORD:-}" ;; + esac + + printf '\n%sWoran liegt es?%s\n' "$C_BLD" "$C_RST" + printf ' Registry : %s\n' "$reg" + printf ' Repository : %s\n' "$repo" + printf ' Tag : %s\n\n' "${tag:-}" + + local line code + line="$(registry_repo_status "$api" "$repo" "$u" "$p")" + code="${line%% *}" + case "$code" in + 401|403) + test_result fail "Anmeldung oder Berechtigung (HTTP $code)" + printf ' Pruefen: %s image test --only registry\n' "$SELF" + return 1 ;; + 404) + test_result fail "Dieses Repository gibt es dort nicht (HTTP 404)" + printf ' Welche es gibt: %s image repos --edition %s\n' "$SELF" "${CMK_EDITION:-enterprise}" + return 1 ;; + 200) : ;; + *) test_result warn "Registry antwortet unerwartet (HTTP $code)"; return 1 ;; + esac + + test_result ok "Repository vorhanden und zugaenglich" + + local -a tags=() + mapfile -t tags < <(registry_tags "$api" "$repo" "$u" "$p" | sed '/^$/d') + if [ ${#tags[@]} -eq 0 ]; then + test_result warn "Es liess sich keine Tag-Liste lesen." + return 1 + fi + if ( set +o pipefail; printf '%s\n' "${tags[@]}" | grep -qxF "$tag" ); then + test_result ok "Das Tag '$tag' ist vorhanden - der Fehler hat eine andere Ursache." + return 0 + fi + + test_result fail "Das Tag '$tag' gibt es in diesem Repository NICHT." + printf ' Genau das meint die Meldung "manifest unknown".\n\n' + + # Naheliegende Tags zeigen: gleicher Zweig, sonst die neuesten + local branch="${tag%%p*}" + local -a near=() + mapfile -t near < <(printf '%s\n' "${tags[@]}" | grep -F "$branch" | sort -rV | head -n 12) + if [ ${#near[@]} -gt 0 ]; then + printf ' %sVorhanden im Zweig %s%s\n' "$C_BLD" "$branch" "$C_RST" + else + mapfile -t near < <(printf '%s\n' "${tags[@]}" | sort -rV | head -n 12) + printf ' %sVorhanden (neueste)%s\n' "$C_BLD" "$C_RST" + fi + printf ' %s\n' "${near[@]}" + printf '\n Uebernehmen mit:\n' + printf ' %s image use %s/%s: --remember\n' "$SELF" "$reg" "$repo" + printf ' %s up\n' "$SELF" + printf '\n Alle Tags: %s image versions --source registry --edition %s --all\n\n' \ + "$SELF" "${CMK_EDITION:-enterprise}" + return 1 +} + # --------------------------------------------------------------------- # image versions - verfuegbare Versionen auflisten # @@ -1517,8 +1604,9 @@ cmd_image() { test|check) image_cmd_test "$@" ;; versions|tags) image_cmd_versions "$@" ;; repos|repo) image_cmd_repos "$@" ;; + why|diagnose) image_diagnose_ref "${1:-${CMK_IMAGE:-}}" ;; *) die "Unbekanntes Unterkommando 'image $sub'. - Moeglich: status | list | versions | repos | test | pull | download | load | use" ;; + Moeglich: status | list | versions | repos | why | test | pull | download | load | use" ;; esac } @@ -1544,7 +1632,15 @@ cmd_up() { else log "Starte Stack (${RUNTIME}) - ohne NGINX Proxy Manager (NPM_ENABLED=off) ..." fi - compose up -d "${svc[@]}" + if ! compose up -d "${svc[@]}"; then + echo + err "Der Stack konnte nicht gestartet werden." + # Haeufigste Ursache: das Image laesst sich nicht holen. + if [ -n "${CMK_IMAGE:-}" ] && ! image_exists_local "$CMK_IMAGE"; then + image_diagnose_ref "$CMK_IMAGE" || true + fi + return 1 + fi npm_enabled || npm_teardown_container ok "Stack gestartet." cmd_status @@ -2177,7 +2273,7 @@ do_restore() { if [ -n "$B_CMK_VERSION" ]; then local installed installed="$(cmk_versions_installed)" - if ! printf '%s\n' "$installed" | grep -qxF "$B_CMK_VERSION"; then + if ! ( set +o pipefail; 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' ' ')" @@ -2551,7 +2647,7 @@ EOF port_in_use() { command -v ss >/dev/null 2>&1 || return 1 - ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$1\$" + ( set +o pipefail; ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[:.]$1\$" ) } valid_port() { @@ -3300,7 +3396,7 @@ cmd_proxy() { local all; all="$(npm_api GET /api/nginx/proxy-hosts)" local i for i in $(json_ids "$all"); do - if npm_api GET "/api/nginx/proxy-hosts/$i" | grep -qF "\"$dom\""; then pid="$i"; break; fi + if ( set +o pipefail; npm_api GET "/api/nginx/proxy-hosts/$i" | grep -qF "\"$dom\"" ); then pid="$i"; break; fi done [ -n "$pid" ] || die "Kein Proxy-Host fuer '$dom' gefunden." fi @@ -3804,7 +3900,7 @@ gui_job_edit() { # name (leer = neu) while IFS= read -r sname; do [ -n "$sname" ] || continue items+=("$sname" "einzelne Site" \ - "$(printf '%s' ",$sites," | grep -q ",$sname," && echo on || echo off)") + "$( ( set +o pipefail; 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 @@ -4822,7 +4918,30 @@ cmd_selftest() { printf ' "set -e" das Skript. Besser als if-Block schreiben.\n' fi - printf '\n%s5. Zeichensatz fuer die Oberflaeche%s\n' "$C_BLD" "$C_RST" + printf '\n%s5. Keine "| grep -q" ohne Schutz vor SIGPIPE%s\n' "$C_BLD" "$C_RST" + n=0 + for f in "${files[@]}"; do + [ -f "$f" ] || continue + while IFS= read -r line; do + [ -n "$line" ] || continue + printf ' %s[FEHLER]%s %s:%s\n' "$C_RED" "$C_RST" "$(basename "$f")" "$line" + n=$((n+1)) + done < <(grep -nE '\|[[:space:]]*grep -[a-zA-Z]*q' "$f" \ + | grep -v 'set +o pipefail' \ + | grep -vE '^[0-9]+:[[:space:]]*#' \ + | grep -v 'printf' | cut -c1-72 || true) + done + if [ "$n" -eq 0 ]; then + printf ' %s[ OK ]%s alle solchen Pipes laufen ohne pipefail\n' "$C_GRN" "$C_RST" + else + printf ' "grep -q" endet beim ersten Treffer; der Erzeuger bekommt\n' + printf ' SIGPIPE und unter "set -o pipefail" gilt die Pipe als\n' + printf ' fehlgeschlagen - abhaengig von der Datenmenge. Schreibweise:\n' + printf ' if ( set +o pipefail; erzeuger | grep -q muster ); then\n' + rc=1 + fi + + printf '\n%s6. Zeichensatz fuer die Oberflaeche%s\n' "$C_BLD" "$C_RST" local before="${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" ui_fix_locale || true printf ' Locale vorher : %s\n' "$before" @@ -4835,7 +4954,7 @@ cmd_selftest() { "$([ -n "${UI_LOCALE_NOTE:-}" ] && echo " (${UI_LOCALE_NOTE})")" fi - printf '\n%s6. Einstellungstabelle%s\n' "$C_BLD" "$C_RST" + printf '\n%s7. Einstellungstabelle%s\n' "$C_BLD" "$C_RST" n="$(settings_defs | awk -F'|' 'NF!=6' | wc -l)" if [ "$n" -eq 0 ]; then printf ' %s[ OK ]%s alle Zeilen haben sechs Felder\n' "$C_GRN" "$C_RST" diff --git a/scripts/lib/common.sh b/scripts/lib/common.sh index c709848..65c85d0 100644 --- a/scripts/lib/common.sh +++ b/scripts/lib/common.sh @@ -168,7 +168,7 @@ cmk_sites() { } cmk_site_exists() { - cmk_sites | grep -qxF "$1" + ( set +o pipefail; cmk_sites | grep -qxF "$1" ) } cmk_site_version() { @@ -338,7 +338,7 @@ ui_fix_locale() { esac local l for l in C.UTF-8 C.utf8 en_US.UTF-8 en_US.utf8 de_DE.UTF-8 de_DE.utf8; do - if locale -a 2>/dev/null | grep -qix "$l"; then + if ( set +o pipefail; locale -a 2>/dev/null | grep -qix "$l" ); then # LC_ALL sticht jede einzelne Kategorie aus - solange es auf # einem Nicht-UTF-8-Wert steht, bliebe LC_CTYPE wirkungslos. if [ -n "${LC_ALL:-}" ]; then unset LC_ALL; fi diff --git a/scripts/migrate_creator.sh b/scripts/migrate_creator.sh index 2065fb8..1abee5b 100755 --- a/scripts/migrate_creator.sh +++ b/scripts/migrate_creator.sh @@ -457,7 +457,7 @@ fi [ ${#SITES[@]} -gt 0 ] || die "Keine Site angegeben. --site SITE, --all oder --gui verwenden." for s in "${SITES[@]}"; do - list_sites | grep -qxF "$s" || die "Site '$s' existiert auf diesem Server nicht." + ( set +o pipefail; list_sites | grep -qxF "$s" ) || die "Site '$s' existiert auf diesem Server nicht." done printf '\n%sZu sichernde Sites:%s %s\n' "$C_BLD" "$C_RST" "${SITES[*]}"