#!/usr/bin/env bash
set -euo pipefail

CLEAR_AFTER=0
DO_SYNC=0
for arg in "$@"; do
    case "$arg" in
        --clear-after) CLEAR_AFTER=1 ;;
        --sync)        DO_SYNC=1 ;;
        --help|-h)     cat <<'HELP'
Обновление Klavdii (klvd-node + klvd-solr + klvd-zookeeper)

Использование:
  bash update.sh [ОПЦИИ]

Опции:
  --sync          Скачать свежий update.sh с сервера и запустить его
                  (самообновление скрипта; без этой опции используется локальная копия)
  --clear-after   Остановить и удалить старые образы после успешного обновления
                  (docker rmi образов, заменённых новыми digest)
  --help, -h      Показать эту справку

Что делает скрипт:
  1. Скачивает новые образы klvd-node, klvd-solr, klvd-zookeeper (digest-pinned)
  2. Обновляет docker-compose.yml (прописывает новые digest)
  3. Перезапускает контейнеры (docker compose up -d)
  4. Проверяет здоровье узла (health-check)
  5. При --clear-after: удаляет старые образы

Лог: update.log в текущей директории.
HELP
                       exit 0 ;;
        *)             echo "[x] Неизвестный аргумент: $arg (допустимы --sync, --clear-after)" >&2; exit 1 ;;
    esac
done

REGISTRY="registry-ru-2.afi-d.ru"
IMAGE_PATH="afi/klvd/klvd-node"
SOLR_IMAGE_PATH="afi/klvd/klvd-solr"
ZK_IMAGE_PATH="afi/klvd/klvd-zookeeper"
KLVD_NODE_TAG="2026.10.02.4"
KLVD_NODE_DIGEST="sha256:40ec78175d15fea5e205b3cba08d8166f5db3c349cce9c05a01fa74066da47d5"
KLVD_SOLR_DIGEST="sha256:d96ce4305141a0886ee5dbf1737567d28d4553efcd9ff18f2a201873529fe519"
KLVD_ZK_DIGEST="sha256:a4822fc64014213189415ebd2b960a9f74439739ee6cfeb5a4cb7067091738c7"
KLVD_PG_DIGEST="sha256:59033b0c825183e84f626aced4805f5b3472269a2f8d69919b3cc04580d1b14b"
UPDATE_URL="https://static.afi-d.ru/products/klvd/install/update.sh"
COMPOSE_FILE="docker-compose.yml"
LOG_FILE="update.log"

