#!/usr/bin/env bash
# install-validata.sh — установка АПК «Валидата Клиент L» под bj-crypto.
#
# Поддерживаемые ОС:
#   - Debian 11 / 12        (основная, бесплатная)
#   - Astra Linux SE 1.7    (платная, для регуляторно-обязанных)
#   - Astra Linux CE 1.8    (бесплатная)
#   - Ubuntu 22.04 / 24.04  (с предупреждением)
#
# Что делает:
#   1. Ставит зависимости (pcscd, libpcsclite, libgtk-3, libldap, p7zip, execstack)
#   2. Ставит zpki + zsdk deb-пакеты Валидаты
#   3. execstack -c libvdcsp.so (исправление GNU_STACK с RWE на RW)
#   4. Создаёт системного пользователя bj (если ещё нет)
#   5. Кладёт 5 systemd drop-ins (pcscd no-autoexit + 3×bj-crypto + 1×bj-server)
#   6. Создаёт /opt/Validata/VDCSP/etc/spki.ini (Валидата с ним капризничает)
#   7. Дописывает заголовочную секцию в pki1.conf
#   8. Включает pcscd в режиме always-on (без socket-активации) — Валидата
#      ожидает постоянно живой демон, иначе ловит 0x8010001D
#   9. Ставит udev-rule + systemd-mount unit для авто-mount USB-флешек с .vdk
#      в /var/lib/bj/usb/<label>/ с владельцем bj — это убирает необходимость
#      пробрасывать /media/<gui-user>/
#  10. systemctl daemon-reload + enable/start bj-crypto + bj-server
#
# Идемпотентный — повторный запуск ничего не ломает.
#
# Запуск:
#   sudo bash install-validata.sh [path-to-validata-zpki.deb]
#
# Если путь не передан — ищет:
#   ./ClientL_Other/zpki-*.deb
#   /opt/bj/src/dist/validata/*.deb
#   ~/Загрузки/ClientL_Other/*.deb
#   ~/Downloads/ClientL_Other/*.deb

set -euo pipefail

# --------------------------------------------------------------------- #
# Логирование
# --------------------------------------------------------------------- #
log()   { echo -e "\033[1;34m[validata-install]\033[0m $*"; }
ok()    { echo -e "\033[1;32m[validata-install OK]\033[0m $*"; }
warn()  { echo -e "\033[1;33m[validata-install WARN]\033[0m $*" >&2; }
fail()  { echo -e "\033[1;31m[validata-install FAIL]\033[0m $*" >&2; exit 1; }

[ "$EUID" -eq 0 ] || fail "Запускать от root (sudo)"

# --------------------------------------------------------------------- #
# 1. Детект ОС
# --------------------------------------------------------------------- #
DISTRO=""
DISTRO_VERSION=""
if [ -r /etc/os-release ]; then
    . /etc/os-release
    case "$ID" in
        astra) DISTRO=astra; DISTRO_VERSION="${VERSION_ID:-unknown}";;
        debian) DISTRO=debian; DISTRO_VERSION="${VERSION_ID:-unknown}";;
        ubuntu) DISTRO=ubuntu; DISTRO_VERSION="${VERSION_ID:-unknown}";;
        *) DISTRO="$ID"; DISTRO_VERSION="${VERSION_ID:-unknown}";;
    esac
fi
case "$DISTRO" in
    debian|astra) ok "ОС: $PRETTY_NAME ($DISTRO $DISTRO_VERSION) — поддерживается";;
    ubuntu)      warn "ОС: $PRETTY_NAME — поддерживается на свой страх (Validata в формуляре нет)";;
    *)           warn "ОС: $PRETTY_NAME — не проверена, продолжаю на свой страх";;
esac

# --------------------------------------------------------------------- #
# 2. Поиск deb-пакетов Валидаты
# --------------------------------------------------------------------- #
ZPKI_DEB=""
ZSDK_DEB=""

if [ -n "${1:-}" ] && [ -f "$1" ]; then
    # Конкретный файл передан аргументом
    ZPKI_DEB="$1"
    # zsdk ищем рядом
    ZSDK_DEB="$(dirname "$1")/$(basename "$1" | sed 's/zpki/zsdk/')"
