Files
Bridge-and-Join-s/deploy/linux/sbin/bj-keymedia-install
T
fontvielle 42ae5ed12b Ключ: igate-traverse к staging (/var/lib/bj) — чинит ложное «нет local.pse»
Пересборку справочника crestor запускает igate, а /var/lib/bj был 0750 bj:bj —
igate не мог пройти в staging и выдавал ложное «нет local.pse». bj-server при
старте даёт /var/lib/bj traverse (0751) — доезжает обновлением. Воркер
bj-keymedia-install тоже даёт права и показывает реальную ошибку crestor вместо
спама «dropped older CRL» (для новых ISO). Убрана ошибочная подсказка про ГСЧ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
2026-10-21 10:00:24 +03:00

374 lines
25 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# bj-keymedia-install — установка ключа Валидаты на съёмный носитель.
# Сохраняет структуру архива НРД (key.reg + spr<N>/ + vdkeys/) в профиль на
# носителе, ПЕРЕСОБИРАЕТ совместимый справочник сертификатов (архивный gdbm НРД
# несовместим — bj-validata-rebuild-gdbm чинит), и формирует профиль для ИШ.
# inspect <staging>
# install <staging> [profile_name]
# restart-ish
set -uo pipefail
DEFAULT_KEYMEDIA=/var/lib/igate/keymedia
BACKUP_DIR=/var/lib/bj/key-backups
IGATE_PROFILES=/var/lib/igate/profiles
IGATE_INI=/var/lib/igate/.Validata/zpki.ini
VDLIB=/opt/Validata/VDCSP/lib/amd64
ZPKI=/opt/Validata/VDCSP/bin/amd64/zpki1utl
REBUILD=/usr/local/sbin/bj-validata-rebuild-gdbm
ACTION="${1:-}"; STAGING="${2:-}"; PROFILE="${3:-}"
TARGET_MNT="${BJ_TARGET_MNT:-}"; TARGET_DEV="${BJ_TARGET_DEV:-}"
err(){ echo "{\"ok\":false,\"error\":\"$1\"}"; exit 1; }
# attach_keymedia DEV — ключевая флешка постоянно доступна ИШ, только чтение, под igate (как на рабочем сервере):
# строка fstab по UUID на $DEFAULT_KEYMEDIA с автомонтированием и ссылка реестра ключей Валидаты пользователя igate
# на флешку. Прежний fstab — копией /etc/fstab.bak-bj-<время>. Уже подключена эта же флешка — ничего не меняет.
attach_keymedia(){
local dev="$1" uuid fst ig_uid ig_gid opts unit
[ -b "$dev" ] || return 0
uuid=$(blkid -s UUID -o value "$dev" 2>/dev/null); fst=$(blkid -s TYPE -o value "$dev" 2>/dev/null)
[ -n "$uuid" ] && [ -n "$fst" ] || return 0
ig_uid=$(id -u igate 2>/dev/null) || return 0; ig_gid=$(id -g igate)
if ! grep -Eq "^UUID=$uuid[[:space:]]+$DEFAULT_KEYMEDIA[[:space:]]" /etc/fstab; then
cp -a /etc/fstab "/etc/fstab.bak-bj-$(date +%Y%m%d-%H%M%S)"
sed -i "\#[[:space:]]$DEFAULT_KEYMEDIA[[:space:]]#d" /etc/fstab
case "$fst" in vfat|exfat|ntfs*) opts="ro,uid=$ig_uid,gid=$ig_gid,fmask=0133,dmask=0022" ;; *) opts="ro" ;; esac
printf 'UUID=%s %s %s %s,nofail,x-systemd.automount,x-systemd.device-timeout=10 0 0\n' "$uuid" "$DEFAULT_KEYMEDIA" "$fst" "$opts" >> /etc/fstab
umount "$DEFAULT_KEYMEDIA" 2>/dev/null; mkdir -p "$DEFAULT_KEYMEDIA"
unit=$(systemd-escape -p --suffix=automount "$DEFAULT_KEYMEDIA")
systemctl daemon-reload; systemctl restart "$unit" 2>/dev/null
fi
install -d -o igate -g igate -m 700 /var/lib/igate/.Validata /var/lib/igate/.Validata/vdkeys
if [ ! -e /var/lib/igate/.Validata/vdkeys/vdkeys ]; then
ln -s "$DEFAULT_KEYMEDIA/vdkeys" /var/lib/igate/.Validata/vdkeys/vdkeys; chown -h igate:igate /var/lib/igate/.Validata/vdkeys/vdkeys
fi
ls "$DEFAULT_KEYMEDIA" >/dev/null 2>&1 # разбудить автомонтирование
KEYMEDIA_UUID="$uuid"
}
KEYMEDIA_UUID=""
# keysvc_restart — служба ключей Валидаты (vdcrysvc, init-скрипт vdmkdev): без неё ключ с носителя не
# загружается — ни для пересборки справочника (как на Астре), ни для подписи ИШ. На свежем сервере простой
# «systemctl restart vdmkdev» мог молча не сработать: юнит systemd появляется только после daemon-reload, а
# init-скрипт понимает лишь start/stop (restart — ошибка). Служба включается и при загрузке сервера.
keysvc_restart(){
systemctl daemon-reload >/dev/null 2>&1
systemctl enable vdmkdev.service >/dev/null 2>&1
if ! systemctl restart vdmkdev.service >/dev/null 2>&1; then
/etc/init.d/vdmkdev stop >/dev/null 2>&1; /etc/init.d/vdmkdev start >/dev/null 2>&1
fi
for _ in 1 2 3 4 5; do pgrep -x vdcrysvc >/dev/null && break; sleep 1; done
pgrep -x vdcrysvc >/dev/null || { /etc/init.d/vdmkdev start >/dev/null 2>&1; sleep 2; }
sleep 1
}
# ---- проверка ключа: подписать проверочный документ профилем ИШ активного контура (как подписывает ИШ) ----
KEYCHECK="skip"; KEYDIAG=""
keysign(){ # 0 — ключ подписал проверочный документ под igate
local kt ko; kt=$(mktemp -d); echo "bj key check" > "$kt/in"; chown -R igate:igate "$kt"
ko=$(timeout -k 5 60 runuser -u igate -- env HOME=/var/lib/igate LD_LIBRARY_PATH="$VDLIB" "$ZPKI" -sign -profile "$PROFILE" -registry -in "$kt/in" -out "$kt/out.p7s" 2>&1)
if [ -s "$kt/out.p7s" ]; then rm -rf "$kt"; KEYCHECK=ok; return 0; fi
rm -rf "$kt"
KEYCHECK="fail: $(printf '%s' "$ko" | grep -iE 'ошибк|результат|error' | head -1 | tr -d '"\\' | head -c 200)"; return 1
}
keydiag(){ # что видит ИШ: смонтирована ли ключевая флешка и какие ключи (*.vdk) ему доступны
local m v
m=$(findmnt -rn -o SOURCE,FSTYPE "$DEFAULT_KEYMEDIA" 2>/dev/null | grep -v autofs | head -1)
v=$(runuser -u igate -- env HOME=/var/lib/igate sh -c 'ls -1L "$HOME"/.Validata/vdkeys/*/*.vdk "$HOME"/.Validata/vdkeys/*.vdk 2>/dev/null' | xargs -r -n1 basename | tr '\n' ' ')
KEYDIAG="флешка в $DEFAULT_KEYMEDIA: ${m:-не смонтирована}; ключи, видимые ИШ: ${v:-нет}; служба ключей Валидаты (vdcrysvc): $(pgrep -x vdcrysvc >/dev/null && echo работает || echo 'не запущена')"
}
if [ "$ACTION" = "restart-ish" ]; then
systemctl restart igate.service >/dev/null 2>&1 || systemctl restart igate-svc >/dev/null 2>&1 || true
echo '{"ok":true,"action":"restart-ish"}'; exit 0
fi
# ---- сертификаты НРД в справочнике активного профиля ИШ ----
# BJ_CHANNEL — начало кода канала ИШ контура (TEST3 | PROD): у тестового и боевого контуров свои ключи и профили
CHAN_PREFIX=$(printf '%s' "${BJ_CHANNEL:-}" | tr -cd 'A-Z0-9')
chan_where(){ if [ -n "$CHAN_PREFIX" ]; then echo "chanel_id in (select id from channels where code like '${CHAN_PREFIX}%')"; else echo "chanel_id is not null"; fi; }
prof_dir(){
PROFILE_NAME=$(su -s /bin/sh postgres -c "psql -d igate -tAc \"select value from parameters where name='cryptography.profile' and value<>'' and $(chan_where) order by chanel_id limit 1\"" 2>/dev/null | tr -d '[:space:]')
[ -n "$PROFILE_NAME" ] || err "профиль ИШ не задан (cryptography.profile)"
PROFDIR="$IGATE_PROFILES/$PROFILE_NAME"
[ -f "$PROFDIR/local.gdbm" ] && [ -f "$PROFDIR/local.pse" ] || err "нет справочника в $PROFDIR"
}
certs_list_json(){
python3 - "$PROFDIR/local.pse" "$PROFDIR/local.gdbm" <<'PY2'
import sys,subprocess,json,re
out=[]; seen=set()
for p in sys.argv[1:]:
store=p.rsplit('.',1)[-1]
try: d=open(p,'rb').read()
except Exception: continue
i=0
while True:
i=d.find(b'\x30\x82',i)
if i<0: break
L=int.from_bytes(d[i+2:i+4],'big'); b=d[i:i+4+L]
if 100<L<20000 and len(b)==4+L:
r=subprocess.run(['openssl','x509','-inform','DER','-noout','-subject','-issuer','-serial','-dates','-nameopt','utf8,sep_comma_plus'],input=b,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL)
if r.returncode==0:
o=r.stdout.decode('utf-8','replace'); g=lambda k:(re.search('^'+k+'=(.*)$',o,re.M) or [None,''])[1].strip()
cn=lambda v:(re.search(r'CN=([^,]+)',v) or [None,''])[1].strip()
sn=g('serial').upper()
if sn in seen: i+=4; continue
seen.add(sn)
out.append({'kind':'x509','store':store,'subject':g('subject'),'subject_cn':cn(g('subject')),'issuer_cn':cn(g('issuer')),'serial':sn,'not_before':g('notBefore'),'not_after':g('notAfter'),'self_signed':g('subject')==g('issuer')})
else:
r=subprocess.run(['openssl','crl','-inform','DER','-noout','-issuer','-nextupdate','-nameopt','utf8,sep_comma_plus'],input=b,stdout=subprocess.PIPE,stderr=subprocess.DEVNULL)
if r.returncode==0:
o=r.stdout.decode('utf-8','replace'); m=re.search(r'CN=([^,\n]+)',o); n=re.search(r'nextUpdate=(.*)',o)
out.append({'kind':'crl','store':store,'issuer_cn':(m.group(1).strip() if m else ''),'not_after':(n.group(1).strip() if n else '')})
i+=4
print(json.dumps(out,ensure_ascii=False))
PY2
}
if [ "$ACTION" = "keycheck" ]; then
prof_dir; PROFILE="$PROFILE_NAME"
RNG=$([ -x /usr/local/sbin/bj-validata-rng ] && /usr/local/sbin/bj-validata-rng status 2>/dev/null | grep -o '"ready":[a-z]*' | cut -d: -f2)
keysvc_up_quiet(){ pgrep -x vdcrysvc >/dev/null || keysvc_restart; }; keysvc_up_quiet
keysign || keydiag
printf '{"ok":true,"profile":"%s","rng":"%s","keycheck":"%s","keydiag":"%s"}\n' "$PROFILE" "${RNG:-}" "$KEYCHECK" "$(printf '%s' "$KEYDIAG" | tr -d '"\\')"
exit 0
fi
if [ "$ACTION" = "certs-list" ]; then
prof_dir
echo "{\"ok\":true,\"profile\":\"$PROFILE_NAME\",\"dir\":\"$PROFDIR\",\"certs\":$(certs_list_json)}"; exit 0
fi
if [ "$ACTION" = "certs-apply" ]; then
prof_dir
case "$STAGING" in /var/lib/bj/nsd-certs-apply/*) [ -d "$STAGING" ] || err "нет папки с сертификатами" ;; *) err "staging вне зоны" ;; esac
TS=$(date +%Y%m%d-%H%M%S); BK="$BACKUP_DIR/certs-$PROFILE_NAME-$TS"; mkdir -p "$BK"
cp -a "$PROFDIR"/local.pse "$PROFDIR"/local.gdbm "$BK"/ || err "бэкап справочника не удался"
restore(){ cp -a "$BK"/local.pse "$BK"/local.gdbm "$PROFDIR"/; chown igate:igate "$PROFDIR"/local.*; chmod 644 "$PROFDIR"/local.*; }
OUT=$(EXTRA_DIR="$STAGING" EXCLUDE_SERIALS="${BJ_EXTRA:-}" "$REBUILD" "$PROFDIR" 2>"$BK/rebuild.log")
echo "$OUT" | grep -q '"ok":true' || { restore; echo "$OUT"; exit 1; }
chown igate:igate "$PROFDIR"/local.*; chmod 644 "$PROFDIR"/local.*
# оракул: подписать файл этим профилем (без GUI). Провал → откат.
T=$(mktemp -d); echo test > "$T/f"; chown -R igate:igate "$T"
SIG=$(su -s /bin/sh igate -c "HOME=/var/lib/igate LD_LIBRARY_PATH=$VDLIB $ZPKI -sign -profile '$PROFILE_NAME' -registry -in '$T/f' -out '$T/s' -sendcert" 2>&1); RC=$?
SIGOK=0; [ $RC -eq 0 ] && [ -s "$T/s" ] && SIGOK=1; echo "$SIG" | grep -qiE 'ошибк|error' && SIGOK=0
printf '%s\n' "$SIG" > "$BK/sign-check.log"; rm -rf "$T"
if [ $SIGOK -ne 1 ]; then restore; systemctl restart igate.service >/dev/null 2>&1; err "проверка подписи после пересборки не прошла — справочник откачен: $(echo "$SIG" | grep -iE 'ошибк|error|Результат' | head -1 | tr -d '"\\')"; fi
systemctl restart igate.service >/dev/null 2>&1 || systemctl restart igate-svc >/dev/null 2>&1 || true
sleep 5; ACT=$(systemctl is-active igate.service 2>/dev/null || echo unknown)
echo "{\"ok\":true,\"profile\":\"$PROFILE_NAME\",\"backup\":\"$BK\",\"igate\":\"$ACT\",\"rebuild\":$OUT,\"certs\":$(certs_list_json)}"; exit 0
fi
# ---- ключ уже на флешке: взять с неё ключ и справочник (только чтение) во временный каталог — дальше обычная
# установка (тот же носитель, то же имя профиля). Раскладка bj: <профиль>/key.reg + <профиль>/spr<N>/local.{pse,gdbm}
# + vdkeys/*.vdk; другая — ключ (*.vdk) и справочник (local.gdbm + local.pse) где угодно на носителе.
if [ "$ACTION" = "stage-media" ]; then
MNT="$TARGET_MNT"; RO_MNT=""
if [ -z "$MNT" ]; then
[ -n "$TARGET_DEV" ] || err "не выбран носитель"
RO_MNT="/run/bj-keymedia-ro-$$"; mkdir -p "$RO_MNT"
mount -o ro "$TARGET_DEV" "$RO_MNT" || { rmdir "$RO_MNT"; err "не смонтировать $TARGET_DEV"; }
MNT="$RO_MNT"
fi
done_ro(){ [ -n "$RO_MNT" ] && { umount "$RO_MNT" 2>/dev/null; rmdir "$RO_MNT" 2>/dev/null; }; }
VDKF=$(find "$MNT" -maxdepth 4 -iname '*.vdk' 2>/dev/null | sort | head -1)
GD=$(find "$MNT" -maxdepth 5 -iname 'local.gdbm' 2>/dev/null | sort | head -1)
[ -n "$VDKF" ] || { done_ro; err "на носителе нет ключа (файла *.vdk)"; }
[ -n "$GD" ] && [ -f "$(dirname "$GD")/local.pse" ] || { done_ro; err "на носителе есть ключ, но нет справочника сертификатов (local.pse и local.gdbm) — загрузите архив ключа от НРД: справочник есть в нём"; }
SPR=$(dirname "$GD"); PROFD=$(dirname "$SPR")
ID="media-$(date -u +%Y%m%d%H%M%S)"; ST="/var/lib/bj/media/iso/$ID"; mkdir -p "$ST/vdkeys"
if [ "$PROFD" != "$MNT" ] && [ -f "$PROFD/key.reg" ]; then
cp -a "$PROFD"/. "$ST"/ 2>/dev/null; PNAME=$(basename "$PROFD") # профиль bj: key.reg + spr<N>/
else
cp -a "$SPR" "$ST"/ 2>/dev/null; PNAME="" # справочник без профиля bj — имя по номеру
fi
cp -f "$VDKF" "$ST/vdkeys/"
rm -rf "$ST/vdkeys/vdkeys" 2>/dev/null
chown -R bj:bj "$ST" 2>/dev/null
done_ro
case "$PNAME" in *[!A-Za-z0-9_-]*) PNAME="" ;; esac
printf '{"ok":true,"staging":"%s","vdk":"%s","profile":"%s","spr":"%s"}\n' "$ID" "$(basename "$VDKF")" "$PNAME" "$(basename "$SPR")"
exit 0
fi
# use-media — ключ на флешке уже подготовлен (например, флешка с другого сервера bj): использовать как есть.
# На флешку НИЧЕГО не пишется: справочник пересобирается в копии на диске (staging от stage-media), из неё —
# профиль ИШ; ключ Валидата читает с флешки сама.
NOWRITE=0
if [ "$ACTION" = "use-media" ]; then NOWRITE=1; ACTION=install; fi
[ -n "$STAGING" ] && [ -d "$STAGING" ] || err "staging не найден"
case "$STAGING" in /var/lib/bj/media/iso/*) : ;; *) err "staging вне зоны" ;; esac
VDK=$(find "$STAGING" -iname '*.vdk' | head -1)
GDBM=$(find "$STAGING" -iname '*.gdbm' | head -1)
[ -n "$VDK" ] || err "в архиве нет .vdk"
VDK_NAME=$(basename "$VDK")
SPRDIR=""; [ -n "$GDBM" ] && SPRDIR=$(dirname "$GDBM") # .../spr1046
SPRNAME=""; [ -n "$SPRDIR" ] && SPRNAME=$(basename "$SPRDIR") # spr1046
USERNUM=$(echo "$SPRNAME" | grep -oE '[0-9]+' | head -1) # 1046
if [ "$ACTION" = "inspect" ]; then
printf '{"ok":true,"vdk":"%s","profile_dir":"%s","has_gdbm":%s,"has_crl":%s}\n' \
"$VDK_NAME" "$SPRNAME" "$([ -n "$GDBM" ]&&echo true||echo false)" \
"$([ -n "$(find "$STAGING" -iname '*.crl' | head -1)" ]&&echo true||echo false)"
exit 0
fi
[ "$ACTION" = "install" ] || err "неизвестное действие"
# датчик случайных чисел Валидаты: пока он не инициализирован (окно «двигайте мышью» на сервере без экрана никто
# не видит), ключ в тихом режиме не работает — ни пересборка справочника, ни подпись ИШ. Проверяем до записи на флешку.
if [ -x /usr/local/sbin/bj-validata-rng ] && /usr/local/sbin/bj-validata-rng status 2>/dev/null | grep -q '"ready":false'; then
err "датчик случайных чисел Валидаты не инициализирован — сначала пройдите «Датчик случайных чисел» на этом шаге (поводить мышью в поле)"
fi
# ---- целевая точка монтирования (флешка) ----
TMP_MNT=""; UMOUNT_AFTER=0
if [ "$NOWRITE" = 1 ]; then
[ -n "$PROFILE" ] || PROFILE="PrUser${USERNUM:-_$(date -u +%Y%m%d-%H%M%S)}"
case "$PROFILE" in *[!A-Za-z0-9_-]*) err "недопустимое имя профиля" ;; esac
BACKUP=""; TARGET_MNT="флешка не менялась"
FLASH_SPR="$SPRDIR"; CRL_SRC="$STAGING" # копия на диске
finish(){ :; }
else
[ -z "$TARGET_MNT" ] && [ -z "$TARGET_DEV" ] && TARGET_MNT="$DEFAULT_KEYMEDIA"
if [ -z "$TARGET_MNT" ] && [ -n "$TARGET_DEV" ]; then
TMP_MNT="/run/bj-keymedia-$$"; mkdir -p "$TMP_MNT"
mount "$TARGET_DEV" "$TMP_MNT" || err "не смонтировать $TARGET_DEV"
TARGET_MNT="$TMP_MNT"; UMOUNT_AFTER=1
fi
[ -d "$TARGET_MNT" ] || err "целевой носитель недоступен"
TS=$(date -u +%Y%m%d-%H%M%S); mkdir -p "$BACKUP_DIR"
BACKUP="$BACKUP_DIR/keymedia-$TS.tar.gz"
tar czf "$BACKUP" -C "$TARGET_MNT" . 2>/dev/null || err "бэкап не удался"
REMOUNTED=0
mount | grep -q " $TARGET_MNT .*[(,]ro[,)]" && { mount -o remount,rw "$TARGET_MNT" || err "remount rw не удался"; REMOUNTED=1; }
finish(){ sync; [ "$REMOUNTED" = 1 ] && mount -o remount,ro "$TARGET_MNT" 2>/dev/null; [ "$UMOUNT_AFTER" = 1 ] && { umount "$TMP_MNT" 2>/dev/null; rmdir "$TMP_MNT" 2>/dev/null; }; }
trap finish EXIT
# имя профиля: задано пользователем, иначе PrUser<N>
[ -n "$PROFILE" ] || PROFILE="PrUser${USERNUM:-_$TS}"
case "$PROFILE" in *[!A-Za-z0-9_-]*) err "недопустимое имя профиля" ;; esac
DEST="$TARGET_MNT/$PROFILE"
rm -rf "$DEST"; mkdir -p "$DEST" "$TARGET_MNT/vdkeys"
cp -rf "$STAGING"/. "$DEST"/ 2>/dev/null # key.reg + spr<N>/ + vdkeys/
cp -f "$VDK" "$TARGET_MNT/vdkeys/$VDK_NAME"
sync
FLASH_SPR="$DEST/$SPRNAME"; CRL_SRC="$DEST"
# записано — временное монтирование снять: дальше флешка подключается постоянно (только чтение)
[ "$UMOUNT_AFTER" = 1 ] && { umount "$TMP_MNT" 2>/dev/null; rmdir "$TMP_MNT" 2>/dev/null; UMOUNT_AFTER=0; }
fi
# ключевая флешка — постоянно для ИШ (справочник подписывается ключом с неё)
[ -n "$TARGET_DEV" ] && attach_keymedia "$TARGET_DEV"
# ---- зарегистрировать ключевой носитель (vdmkdev читает ключ с флешки) ----
keysvc_restart
sleep 2
# ---- ПЕРЕСОБРАТЬ совместимый справочник на флешке (архивный gdbm malformed) ----
REBUILD_RES="skip"
if [ -n "$SPRNAME" ] && [ -x "$REBUILD" ] && [ -f "$FLASH_SPR/local.pse" ]; then
chmod o+x /var/lib/bj 2>/dev/null; chmod -R a+rX "$FLASH_SPR" 2>/dev/null # igate (crestor) должен читать staging под /var/lib/bj
RB=$("$REBUILD" "$FLASH_SPR" 2>&1)
if echo "$RB" | grep -q '"ok":true'; then REBUILD_RES="ok"; else e=$(echo "$RB" | grep -o '"error":"[^"]*"' | head -1 | sed 's/^"error":"//;s/"$//'); [ -n "$e" ] || e=$(echo "$RB"|tr -d '\n"'|head -c160); REBUILD_RES="fail: $e"; fi
sync
fi
# флешка уже подготовлена (use-media): её справочник совместим — пересобран при первой установке; пересборка
# не удалась (ключ подписи справочника не загрузился) — взять его как есть. Совместимый gdbm начинается с cf9a5713,
# архивный НРД — с ce9a5713 (его без пересборки ИШ не читает).
if [ "$NOWRITE" = 1 ] && [ "$REBUILD_RES" != ok ] && [ -f "$FLASH_SPR/local.gdbm" ] && \
[ "$(head -c4 "$FLASH_SPR/local.gdbm" | od -An -tx1 | tr -d ' \n')" = cf9a5713 ]; then
REBUILD_RES="as-is"
fi
# ---- сформировать профиль для ИШ (стораны на диске, ключ остаётся на флешке) ----
IGATE_RES="skip"
if { [ "$REBUILD_RES" = "ok" ] || [ "$REBUILD_RES" = "as-is" ]; } && [ -d "$(dirname "$IGATE_PROFILES")" ]; then
IGP="$IGATE_PROFILES/$PROFILE"
rm -rf "$IGP"; mkdir -p "$IGP"
cp -f "$FLASH_SPR/local.pse" "$FLASH_SPR/local.gdbm" "$IGP"/ 2>/dev/null
# CRL-файлы рядом (если есть) — для офлайн-валидации
find "$CRL_SRC" -iname '*.crl' -exec cp -f {} "$IGP"/ \; 2>/dev/null
chown -R igate:igate "$IGP" 2>/dev/null
chmod 644 "$IGP"/local.* 2>/dev/null
# зарегистрировать профиль в zpki.ini ИШ (идемпотентно)
if [ -f "$IGATE_INI" ]; then
python3 - "$IGATE_INI" "$PROFILE" "$IGP" <<'PY' && IGATE_RES="ok" || IGATE_RES="ini-fail"
import sys
ini,name,igp=sys.argv[1],sys.argv[2],sys.argv[3]
L=open(ini).read().split("\n")
# убрать прежний блок профиля с тем же именем
out=[]; skip=False
i=0
while i<len(L):
l=L[i]
if l.strip().startswith("[Profile_") :
# прочитать имя блока
blk=[l]; j=i+1
while j<len(L) and not L[j].strip().startswith("["):
blk.append(L[j]); j+=1
if any(b.strip()==f"Name = {name}" for b in blk):
i=j; continue # выкинуть старый одноимённый
out+=blk; i=j; continue
out.append(l); i+=1
L=out
# найти max индекс Profile_N
idx=-1
for l in L:
s=l.strip()
if s.startswith("[Profile_") and s.endswith("]"):
try: idx=max(idx,int(s[len("[Profile_"):-1]))
except: pass
n=idx+1
L+=[f"[Profile_{n}]",f"Name = {name}","Count = 2",
f"store_0 = pse://signed//{igp}/local.pse",
f"store_1 = file://{igp}/local.gdbm"]
# Count в секции [Profile] — число блоков [Profile_N] (раньше прибавлялась единица при каждой установке,
# и после переустановки того же профиля Count расходился с числом профилей)
# блоки — подряд с нуля: Валидата читает Profile_0…Profile_{Count-1}, дыра после удалённого блока прятала профиль
cnt=0
for k,l in enumerate(L):
if l.strip().startswith("[Profile_") and l.strip().endswith("]"):
L[k]=f"[Profile_{cnt}]"; cnt+=1
firstp=next((k for k,l in enumerate(L) if l.strip().startswith("[Profile_")), len(L))
for k in range(firstp-1,-1,-1):
if L[k].strip().startswith("Count = "):
L[k]=f"Count = {cnt}"
break
open(ini,"w").write("\n".join(L))
PY
fi
# подключить профиль к ИШ: cryptography.profile в БД igate (канал 33)
nchan=1
if [ "$IGATE_RES" = "ok" ] && [ -n "$CHAN_PREFIX" ]; then
nchan=$(su -s /bin/sh postgres -c "psql -d igate -tAc \"select count(*) from channels where code like '${CHAN_PREFIX}%'\"" 2>/dev/null | tr -d '[:space:]')
fi
if [ "$IGATE_RES" = "ok" ] && [ "${nchan:-0}" = 0 ]; then
# канала этого контура в ИШ ещё нет: профиль запоминаем — bj-igate-admin подключит его при настройке канала
printf '%s\n' "$PROFILE" > "/var/lib/igate/.bj-profile-$CHAN_PREFIX"; chown igate:igate "/var/lib/igate/.bj-profile-$CHAN_PREFIX" 2>/dev/null
IGATE_RES="pending-channel"
elif [ "$IGATE_RES" = "ok" ]; then
# только канал контура этого ключа (BJ_CHANNEL); старый bj без контура — все каналы, как раньше. Канал с ключом включаем.
if su -s /bin/sh postgres -c "psql -d igate -tAc \"update parameters set value='$PROFILE' where name='cryptography.profile' and $(chan_where); update channels set enable=true where id in (select chanel_id from parameters where name='cryptography.profile' and value='$PROFILE'); update parameters set value='True' where name='enable' and chanel_id in (select chanel_id from parameters where name='cryptography.profile' and value='$PROFILE')\"" >/dev/null 2>&1; then
IGATE_RES="connected"
fi
fi
fi
# ---- проверка: ключ подписывает под igate (как будет подписывать ИШ) — проверочный документ профилем ----
if [ "$IGATE_RES" = connected ] || [ "$IGATE_RES" = pending-channel ]; then
if ! keysign; then
if [ -n "$TARGET_DEV" ] && [ -b "$TARGET_DEV" ] && [ -z "$(findmnt -rn -o FSTYPE "$DEFAULT_KEYMEDIA" 2>/dev/null | grep -v autofs)" ]; then
# автомонтирование не сработало — смонтировать флешку напрямую (только чтение, под igate)
FT=$(blkid -s TYPE -o value "$TARGET_DEV" 2>/dev/null)
systemctl stop "$(systemd-escape -p --suffix=automount "$DEFAULT_KEYMEDIA")" 2>/dev/null; umount "$DEFAULT_KEYMEDIA" 2>/dev/null
case "$FT" in vfat|exfat|ntfs*) MO="ro,uid=$(id -u igate),gid=$(id -g igate),fmask=0133,dmask=0022" ;; *) MO="ro" ;; esac
mkdir -p "$DEFAULT_KEYMEDIA"; mount -o "$MO" "$TARGET_DEV" "$DEFAULT_KEYMEDIA" 2>/dev/null
fi
keysvc_restart # служба ключей заново видит носитель
keysign
fi
[ "$KEYCHECK" = ok ] || keydiag
fi
finish; trap - EXIT
printf '{"ok":true,"profile":"%s","spr":"%s","vdk":"%s","backup":"%s","target":"%s","rebuild":"%s","igate":"%s","igate_profile_dir":"%s","keymedia":"%s","nowrite":%s,"keycheck":"%s","keydiag":"%s"}\n' \
"$PROFILE" "$SPRNAME" "$VDK_NAME" "$BACKUP" "$TARGET_MNT" "$REBUILD_RES" "$IGATE_RES" "$IGATE_PROFILES/$PROFILE" "$KEYMEDIA_UUID" "$([ "$NOWRITE" = 1 ] && echo true || echo false)" "$KEYCHECK" "$(printf '%s' "$KEYDIAG" | tr -d '"\\')"