# Логирование: всё в update.log (timestamp + сообщение) + stdout/stderr (цвет).
_log_ts() { date '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo '?'; }
log()  { local msg="[$(_log_ts)] $*"; printf '%s\n' "$msg" >> "$LOG_FILE"; printf '\033[1;34m[klvd]\033[0m %s\n' "$*"; }
ok()   { local msg="[$(_log_ts)] OK: $*"; printf '%s\n' "$msg" >> "$LOG_FILE"; printf '\033[1;32m[✓]\033[0m %s\n' "$*"; }
warn() { local msg="[$(_log_ts)] WARN: $*"; printf '%s\n' "$msg" >> "$LOG_FILE"; printf '\033[1;33m[!]\033[0m %s\n' "$*" >&2; }
err()  { local msg="[$(_log_ts)] ERROR: $*"; printf '%s\n' "$msg" >> "$LOG_FILE"; printf '\033[1;31m[x]\033[0m %s\n' "$*" >&2; }
die()  { err "$*"; exit 1; }

# --sync: скачать свежий update.sh с static, заменить себя и перезапустить без --sync.
if [[ "$DO_SYNC" -eq 1 ]]; then
    log "Самообновление: скачивание свежего update.sh с static.afi-d.ru…"
    TMP_NEW="$(mktemp)"
    wget -qO "$TMP_NEW" "$UPDATE_URL" || { rm -f "$TMP_NEW"; die "Не удалось скачать update.sh ($UPDATE_URL)."; }
    if wget -qO /tmp/klvd-update.sha256 "${UPDATE_URL}.sha256" 2>/dev/null && [[ -s /tmp/klvd-update.sha256 ]]; then
        WANT="$(awk '{print $1}' /tmp/klvd-update.sha256 | tr -dc 'a-fA-F0-9')"
        GOT="$(sha256sum "$TMP_NEW" | awk '{print $1}')"
        if [[ -z "$WANT" || "${WANT,,}" != "${GOT,,}" ]]; then
            rm -f "$TMP_NEW" /tmp/klvd-update.sha256
            die "Контрольная сумма update.sh не совпала (ожидалась ${WANT:-?}, получена $GOT). Возможна подмена."
        fi
        ok "Контрольная сумма update.sh совпадает."
        rm -f /tmp/klvd-update.sha256
    else
        warn "sidecar .sha256 не найден — целостность не проверена."
        rm -f /tmp/klvd-update.sha256
    fi
    local_script="$(readlink -f "$0" 2>/dev/null || echo "$0")"
    chmod +x "$TMP_NEW"
    mv -f "$TMP_NEW" "$local_script"
    ok "update.sh обновлён. Перезапуск…"
    exec "$local_script" ${CLEAR_AFTER:+--clear-after}
fi

# Текущая версия (из running-контейнера).
CURRENT_IMG=""
if docker inspect --format='{{.Config.Image}}' klvd-node >/dev/null 2>&1; then
    CURRENT_IMG="$(docker inspect --format='{{.Config.Image}}' klvd-node 2>/dev/null)"
fi

valid_digest() {
    local d="$1"
    [[ "$d" =~ ^sha256:[0-9a-f]{64}$ ]]
}

if ! valid_digest "$KLVD_NODE_DIGEST"; then
    die "update.sh не заполнен (KLVD_NODE_DIGEST = «${KLVD_NODE_DIGEST:-<empty>}» не похож на sha256:...). Это шаблон, не релиз. Используйте выпущенный update.sh с static.afi-d.ru."
fi
if [[ -n "$KLVD_SOLR_DIGEST" ]] && ! valid_digest "$KLVD_SOLR_DIGEST"; then
    die "KLVD_SOLR_DIGEST = «$KLVD_SOLR_DIGEST» — некорректный формат (ожидался sha256:...)."
fi
if [[ -n "$KLVD_ZK_DIGEST" ]] && ! valid_digest "$KLVD_ZK_DIGEST"; then
    die "KLVD_ZK_DIGEST = «$KLVD_ZK_DIGEST» — некорректный формат (ожидался sha256:...)."
fi
if [[ -n "$KLVD_PG_DIGEST" ]] && ! valid_digest "$KLVD_PG_DIGEST"; then
    die "KLVD_PG_DIGEST = «$KLVD_PG_DIGEST» — некорректный формат (ожидался sha256:...)."
fi

if [[ ! -f "$COMPOSE_FILE" ]]; then
    die "Не найден $COMPOSE_FILE в текущей папке. Запускайте из INSTALL_DIR (где лежит docker-compose.yml), например: cd /opt/klvd && bash update.sh"
fi

command -v docker >/dev/null 2>&1 || die "docker не найден в PATH."
docker info >/dev/null 2>&1 || die "docker daemon недоступен (docker info failed)."
docker compose version >/dev/null 2>&1 || die "docker compose plugin недоступен."

# ── Миграция: старые установки с сервисом «zookeeper» (без klvd-) ──────────────
# В ранних версиях compose сервис назывался `zookeeper`, а update.sh ссылается на
# `klvd-zookeeper` (docker compose оперирует именами сервисов, не container_name).
# Если в compose есть `  zookeeper:` — останавливаем старые контейнеры, переименовываем
# сервис + depends_on у solr, затем продолжаем обычный update (создаст новые контейнеры).
if grep -qE '^  zookeeper:' "$COMPOSE_FILE" 2>/dev/null; then
    log "Миграция: найден старый сервис «zookeeper» — переименование в «klvd-zookeeper»…"
    # Остановить старые контейнеры (по container_name, т.к. сервис переименуется).
    docker compose stop solr zookeeper 2>/dev/null || true
    docker compose rm -f solr zookeeper 2>/dev/null || true
    # Переименовать в compose: `  zookeeper:` → `  klvd-zookeeper:`,
    # и `depends_on: - zookeeper` → `- klvd-zookeeper` (у сервиса solr).
    python3 - "$COMPOSE_FILE" <<'PY' || die "Миграция zookeeper→klvd-zookeeper не удалась."
import re, sys
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
    content = f.read()
# Сервис: «  zookeeper:» в начале строки (2 пробела — top-level service key).
content = re.sub(r'^  zookeeper:', '  klvd-zookeeper:', content, flags=re.MULTILINE)
# depends_on у solr: «- zookeeper» → «- klvd-zookeeper».
content = re.sub(r'(- )zookeeper\b', r'\1klvd-zookeeper', content)
with open(path, "w", encoding="utf-8") as f:
    f.write(content)
print("docker-compose.yml: zookeeper → klvd-zookeeper (сервис + depends_on)")
PY
    ok "Миграция выполнена: сервис переименован в klvd-zookeeper."
fi

# ── Миграция: старые установки с сервисом «solr» (без klvd-) ──────────────────
# Аналогично zookeeper: в ранних версиях compose сервис назывался `solr`, а update.sh
# и depends_on klvd-node ссылаются на `klvd-solr`. Если в compose есть `  solr:` —
# останавливаем старый контейнер, переименовываем сервис + depends_on, затем
# продолжаем обычный update (пересоздаст контейнер под новым именем сервиса).
if grep -qE '^  solr:' "$COMPOSE_FILE" 2>/dev/null; then
    log "Миграция: найден старый сервис «solr» — переименование в «klvd-solr»…"
    docker compose stop solr 2>/dev/null || true
    docker compose rm -f solr 2>/dev/null || true
    python3 - "$COMPOSE_FILE" <<'PY' || die "Миграция solr→klvd-solr не удалась."
import re, sys
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
    content = f.read()
# Сервис: «  solr:» → «  klvd-solr:».
content = re.sub(r'^  solr:', '  klvd-solr:', content, flags=re.MULTILINE)
# depends_on: «- solr» → «- klvd-solr» (у klvd-node и пр.), но НЕ трогаем klvd-solr.
content = re.sub(r'(- )solr\b(?!-)', r'\1klvd-solr', content)
with open(path, "w", encoding="utf-8") as f:
    f.write(content)
print("docker-compose.yml: solr → klvd-solr (сервис + depends_on)")
PY
    ok "Миграция выполнена: сервис переименован в klvd-solr."
fi

# ── Миграция: HTTPS для storage + локального Solr (WUI-серт из secrets/ssl_cert) ──
# На свежих install.sh это уже в compose. Для существующих установок добавляем:
#   • klvd-node env: KLVD_STORAGE_SSL_CERT=/run/secrets/ssl_cert (storage-сервер по HTTPS)
#   • klvd-solr: secrets: [ssl_cert] + healthcheck с https-fallback (Solr HTTPS на WUI-серте)
#   • .env: KLVD_SOLR_URL http://127.0.0.1:8983 → https:// (только точный local-default;
#     кастомный/внешний URL не трогаем). Solr получит серт (secret mount) → HTTPS → URL сходится.
# Идемпотентно. Целевые правки (не YAML-round-trip) — как миграция zookeeper.
HTTPS_MIGRATED=0
if grep -qE 'KLVD_STORAGE_SSL_CERT' "$COMPOSE_FILE" 2>/dev/null; then
    log "HTTPS-миграция: KLVD_STORAGE_SSL_CERT уже присутствует в compose — пропуск."
else
    log "HTTPS-миграция: добавление storage/Solr HTTPS в compose + .env…"
    python3 - "$COMPOSE_FILE" <<'PY' || die "HTTPS-миграция compose не удалась."
import re, sys
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
    c = f.read()
changed = []

# 1) klvd-node env: KLVD_STORAGE_SSL_CERT после KLVD_STORAGE_DATABASE_PATH (если строки нет).
if "KLVD_STORAGE_SSL_CERT" not in c:
    c, n = re.subn(
        r'(KLVD_STORAGE_DATABASE_PATH:\s*"[^"]*"\n)',
        r'\1      KLVD_STORAGE_SSL_CERT: /run/secrets/ssl_cert\n',
        c, count=1)
    if n: changed.append("klvd-node KLVD_STORAGE_SSL_CERT env")

# 2) klvd-solr: secrets: [ssl_cert] — вставляем после command: [...] строки solr-сервиса,
#    если секрета там ещё нет. Ищем блок «container_name: klvd-solr» … «command:».
if re.search(r'container_name:\s*klvd-solr', c) and not re.search(
        r'container_name:\s*klvd-solr.*?secrets:\s*\n\s*-\s*ssl_cert', c, re.DOTALL):
    # вставить «secrets:\n  - ssl_cert» после строки «command: [...]»
    c, n = re.subn(
        r'(command:\s*\[.*?\]\n)(\s*ulimits:)',
        r'\1    secrets:\n      - ssl_cert\n\2',
        c, count=1)
    if n: changed.append("klvd-solr secrets mount")

# 3) healthcheck: http-only → https-fallback (если ещё старый). Идемпотентно: не трогаем,
#    если уже есть https:// (fallback-часть http:// совпала бы с паттерном → двойная замена).
if "https://localhost:8983" not in c:
    c, n = re.subn(
        r'curl -fsS http://localhost:8983/solr/admin/cores\?action=STATUS >/dev/null 2>&1 \|\| exit 1',
        r'curl -fsS -k https://localhost:8983/solr/admin/cores?action=STATUS >/dev/null 2>&1 || curl -fsS http://localhost:8983/solr/admin/cores?action=STATUS >/dev/null 2>&1 || exit 1',
        c)
    if n: changed.append("solr healthcheck https-fallback")

with open(path, "w", encoding="utf-8") as f:
    f.write(c)
print("docker-compose.yml HTTPS: " + (", ".join(changed) if changed else "ничего не изменено (уже актуально)"))
PY
    # .env: KLVD_SOLR_URL local-default http→https (только точное совпадение).
    if [[ -f ".env" ]]; then
        if grep -qx 'KLVD_SOLR_URL=http://127.0.0.1:8983/solr' .env 2>/dev/null \
           || grep -qx 'KLVD_SOLR_URL=http://localhost:8983/solr' .env 2>/dev/null; then
            python3 - <<'PY'
import re
with open(".env", "r", encoding="utf-8") as f: c = f.read()
c2 = re.sub(r'^KLVD_SOLR_URL=http://(127\.0\.0\.1|localhost):8983/solr\s*$',
            lambda m: m.group(0).replace("http://", "https://", 1), c, flags=re.MULTILINE)
with open(".env", "w", encoding="utf-8") as f: f.write(c2)
print(".env: KLVD_SOLR_URL http→https (local-default)")
PY
            ok ".env: KLVD_SOLR_URL обновлён на https (локальный Solr)."
        else
            log ".env: KLVD_SOLR_URL не local-default (внешний/кастомный) — не трогается. Если это локальный Solr и URL=http, смените на https вручную после обновления."
        fi
    fi
    HTTPS_MIGRATED=1
fi

# ── Миграция: проброс системного CA-store хоста в klvd-node ─────────────────────
# Контейнер использует свой собственный CA-store из образа, в котором нет
# корпоративного CA (LDAP/AD, внутренние Solr/EWS). Монтирование /etc/ssl/certs
# хоста read-only позволяет контейнеру доверять CA, установленным через
# update-ca-certificates на хосте. Идемпотентно: пропуск, если volume уже есть.
CA_MIGRATED=0
if grep -qE '/etc/ssl/certs:/etc/ssl/certs' "$COMPOSE_FILE" 2>/dev/null; then
    log "CA-store миграция: volume /etc/ssl/certs уже есть в compose — пропуск."
else
    log "CA-store миграция: добавление volume /etc/ssl/certs (read-only) в klvd-node…"
    python3 - "$COMPOSE_FILE" <<'PY' || die "CA-store миграция compose не удалась."
import re, sys
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
    c = f.read()

# Вставить volume в секцию klvd-node, после последней строки «- klvd_shared:/shared»
# (или после последнего «- ...:/var/...» volume, если klvd_shared отсутствует).
# Идемпотентно: если строка уже есть — ничего не делаем.
if "/etc/ssl/certs:/etc/ssl/certs" not in c:
    # Ищем блок volumes: сервис klvd-node (до следующего «  <svc>:» или «volumes:»
    # верхнего уровня). Берём последнюю строку «      - ...» в нём и вставляем после.
    pat = re.compile(r'(container_name:\s*klvd-node\b.*?(\n\s+volumes:\s*\n(?:\s+-\s+[^\n]+\n)+))', re.DOTALL)
    m = pat.search(c)
    if not m:
        # У сервиса klvd-node нет volumes: вообще — вставляем секцию volumes после
        # depends_on: блока (или после environment:).
        pat2 = re.compile(r'(container_name:\s*klvd-node\b.*?(\n\s+(?:depends_on|environment):[^\n]*(?:\n\s+(?:-\s+\S+|\S+:[^\n]*))*))', re.DOTALL)
        m2 = pat2.search(c)
        if m2:
            insert_after = m2.group(2)
            addition = insert_after + "\n    volumes:\n      - /etc/ssl/certs:/etc/ssl/certs:ro"
            c = c[:m2.start(2)] + addition + c[m2.end(2):]
        else:
            print("WARN: блок klvd-node не найден — volume не добавлен.", file=sys.stderr)
            sys.exit(0)
    else:
        vol_block = m.group(2)
        # Найти последнюю строку «      - ...» в vol_block, вставить CA после неё.
        lines = vol_block.splitlines()
        last_vol_idx = max(i for i, ln in enumerate(lines) if ln.strip().startswith("- "))
        lines.insert(last_vol_idx + 1, "      - /etc/ssl/certs:/etc/ssl/certs:ro")
        new_vol_block = "\n".join(lines)
        c = c[:m.start(2)] + new_vol_block + c[m.end(2):]

    with open(path, "w", encoding="utf-8") as f:
        f.write(c)
    print("docker-compose.yml: добавлен volume /etc/ssl/certs:/etc/ssl/certs:ro в klvd-node")
PY
    ok "CA-store миграция выполнена: /etc/ssl/certs пробрасывается в klvd-node."
    CA_MIGRATED=1
fi

pin_image_in_compose() {
    local image_regex="$1" digest="$2" svc="$3"
    local rc=0
    python3 - "$COMPOSE_FILE" "$image_regex" "$digest" "$svc" <<'PY' || rc=$?
import re, sys
path, image_regex, digest, svc = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
with open(path, "r", encoding="utf-8") as f:
    content = f.read()
pat = re.compile(image_regex, re.MULTILINE)
new, n = pat.subn(lambda m: f"{m.group(1)}@{digest}", content)
if n == 0:
    print(f"WARN: строка image для «{svc}» не найдена в {path} — сервис отсутствует в этом compose (возможно, standalone без Solr/ZK).", file=sys.stderr)
    sys.exit(2)
with open(path, "w", encoding="utf-8") as f:
    f.write(new)
print(f"docker-compose.yml: {svc} — заменено строк image: {n}")
PY
    if [[ $rc -eq 0 ]]; then
        return 0
    elif [[ $rc -eq 2 ]]; then
        return 1
    else
        die "Не удалось обновить image-строку $svc в $COMPOSE_FILE (python exit $rc)."
    fi
}

NODE_PINNED="${REGISTRY}/${IMAGE_PATH}@${KLVD_NODE_DIGEST}"
# Версия: переход current → new.
if [[ -n "$CURRENT_IMG" ]]; then
    log "Версия: ${CURRENT_IMG##*/}  →  ${IMAGE_PATH##*/}:${KLVD_NODE_TAG} (@${KLVD_NODE_DIGEST:0:19}…)"
else
    log "Версия: (контейнер не найден)  →  ${IMAGE_PATH##*/}:${KLVD_NODE_TAG} (@${KLVD_NODE_DIGEST:0:19}…)"
fi
log "Обновление klvd-node -> $NODE_PINNED"
pin_image_in_compose \
    "^(\s*image:\s*registry-ru-2\.afi-d\.ru/afi/klvd/klvd-node)[:@][^\n]*$" \
    "$KLVD_NODE_DIGEST" "klvd-node" \
    && ok "docker-compose.yml: klvd-node обновлён."

# Локальный Solr используется на узле, только если KLVD_SOLR_URL задан в .env
# (установщик пишет туда http://127.0.0.1:8983/solr при локальном Solr; пусто — Solr
# внешний или отключён). Без локального Solr обновлять klvd-solr/klvd-zookeeper не нужно,
# даже если их строки случайно остались в compose — их не запускают.
ENV_SOLR_URL=""
if [[ -f ".env" ]]; then
    ENV_SOLR_URL="$(grep -E '^KLVD_SOLR_URL=' .env 2>/dev/null | head -1 | cut -d'=' -f2- || true)"
fi

SOLR_UPDATED=0
if [[ -z "$ENV_SOLR_URL" ]]; then
    log "KLVD_SOLR_URL в .env пуст — локальный Solr на узле не используется. Обновление klvd-solr пропускается."
elif [[ -n "$KLVD_SOLR_DIGEST" ]]; then
    SOLR_PINNED="${REGISTRY}/${SOLR_IMAGE_PATH}@${KLVD_SOLR_DIGEST}"
    log "Обновление klvd-solr -> $SOLR_PINNED"
    if pin_image_in_compose \
        "^(\s*image:\s*registry-ru-2\.afi-d\.ru/afi/klvd/klvd-solr)[:@][^\n]*$" \
        "$KLVD_SOLR_DIGEST" "klvd-solr"
    then
        ok "docker-compose.yml: klvd-solr обновлён."
        SOLR_UPDATED=1
    else
        warn "klvd-solr не найден в этом compose — обновление Solr пропущено (standalone-установка без поиска)."
    fi
else
    warn "KLVD_SOLR_DIGEST пуст — Solr не обновляется этим релизом (только klvd-node)."
fi

ZK_UPDATED=0
if [[ -z "$ENV_SOLR_URL" ]]; then
    log "Локальный Solr не используется — обновление klvd-zookeeper пропускается."
elif [[ -n "$KLVD_ZK_DIGEST" ]]; then
    ZK_PINNED="${REGISTRY}/${ZK_IMAGE_PATH}:3.8@${KLVD_ZK_DIGEST}"
    log "Обновление klvd-zookeeper -> $ZK_PINNED"
    rc=0
    python3 - "$COMPOSE_FILE" "$KLVD_ZK_DIGEST" <<'PY' || rc=$?
import re, sys
path, digest = sys.argv[1], sys.argv[2]
with open(path, "r", encoding="utf-8") as f:
    content = f.read()
pat1 = re.compile(r"^(\s*image:\s*)zookeeper:3\.8\s*$", re.MULTILINE)
pat2 = re.compile(r"^(\s*image:\s*registry-ru-2\.afi-d\.ru/afi/klvd/klvd-zookeeper:3\.8)[:@][^\n]*$", re.MULTILINE)
new, n1 = pat1.subn(lambda m: f"{m.group(1)}registry-ru-2.afi-d.ru/afi/klvd/klvd-zookeeper:3.8@{digest}", content)
n = n1
if n == 0:
    new, n2 = pat2.subn(lambda m: f"{m.group(1)}@{digest}", content)
    n = n2
if n == 0:
    print(f"WARN: строка image zookeeper:3.8 (или уже-пинённая klvd-zookeeper) не найдена в {path} — сервис отсутствует (standalone без SolrCloud).", file=sys.stderr)
    sys.exit(2)
with open(path, "w", encoding="utf-8") as f:
    f.write(new)
print(f"docker-compose.yml: klvd-zookeeper — заменено строк image: {n}")
PY
    if [[ $rc -eq 0 ]]; then
        ok "docker-compose.yml: klvd-zookeeper обновлён."
        ZK_UPDATED=1
    elif [[ $rc -eq 2 ]]; then
        warn "klvd-zookeeper не найден в этом compose — обновление ZK пропущено."
    else
        die "Не удалось обновить image-строку klvd-zookeeper в $COMPOSE_FILE (python exit $rc)."
    fi
else
    warn "KLVD_ZK_DIGEST пуст — ZooKeeper не обновляется этим релизом (только klvd-node)."
fi

SERVICES_TO_UPDATE=("klvd-node")
[[ "$SOLR_UPDATED" -eq 1 ]] && SERVICES_TO_UPDATE+=("klvd-solr")
[[ "$ZK_UPDATED"   -eq 1 ]] && SERVICES_TO_UPDATE+=("klvd-zookeeper")

log "docker compose pull: ${SERVICES_TO_UPDATE[*]} …"
for svc in "${SERVICES_TO_UPDATE[@]}"; do
    docker compose pull "$svc" || die "docker compose pull $svc не выполнен. Проверьте доступ к https://registry-ru-2.afi-d.ru/ и учётные данные (docker login)."
done

log "Пересоздание контейнеров: ${SERVICES_TO_UPDATE[*]} (PG не затрагивается)…"
docker compose up -d --force-recreate "${SERVICES_TO_UPDATE[@]}" || die "docker compose up --force-recreate не выполнен."

ok "Обновление выполнено:"
ok "  klvd-node      -> $NODE_PINNED"
[[ "$SOLR_UPDATED" -eq 1 ]] && ok "  klvd-solr      -> $SOLR_PINNED"
[[ "$ZK_UPDATED"   -eq 1 ]] && ok "  klvd-zookeeper -> $ZK_PINNED"
echo
docker compose ps

verify_running() {
    local svc="$1" digest="$2"
    # Фактический digest образа, на котором бежит контейнер (надёжнее, чем .Config.Image —
    # строка из конфига может отставать, если compose не пересоздал контейнер). .Image
    # возвращает ID образа → docker inspect этого образа → .RepoDigests[] содержит digest.
    local img_digest
    img_digest="$(docker inspect --format='{{.Image}}' "$svc" 2>/dev/null || true)"
    if [[ -n "$img_digest" ]]; then
        local repo_digest
        repo_digest="$(docker inspect --format='{{json .RepoDigests}}' "$img_digest" 2>/dev/null \
            | python3 -c "import json,sys; d=json.load(sys.stdin); print(next((x.split('@',1)[1] for x in (d or []) if '@' in x), ''))" 2>/dev/null || true)"
        if [[ -n "$repo_digest" && "$repo_digest" == "$digest" ]]; then
            ok "Контейнер $svc запущен на ожидаемом digest $digest"
        elif [[ -n "$repo_digest" ]]; then
            err "ВНИМАНИЕ: $svc запущен на digest $repo_digest, а ожидался $digest. Проверьте docker compose ps и повторите update.sh."
        else
            # .RepoDigests пуст (локальный образ без push) — fallback на .Config.Image.
            local running
            running="$(docker inspect --format='{{.Config.Image}}' "$svc" 2>/dev/null || true)"
            if [[ -n "$running" && "$running" == *"$digest" ]]; then
                ok "Контейнер $svc: $running"
            else
                warn "Контейнер $svc: не удалось проверить digest (образ без RepoDigests). image=$running"
            fi
        fi
    fi
}
verify_running "klvd-node" "$KLVD_NODE_DIGEST"
[[ "$SOLR_UPDATED" -eq 1 ]] && verify_running "klvd-solr" "$KLVD_SOLR_DIGEST"
[[ "$ZK_UPDATED"   -eq 1 ]] && verify_running "klvd-zookeeper" "$KLVD_ZK_DIGEST"

if [[ "$CLEAR_AFTER" -eq 1 ]]; then
    log "Очистка старых образов и неиспользуемых volumes (--clear-after)…"
    docker image prune -a -f || warn "image prune завершился с ошибкой (не критично)."
    docker volume prune -f    || warn "volume prune завершился с ошибкой (не критично)."
    ok "Очистка выполнена."
fi