else
    for d in \
        ./ClientL_Other \
        ./ClientL_ALSE \
        /opt/bj/src/dist/validata \
        /home/*/Загрузки/ClientL_Other \
        /home/*/Загрузки/ClientL_ALSE \
        /home/*/Downloads/ClientL_Other \
        /home/*/Downloads/ClientL_ALSE \
        /tmp/ClientL_Other; do
        [ -d "$d" ] || continue
        cand_zpki=$(find "$d" -maxdepth 1 -name "zpki-*.amd64.deb" 2>/dev/null | head -1)
        cand_zsdk=$(find "$d" -maxdepth 1 -name "zsdk-*.amd64.deb" 2>/dev/null | head -1)
        if [ -n "$cand_zpki" ]; then
            ZPKI_DEB="$cand_zpki"
            ZSDK_DEB="$cand_zsdk"
            break
        fi
    done
fi

[ -n "$ZPKI_DEB" ] || fail "Не найден zpki-*.amd64.deb. Скачайте https://fs.moex.com/cdp/po/ClientL_Other.zip и распакуйте рядом со скриптом или в /opt/bj/src/dist/validata/"

log "Найдено:"
log "  zpki: $ZPKI_DEB"
[ -n "$ZSDK_DEB" ] && log "  zsdk: $ZSDK_DEB" || warn "  zsdk не найден — будет пропущен"

# --------------------------------------------------------------------- #
# 3. Системные зависимости
# --------------------------------------------------------------------- #
log "Обновляю apt-кеш и ставлю зависимости..."
apt-get update -qq

# Базовые зависимости (одинаковые на Debian/Astra)
DEPS=(
    libgtk-3-0
    libpcsclite1
    libccid
    pcscd
    libcurl4
    libkrb5-3
    libgssapi-krb5-2
    libsasl2-modules
    libsasl2-modules-gssapi-mit
    execstack
    p7zip-full
    util-linux
    uuid-runtime
)

# libldap-2.4-2 в Bullseye/Astra; в Bookworm уже libldap-2.5-0.
# Зависимость в zpki жёстко на 2.4-2 → используем --force-depends на этом этапе
# и ставим libldap-2.5-0 как замену (ABI совместим в нашем use-case).
if apt-cache show libldap-2.4-2 >/dev/null 2>&1; then
    DEPS+=(libldap-2.4-2)
    USE_FORCE=0
else
    DEPS+=(libldap-2.5-0)
    USE_FORCE=1
    warn "libldap-2.4-2 недоступен → ставлю libldap-2.5-0 и буду форсить --force-depends при установке zpki"
fi

apt-get install -y --no-install-recommends "${DEPS[@]}"
ok "Зависимости установлены"

# --------------------------------------------------------------------- #
# 4. Установка Валидаты
# --------------------------------------------------------------------- #
log "Ставлю zpki..."
if [ "$USE_FORCE" = "1" ]; then
    dpkg --force-depends -i "$ZPKI_DEB"
else
    dpkg -i "$ZPKI_DEB" || { apt-get -f install -y; dpkg -i "$ZPKI_DEB"; }
fi
if [ -n "$ZSDK_DEB" ]; then
    log "Ставлю zsdk..."
    dpkg -i "$ZSDK_DEB" || { apt-get -f install -y; dpkg -i "$ZSDK_DEB"; }
fi

[ -d /opt/Validata/VDCSP/lib/amd64 ] || fail "Валидата не установилась в /opt/Validata — проверьте dpkg -L zpki"
ok "Валидата в /opt/Validata/VDCSP"

# --------------------------------------------------------------------- #
# 5. execstack libvdcsp.so (GNU_STACK RWE → RW)
# --------------------------------------------------------------------- #
log "execstack -c libvdcsp.so (требование Валидаты)..."
if execstack -q /opt/Validata/VDCSP/lib/amd64/libvdcsp.so 2>/dev/null | grep -q '^X'; then
    execstack -c /opt/Validata/VDCSP/lib/amd64/libvdcsp.so
    ok "executable-stack снят"
else
    ok "executable-stack уже снят"
fi

# --------------------------------------------------------------------- #
# 6. Системный пользователь bj
# --------------------------------------------------------------------- #
if ! id bj >/dev/null 2>&1; then
    log "Создаю системного пользователя bj..."
    useradd --system --create-home --home-dir /var/lib/bj --shell /bin/bash bj
    ok "Пользователь bj создан (home=/var/lib/bj)"
else
    ok "Пользователь bj уже есть"
fi

install -d -o bj -g bj -m 0755 /var/lib/bj/usb
install -d -o bj -g bj -m 0700 /var/lib/bj/.Validata
install -d -o bj -g bj -m 0700 /var/lib/bj/.Validata/vdkeys
install -d -o bj -g bj -m 0755 /var/lib/bj/profiles
install -d -o bj -g bj -m 0755 /var/log/bj

