image test: Zugangsdaten pruefen, ohne etwas herunterzuladen
Neues Unterkommando "image test" und ein Menuepunkt im Image-Menue.
Geprueft wird in Sekunden, statt erst beim mehrere Gigabyte grossen
Download zu scheitern:
Registry - Erreichbarkeit, Anmeldung und ob das Konto ueberhaupt
Zugriff auf das Repository hat. Umgesetzt ueber den
Docker-Registry-v2-Ablauf: WWW-Authenticate auswerten,
Token beim genannten realm holen, damit /tags/list
abfragen. Dadurch wird "Anmeldung stimmt, aber keine
Berechtigung" von "Anmeldung falsch" unterschieden.
Downloads - HTTP-Basic gegen die Tarball-URLs. HTTP 404 ohne 401
bedeutet: Zugangsdaten sind richtig, nur Version oder
Dateiname passen nicht - das wird auch so gemeldet.
Es wird nichts gespeichert: die Zugangsdaten gehen per temporaerer
.netrc an curl und stehen damit nicht in der Prozessliste (geprueft).
Fehlt curl, weicht die Registry-Pruefung auf podman/docker login in
eine Wegwerfdatei aus (--authfile bzw. DOCKER_CONFIG), die gespeicherte
Anmeldung des Systems bleibt unberuehrt.
Beim Testen gegen die echten Dienste gefunden und behoben:
- "docker.io" ist nicht der API-Host der Registry; angesprochen wird
registry-1.docker.io. Vorher lieferte /v2/ nur eine 302.
- Die Schlussauswertung zaehlte auch Pruefungen als bestanden, die
wegen --only gar nicht gelaufen waren.
Verifiziert gegen die echten Endpunkte: Docker Hub anonym inkl.
Tag-Liste von checkmk/check-mk-raw (HTTP 200), registry.checkmk.com
und download.checkmk.com jeweils mit falschen Zugangsdaten (HTTP 401,
korrekt als abgelehnt gemeldet).
Dokumentiert als Abschnitt 8.4 in beiden Sprachfassungen.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+321
-1
@@ -92,6 +92,11 @@ KOMMANDOS - Backup / Restore / Migration
|
||||
KOMMANDOS - Checkmk-Image beschaffen
|
||||
image status konfiguriertes und lokal vorhandene Images anzeigen
|
||||
image list lokal vorhandene Checkmk-Images
|
||||
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)
|
||||
@@ -137,6 +142,7 @@ 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
|
||||
@@ -649,6 +655,301 @@ image_cmd_status() {
|
||||
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")
|
||||
curl "${a[@]}" "$url" 2>/dev/null || echo "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 || echo 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 pkg; pkg="$(edition_pkgname "$edition")"
|
||||
local -a urls=()
|
||||
if [ -n "${CMK_DOWNLOAD_URL:-}" ]; then
|
||||
urls=("$CMK_DOWNLOAD_URL")
|
||||
elif [ -n "$version" ]; then
|
||||
urls=("$base/$version/$pkg-$version.docker.tar.gz"
|
||||
"$base/$version/$pkg-docker-$version.tar.gz")
|
||||
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
|
||||
}
|
||||
|
||||
cmd_image() {
|
||||
local sub="${1:-status}"; [ $# -gt 0 ] && shift || true
|
||||
case "$sub" in
|
||||
@@ -658,8 +959,9 @@ cmd_image() {
|
||||
download) image_cmd_download "$@" ;;
|
||||
load) image_cmd_load "$@" ;;
|
||||
use|set) image_cmd_use "$@" ;;
|
||||
test|check) image_cmd_test "$@" ;;
|
||||
*) die "Unbekanntes Unterkommando 'image $sub'.
|
||||
Moeglich: status | list | pull | download | load | use" ;;
|
||||
Moeglich: status | list | test | pull | download | load | use" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -1881,6 +2183,7 @@ gui_image_menu() {
|
||||
"Aktuell (.env): ${CMK_IMAGE:-<nicht gesetzt>}
|
||||
Edition/Version: ${CMK_EDITION:-raw} / ${CMK_VERSION:-?}" \
|
||||
status "Status und lokale Images anzeigen" \
|
||||
test "Zugangsdaten pruefen (Registry und Downloadportal)" \
|
||||
pull "Aus Registry holen (Docker Hub / registry.checkmk.com)" \
|
||||
download "Tarball vom Downloadportal holen (mit Zugangsdaten)" \
|
||||
load "Heruntergeladenen Tarball einspielen (load)" \
|
||||
@@ -1889,6 +2192,7 @@ Edition/Version: ${CMK_EDITION:-raw} / ${CMK_VERSION:-?}" \
|
||||
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" ;;
|
||||
test) gui_image_test ;;
|
||||
pull) gui_image_pull ;;
|
||||
download) gui_image_download ;;
|
||||
load) local lf; lf="$(ui_pick_file "${IMAGE_DIR:-$PROJECT_DIR/images}" '*.tar.gz')" || continue
|
||||
@@ -1913,6 +2217,22 @@ gui_image_pick_edition() {
|
||||
ultimatemt "Ultimate Multi-Tenancy (ab 2.5)"
|
||||
}
|
||||
|
||||
gui_image_test() {
|
||||
local ed ver user pass
|
||||
ed="$(gui_image_pick_edition)" || return
|
||||
ver="$(ui_input "Version" "Version (fuer die Pruefung des Downloadportals):" "${CMK_VERSION:-}")" || return
|
||||
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
|
||||
pass="$(ui_password "Zugangsdaten" "Kennwort:")" || return
|
||||
[ -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
|
||||
|
||||
Reference in New Issue
Block a user