# --------------------------------------------------------------------- #
# 7. pcscd: убираем --auto-exit и socket-активацию
# --------------------------------------------------------------------- #
log "Настраиваю pcscd как always-on демон..."
install -d /etc/systemd/system/pcscd.service.d
cat >/etc/systemd/system/pcscd.service.d/no-autoexit.conf <<'EOF'
[Unit]
# Отвязываем сервис от сокет-юнита, чтобы можно было держать его постоянно живым
Requires=
After=
Sockets=

[Service]
# Убираем --auto-exit — Валидата ожидает постоянно живой pcscd, иначе
# получает 0x8010001D «Диспетчер ресурсов смарт-карт не выполняется»
# при попытке найти ключевой носитель (.vdk файл выглядит для неё как
# виртуальная смарт-карта)
ExecStart=
ExecStart=/usr/sbin/pcscd --foreground
EOF
ok "pcscd drop-in: /etc/systemd/system/pcscd.service.d/no-autoexit.conf"

# --------------------------------------------------------------------- #
# 8. bj-crypto drop-ins
# --------------------------------------------------------------------- #
log "Кладу drop-ins для bj-crypto..."
install -d /etc/systemd/system/bj-crypto.service.d

cat >/etc/systemd/system/bj-crypto.service.d/validata-paths.conf <<'EOF'
[Service]
# Валидата ищет pki1.conf в текущей рабочей директории — работаем оттуда
WorkingDirectory=/opt/Validata/VDCSP/etc
# Валидата пишет в /opt/Validata/VDCSP/etc/pki1.conf при инициализации
# профиля. ProtectSystem=strict делает /opt read-only — открываем точечно.
ReadWritePaths=/opt/Validata/VDCSP/etc
ReadWritePaths=/var/lib/bj
EOF

cat >/etc/systemd/system/bj-crypto.service.d/usb-access.conf <<'EOF'
[Service]
# Без этого PrivateTmp + ProtectSystem закроет /media и /var/lib/bj/usb,
# а нам нужно туда смотреть в поисках .vdk на флешке.
ReadOnlyPaths=/media
ReadOnlyPaths=/var/lib/bj/usb
EOF

cat >/etc/systemd/system/bj-crypto.service.d/share-crysvc.conf <<'EOF'
[Service]
# Валидата общается с криптодрайвером (vdcrysvc) через Unix-сокет
# /tmp/.crysvc.sock — но PrivateTmp=true даёт нам приватный /tmp.
# Прокидываем именно этот сокет внутрь нашего namespace.
PrivateTmp=true
BindPaths=/tmp/.crysvc.sock:/tmp/.crysvc.sock
EOF
ok "bj-crypto drop-ins: validata-paths.conf, usb-access.conf, share-crysvc.conf"

# --------------------------------------------------------------------- #
# 9. bj-server drop-in
# --------------------------------------------------------------------- #
log "Кладу drop-in для bj-server..."
install -d /etc/systemd/system/bj-server.service.d
cat >/etc/systemd/system/bj-server.service.d/pki1conf.conf <<'EOF'
[Service]
# bj-server при импорте профиля дописывает секцию в pki1.conf.
# ProtectSystem=strict закрывает /opt — открываем точечно.
ReadWritePaths=/opt/Validata/VDCSP/etc
EOF
ok "bj-server drop-in: pki1conf.conf"

# --------------------------------------------------------------------- #
# 10. spki.ini — Валидата требует, при отсутствии mkstores падает
# --------------------------------------------------------------------- #
SPKI=/opt/Validata/VDCSP/etc/spki.ini
if [ ! -f "$SPKI" ]; then
    log "Создаю $SPKI (Валидата без него падает в mkstores/zpki1utl)..."
    cat >"$SPKI" <<'EOF'
[store]
count = 0

[Parameters]
PkiLdapTimeout = 10
PkiHttpTimeout = 60
EOF
    chmod 644 "$SPKI"
    ok "spki.ini создан"
else
    ok "spki.ini уже есть"
fi

# --------------------------------------------------------------------- #
# 11. pki1.conf — делаем доступным для записи группе bj
# --------------------------------------------------------------------- #
PKI1=/opt/Validata/VDCSP/etc/pki1.conf
if [ -f "$PKI1" ]; then
    chgrp bj "$PKI1"
    chmod g+w "$PKI1"
    ok "pki1.conf: group=bj, g+w"
    if ! grep -q "^# --- bj-server: BEGIN ---" "$PKI1"; then
        printf '\n# --- bj-server: BEGIN ---\n# Секции профилей дописываются автоматически при импорте через /admin/setup.\n# --- bj-server: END ---\n' >> "$PKI1"
        ok "В pki1.conf добавлены маркеры bj-server"
    fi
fi

# --------------------------------------------------------------------- #
# 12. udev-rule для авто-mount USB с .vdk
# --------------------------------------------------------------------- #
log "Ставлю udev-rule для авто-mount USB → /var/lib/bj/usb/..."
cat >/etc/udev/rules.d/99-bj-usb.rules <<'EOF'
# Авто-mount USB-флешек в /var/lib/bj/usb/<label> с владельцем bj.
# Применяется только к USB-устройствам (SUBSYSTEMS=="usb") с файловой
# системой. Mountpoint выбирается по метке тома или UUID.
ACTION=="add", KERNEL=="sd?[0-9]", SUBSYSTEMS=="usb", \
    ENV{ID_FS_TYPE}!="", \
    ENV{SYSTEMD_WANTS}="bj-usb-mount@$env{ID_FS_UUID}.service", \
    ENV{SYSTEMD_USER_WANTS}=""
ACTION=="remove", KERNEL=="sd?[0-9]", SUBSYSTEMS=="usb", \
    ENV{ID_FS_TYPE}!="", \
    ENV{SYSTEMD_WANTS}="bj-usb-umount@$env{ID_FS_UUID}.service"
EOF

# Систэмный template-сервис, который монтирует и umonтирует
cat >/etc/systemd/system/bj-usb-mount@.service <<'EOF'
[Unit]
Description=Mount USB %i to /var/lib/bj/usb/%i for bj
DefaultDependencies=no
After=local-fs.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/bash -c 'mkdir -p /var/lib/bj/usb/%i && /usr/bin/mount -o uid=$(id -u bj),gid=$(id -g bj),fmask=0133,dmask=0022 UUID=%i /var/lib/bj/usb/%i'
ExecStop=/usr/bin/umount /var/lib/bj/usb/%i || true
EOF

cat >/etc/systemd/system/bj-usb-umount@.service <<'EOF'
[Unit]
Description=Umount USB %i from /var/lib/bj/usb/%i
DefaultDependencies=no

[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c '/usr/bin/umount /var/lib/bj/usb/%i 2>/dev/null; /usr/bin/rmdir /var/lib/bj/usb/%i 2>/dev/null; true'
EOF

udevadm control --reload-rules
udevadm trigger
ok "udev-rule + systemd-mount шаблон установлены"

# --------------------------------------------------------------------- #
# 13. Установка bj-crypto.service unit (если его ещё нет — берём из репы)
# --------------------------------------------------------------------- #
SELF_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_UNIT="$SELF_DIR/../systemd/bj-crypto.service"
if [ ! -f /etc/systemd/system/bj-crypto.service ] && [ -f "$REPO_UNIT" ]; then
    log "Устанавливаю /etc/systemd/system/bj-crypto.service из репы..."
    install -m 0644 "$REPO_UNIT" /etc/systemd/system/bj-crypto.service
    ok "bj-crypto.service установлен"
fi

# --------------------------------------------------------------------- #
# 14. daemon-reload + старт сервисов
# --------------------------------------------------------------------- #
log "systemctl daemon-reload..."
systemctl daemon-reload

log "Отключаю pcscd.socket (его подменяет наш drop-in always-on)..."
systemctl disable pcscd.socket 2>/dev/null || true
systemctl stop pcscd.socket 2>/dev/null || true

log "Запускаю pcscd..."
systemctl enable pcscd
systemctl restart pcscd

if [ -f /etc/systemd/system/bj-crypto.service ]; then
    log "Запускаю bj-crypto..."
    systemctl enable bj-crypto
    systemctl restart bj-crypto
fi

# --------------------------------------------------------------------- #
# 15. Финальная проверка
# --------------------------------------------------------------------- #
echo
echo "================================================================"
echo "  Валидата установлена, окружение настроено"
echo "================================================================"
for svc in pcscd vdcrysvc bj-crypto; do
    if systemctl is-active --quiet "$svc" 2>/dev/null; then
        echo "  ✓ $svc — active"
    else
        echo "  ✗ $svc — НЕ active (проверьте journalctl -u $svc)"
    fi
done
echo
echo "  Дальнейшие шаги:"
echo "    1. Подключите USB с .vdk → авто-маунт в /var/lib/bj/usb/<UUID>/"
echo "    2. Откройте /admin/setup в bj-server"
echo "    3. Загрузите .7z с профилем → bj-server сам всё извлечёт и импортирует"
echo "    4. Нажмите «Активировать профиль»"
echo "================================================================"
