Files
Bridge-and-Join-s/deploy/linux/sbin/bj-igate-admin
T
zuevav a53b9f9e9f Автоматический режим M2M по умолчанию; контуры НРД карточками; адрес папок шлюза для проводника; Zabbix — в «Диагностике»
- M2M: «bj сам — автоматически» — режим по умолчанию (где ни разу не выбирали; момент включения записывается, берутся переводы не старше суток до него); «Оператор» — запасной; явный выбор не меняется.
- Контуры НРД: TEST3 и боевой — карточки с состоянием (Активный / Готов / Нужен ключ / Канала нет); «Сделать активным» видна всегда, без ключа неактивна с пояснением; «Установить ключ», «Обновить канал».
- Папки шлюза по сети: после открытия — адреса для проводника (\\IP\igate_exchange и по имени сервера, боевой — igate_exchange_prod) с кнопкой «Скопировать»; bj-igate-admin share status отдаёт ip, host, shares.
- Zabbix перенесён из «Оповещений» в «Диагностику».

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
2026-10-07 08:29:25 +03:00

372 lines
29 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-igate-admin — установка и настройка интеграционного шлюза НРД (ИШ, igate) без графики.
# Вызывает root-демон bj-keymedia по запросу из bj (мастер, шаг «Шлюз НРД»); параметры — JSON в BJ_EXTRA.
# status — JSON: версия, служба, движок, каналы (контуры)
# install — установить или обновить ИШ из дистрибутива НРД (в фоне; ход — /var/lib/bj/igate-install-status.json)
# BJ_EXTRA: {"file": "<путь к igate-*.zip|.deb>"}, {"version": "107.0.930"} — скачать с nsd.ru,
# или {"bundled": "1"} — дистрибутив с образа (/var/lib/bj/igate-dist), без интернета
# install-now — сама установка (запускается через systemd-run)
# contour — создать или обновить канал контура и перезапустить ИШ
# BJ_EXTRA: {"contour": "test3|prod", "crypto": "gost|rsa", "deponent": "MC…", "profile": "<профиль Валидаты>"}
#
# ИШ 107 без графики (проверено 19.08.2026, см. deploy/ish/upgrade-ish-v107.sh): пароли — в gnome-keyring
# (разблокируется паролем из файла), движок — под xvfb-run, сессионная шина — dbus-run-session; настройки
# ИШ — в PostgreSQL (channels + parameters), канал ищется по составному коду <канал><депозитарный код>.
set -uo pipefail
IGATE_DIR=/opt/nsd/igate
SVC_USER=igate
HOME_DIR=/var/lib/igate
DB=${BJ_IGATE_DB:-igate} # переопределение — только для проверки на копии базы
DATA_DIR="$HOME_DIR/.local/share/NSD Integration Gateway"
WRAPPER=/usr/local/bin/igate-run.sh
UNIT=/etc/systemd/system/igate.service
STATUS=/var/lib/bj/igate-install-status.json
PARAMS=/run/bj-igate-install.json
WEB=http://localhost:8090
cmd=${1:-}
j() { python3 -c "import json,os,sys;print(json.loads(os.environ.get('BJ_EXTRA') or '{}').get(sys.argv[1],''))" "$1"; }
q() { python3 -c "import json,sys;print(json.dumps(sys.argv[1],ensure_ascii=False))" "$1"; }
ok() { echo "{\"ok\":true,\"output\":$(q "$1")}"; }
err() { echo "{\"ok\":false,\"error\":$(q "$1")}"; exit 0; }
psqlq() { (cd /tmp && runuser -u postgres -- psql -d "$DB" -v ON_ERROR_STOP=1 -qtA "$@"); }
# Адреса и отпечатки УЦ контуров НРД — «Обновление ИШ до версии 107» (НРД), разделы 1.1 и 1.2.
contour_params() { # contour crypto → CODE|NAME|ENDPOINT|FINGERPRINT|TYPE
case "$1-$2" in
test3-gost) echo "TEST3|TEST3 GOST|https://gost-t3.nsd.ru/onyx-ms|EB:C2:D6:CA:73:AC:22:69:44:09:07:ED:6A:58:2C:4E:55:29:5D:00:F8:0F:1D:1E:70:A0:85:25:32:0D:DC:BF|GOST" ;;
test3-rsa) echo "TEST3|TEST3 RSA|https://rsa-t3.nsd.ru/onyx-ms|5D:75:60:87:AA:1E:8D:E2:FA:F8:AF:DB:B0:43:FF:BD:E3:3C:B7:50|RSA" ;;
prod-gost) echo "PROD|PROD GOST|https://edog.nsd.ru/onyx|58:C6:77:5B:20:29:AE:CA:C0:5A:1C:6D:E2:72:4B:C4:C7:CC:45:3B:EB:07:E5:D1:1E:55:0C:FB:EC:1B:D4:8A|GOST" ;;
prod-rsa) echo "PROD|PROD RSA|https://edor.nsd.ru/onyx|C2:E1:6F:FB:5F:EF:2C:72:C4:F6:4A:07:24:87:B6:3F:ED:8D:DD:E9|RSA" ;;
*) return 1 ;;
esac
}
exchange_dir() { case "$1" in prod) echo "$HOME_DIR/exchange-prod" ;; *) echo "$HOME_DIR/exchange" ;; esac; }
igate_version() {
python3 - "$IGATE_DIR/igate-svc/igate-svc.deps.json" "$IGATE_DIR/igate/igate.deps.json" <<'PY' 2>/dev/null
import json,sys
for p in sys.argv[1:]:
try:
libs=json.load(open(p))['libraries']
for k in libs:
n,_,v=k.partition('/')
if n.lower() in ('igate','igate-svc','igate.svc'):
print(v); sys.exit(0)
except Exception: pass
PY
}
engine_state() { curl -s -m 4 "$WEB/api/admin/engine/state" 2>/dev/null | tr -d '"'; }
wait_engine() { # секунд
for _ in $(seq 1 $(( $1 / 3 ))); do
[ "$(engine_state)" = Running ] && return 0
sleep 3
done
return 1
}
run_as_igate_keyring() { # команда в сессии igate с разблокированным keyring (для igate-cli / igate-mig)
local uid; uid=$(id -u "$SVC_USER")
runuser -u "$SVC_USER" -- env HOME="$HOME_DIR" XDG_DATA_HOME="$HOME_DIR/.local/share" XDG_RUNTIME_DIR="/run/user/$uid" GIO_USE_VFS=local \
dbus-run-session -- bash -c 'eval "$(printf "%s\n" "$(cat "$HOME/.keyring-pass")" | gnome-keyring-daemon --replace --unlock --components=secrets,pkcs11 2>/dev/null)"; export GNOME_KEYRING_CONTROL; '"$1"
}
case "$cmd" in
status)
inst=false; [ -x "$IGATE_DIR/igate-svc/igate-svc" ] && inst=true
svc=$(systemctl is-active igate 2>/dev/null); st=$(engine_state)
chans="[]"
if runuser -u postgres -- psql -lqt 2>/dev/null | cut -d'|' -f1 | grep -qw "$DB"; then
chans=$(psqlq -c "select coalesce(json_agg(json_build_object('id',c.id,'code',c.code,'enabled',c.enable,
'endpoint',(select value from parameters p where p.chanel_id=c.id and p.name='wslEndpoint'),
'profile',(select value from parameters p where p.chanel_id=c.id and p.name='cryptography.profile'),
'crypto',(select value from parameters p where p.chanel_id=c.id and p.name='cryptography.type'),
'deponent',(select value from parameters p where p.chanel_id=c.id and p.name='repositoryCode'),
'dir',(select value from parameters p where p.chanel_id=c.id and p.name='dir')) order by c.id),'[]') from channels c" 2>/dev/null || echo "[]")
fi
echo "{\"ok\":true,\"installed\":$inst,\"version\":$(q "$(igate_version)"),\"service\":$(q "$svc"),\"engine\":$(q "$st"),\"channels\":${chans:-[]}}" ;;
install)
if pgrep -f "bj-igate-admin install-now" >/dev/null; then
# идёт — не мешать; но если ход не менялся 15 минут, установка зависла: снять её и начать заново
age=$(( $(date +%s) - $(stat -c %Y "$STATUS" 2>/dev/null || echo 0) ))
[ "$age" -gt 900 ] || err "установка ИШ уже идёт"
systemctl stop 'bj-igate-install-*' >/dev/null 2>&1; pkill -f "bj-igate-admin install-now" 2>/dev/null; sleep 2
fi
file=$(j file); ver=$(j version)
[ "$(j bundled)" = 1 ] && [ -z "$file$ver" ] && { file=$(ls /var/lib/bj/igate-dist/igate-*-amd64.zip 2>/dev/null | sort -V | tail -1); [ -n "$file" ] || err "в образе нет дистрибутива ИШ"; BJ_EXTRA="{\"file\":\"$file\"}"; }
[ -n "$file$ver" ] || err "не указан дистрибутив ИШ (файл или версия)"
[ -z "$file" ] || [ -f "$file" ] || err "файл дистрибутива не найден: $file"
[[ -z "$ver" || "$ver" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || err "версия ИШ — вида 107.0.930"
(umask 077; printf '%s' "${BJ_EXTRA:-}" > "$PARAMS")
echo '{"stage":"queued","msg":"установка запущена"}' > "$STATUS"; chown bj:bj "$STATUS" 2>/dev/null
# не дольше 40 минут: что бы ни зависло, служба установки будет остановлена (ход покажет «прервалась»)
systemd-run --unit="bj-igate-install-$(date +%s)" --collect -p RuntimeMaxSec=2400 /usr/local/sbin/bj-igate-admin install-now >/dev/null 2>&1 || err "не удалось запустить установку"
ok "установка ИШ запущена" ;;
install-now)
BJ_EXTRA=$(cat "$PARAMS" 2>/dev/null); export BJ_EXTRA="${BJ_EXTRA:-{\}}"; rm -f "$PARAMS"
LOG=/var/log/bj-igate-install.log; : > "$LOG"
st() { python3 - "$STATUS" "$1" "$2" "$LOG" <<'PY'
import json,sys,datetime
tail=open(sys.argv[4],errors='replace').read()[-1500:]
json.dump({"at":datetime.datetime.now().astimezone().isoformat(timespec="seconds"),"stage":sys.argv[2],"msg":sys.argv[3],"log":tail},open(sys.argv[1],"w"),ensure_ascii=False)
PY
chown bj:bj "$STATUS" 2>/dev/null; echo "== $2" >> "$LOG"; }
die() { st error "$1"; exit 1; }
exec >>"$LOG" 2>&1
W=$(mktemp -d /var/tmp/bj-igate.XXXXXX); trap 'rm -rf "$W"' EXIT
st deps "системные пакеты для работы ИШ без графики"
need=""
for p in gnome-keyring dbus xvfb xauth libsecret-tools unzip curl; do dpkg -s "$p" >/dev/null 2>&1 || need="$need $p"; done
if [ -n "$need" ]; then
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends $need || \
{ DEBIAN_FRONTEND=noninteractive apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends $need; } || die "не удалось установить пакеты:$need"
fi
st dist "дистрибутив ИШ"
file=$(j file); ver=$(j version)
if [ -z "$file" ]; then
v=$(printf '%s' "$ver" | tr . -)
url="https://www.nsd.ru/media/integratsionnyj-shlyuz/$v/igate-$v-amd64.zip"
echo "скачивание $url"
curl -fsSL -m 600 -A 'Mozilla/5.0 (X11; Linux x86_64) bj-installer' -H 'Accept: */*' -o "$W/igate.zip" "$url" || die "не удалось скачать дистрибутив ИШ $ver с сайта НРД ($url)"
file="$W/igate.zip"
fi
case "$file" in
*.deb) deb="$file" ;;
*) unzip -o -q "$file" -d "$W/x" || die "архив дистрибутива не распаковывается"; deb=$(find "$W/x" -name 'igate_*.deb' | head -1) ;;
esac
[ -n "$deb" ] && [ -f "$deb" ] || die "в дистрибутиве нет пакета igate_*.deb"
echo "пакет $(basename "$deb"), версия $(dpkg-deb -f "$deb" Version), sha256 $(sha256sum "$deb" | cut -c1-16)…"
st user "пользователь igate и хранилище паролей"
id "$SVC_USER" >/dev/null 2>&1 || useradd --system --home-dir "$HOME_DIR" --create-home --shell /usr/sbin/nologin "$SVC_USER" || die "не создать пользователя igate"
install -d -o "$SVC_USER" -g "$SVC_USER" -m 755 "$HOME_DIR" "$HOME_DIR/.local" "$HOME_DIR/.local/share" "$DATA_DIR" "$HOME_DIR/profiles" "$HOME_DIR/.Validata" "$(exchange_dir test3)"
uid=$(id -u "$SVC_USER")
loginctl enable-linger "$SVC_USER" >/dev/null 2>&1 || true
[ -d "/run/user/$uid" ] || { install -d -o "$SVC_USER" -g "$SVC_USER" -m 700 "/run/user/$uid"; }
if [ ! -s "$HOME_DIR/.keyring-pass" ]; then
(umask 077; head -c 24 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 20 > "$HOME_DIR/.keyring-pass")
chown "$SVC_USER:$SVC_USER" "$HOME_DIR/.keyring-pass"; chmod 600 "$HOME_DIR/.keyring-pass"
fi
[ -f "$HOME_DIR/.Validata/zpki.ini" ] || { printf '[Profile]\nCount = 0\n' > "$HOME_DIR/.Validata/zpki.ini"; chown "$SVC_USER:$SVC_USER" "$HOME_DIR/.Validata/zpki.ini"; }
st db "база ИШ в PostgreSQL"
runuser -u postgres -- psql -tAc "select 1 from pg_roles where rolname='$SVC_USER'" | grep -q 1 || runuser -u postgres -- psql -qc "create role $SVC_USER login" || die "не создать роль igate"
runuser -u postgres -- psql -lqt | cut -d'|' -f1 | grep -qw "$DB" || runuser -u postgres -- createdb -O "$SVC_USER" "$DB" || die "не создать базу igate"
hba=$(runuser -u postgres -- psql -tAc "show hba_file")
if [ -f "$hba" ] && ! grep -q '^# bj-igate-trust' "$hba"; then
# ИШ хранит пароль БД зашифрованным — локальному igate доступ без пароля (только своя база, только с этого сервера)
cp -a "$hba" "$hba.bak-bj-igate"
{ printf '# bj-igate-trust\nlocal %s %s trust\nhost %s %s 127.0.0.1/32 trust\nhost %s %s ::1/128 trust\n' "$DB" "$SVC_USER" "$DB" "$SVC_USER" "$DB" "$SVC_USER"; cat "$hba.bak-bj-igate"; } > "$hba"
runuser -u postgres -- psql -qtAc "select pg_reload_conf()" >/dev/null
fi
st install "установка пакета ИШ"
systemctl stop igate >/dev/null 2>&1 || true
pkill -9 -u "$SVC_USER" -f igate-svc 2>/dev/null; pkill -9 -u "$SVC_USER" -f Xvfb 2>/dev/null; rm -f /tmp/CoreFxPipe_NSD.IGate.*
if [ -d "$IGATE_DIR" ]; then
BK=/var/backups/ish-$(date +%Y%m%d-%H%M%S); mkdir -p "$BK"
tar czf "$BK/opt-nsd-igate.tar.gz" -C "$(dirname "$IGATE_DIR")" "$(basename "$IGATE_DIR")" 2>/dev/null
runuser -u postgres -- pg_dump -Fc "$DB" > "$BK/igate-db.dump" 2>/dev/null
echo "$BK" > /var/backups/ish-last-backup.txt; echo "резервная копия прежнего ИШ: $BK"
fi
echo "igate igate/service boolean false" | debconf-set-selections
echo "igate igate/shortcut boolean false" | debconf-set-selections
DEBIAN_FRONTEND=noninteractive dpkg -i --force-confnew "$deb" || { DEBIAN_FRONTEND=noninteractive apt-get -f install -y -qq || die "пакет ИШ не установился (dpkg)"; }
[ -x "$IGATE_DIR/igate-svc/igate-svc" ] || die "после установки нет $IGATE_DIR/igate-svc/igate-svc"
st config "настройка ИШ для работы без графики"
cfg='{"db":{"databaseProvider":"Postgresql","sqlite":{"path":"'"$DATA_DIR"'/igate.db.dat"},"postgresql":{"host":"127.0.0.1","port":"5432","database":"'"$DB"'","userName":"'"$SVC_USER"'","extensions":null}},"migrationCompleted":true}'
for f in "$DATA_DIR/config.json" "$HOME_DIR/config.json"; do
[ -s "$f" ] || { printf '%s\n' "$cfg" > "$f"; chown "$SVC_USER:$SVC_USER" "$f"; chmod 600 "$f"; }
done
cat > "$WRAPPER" <<EOF
#!/usr/bin/env bash
# bj: запуск ИШ без графики — gnome-keyring (пароли ИШ 107) + виртуальный дисплей; из ExecStart через dbus-run-session.
export HOME=$HOME_DIR XDG_DATA_HOME="$HOME_DIR/.local/share" XDG_RUNTIME_DIR="/run/user/\$(id -u)" GIO_USE_VFS=local
eval "\$(printf '%s\n' "\$(cat "\$HOME/.keyring-pass")" | gnome-keyring-daemon --replace --unlock --components=secrets,pkcs11 2>/dev/null)"
export GNOME_KEYRING_CONTROL SSH_AUTH_SOCK
cd "\$HOME"
exec xvfb-run -a $IGATE_DIR/igate-svc/igate-svc
EOF
chmod 755 "$WRAPPER"
rm -f /etc/systemd/system/igate.service.d/override.conf
cat > "$UNIT" <<EOF
[Unit]
Description=Интеграционный шлюз НРД (ИШ)
After=network-online.target postgresql.service
Wants=network-online.target
StartLimitIntervalSec=300
StartLimitBurst=8
[Service]
User=$SVC_USER
WorkingDirectory=$HOME_DIR
# самоочистка перед стартом: осиротевшие сокеты, keyring, X-локи — иначе перезапуски срываются
# каталог сеанса igate (XDG_RUNTIME_DIR): обычно его создаёт systemd-logind (linger), но без него keyring не стартует
ExecStartPre=+/usr/bin/install -d -o $SVC_USER -g $SVC_USER -m 700 /run/user/$uid
ExecStartPre=-/bin/bash -c 'rm -f /tmp/CoreFxPipe_NSD.IGate.* $HOME_DIR/.local/share/keyrings/login.keyring; find /tmp -maxdepth 1 -name ".X9*-lock" -delete 2>/dev/null; true'
ExecStart=/usr/bin/dbus-run-session -- $WRAPPER
Restart=on-failure
RestartSec=8
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
st schema "база ИШ: создание схемы"
if ! psqlq -c "select to_regclass('public.parameters') is not null" | grep -q t; then
# -k: .NET-программа может не завершиться по SIGTERM — через 15 с принудительно (иначе ждали бы бесконечно)
timeout -k 15 150 bash -c "$(declare -f run_as_igate_keyring); SVC_USER=$SVC_USER HOME_DIR=$HOME_DIR run_as_igate_keyring 'timeout -k 15 90 $IGATE_DIR/igate-cli/igate-cli --data $HOME_DIR </dev/null >/dev/null 2>&1; true'" || true
psqlq -c "select to_regclass('public.parameters') is not null" | grep -q t || die "ИШ не создал схему базы (igate-cli). Журнал: $LOG"
fi
# Общие параметры ИШ. Обязательные для работы без графики и для bj (движок при старте, Web API на
# localhost:8090 без авторизации) — выставляем всегда; остальные — только если их ещё нет: настройки ИШ,
# который у клиента уже работает (очистка, хранение, прокси), не трогаем. Значения по умолчанию — эталон GUI.
psqlq <<'SQL' || die "не записаны параметры ИШ"
CREATE TEMP TABLE must(n text, v text);
INSERT INTO must VALUES ('runEngineOnStartApp','True'),('server.useServer','True'),('server.host','localhost'),
('server.port','8090'),('server.scheme','Http'),('server.authentication.enable','False');
CREATE TEMP TABLE dflt(n text, v text);
INSERT INTO dflt VALUES ('server.authentication.userName',''),('server.authentication.password',''),('server.certificate.storage','File'),
('server.certificate.store.location','CurrentUser'),('server.certificate.store.name','My'),('server.certificate.file.path',''),
('server.certificate.file.password',''),('wsl.httpsMode','Auto'),('wsl.maxConnsPerServer','4'),('wsl.proxy.mode','None'),
('wsl.proxy.address',''),('wsl.proxy.port','0'),('wsl.proxy.username',''),('wsl.proxy.password',''),('enableDbLogging','False'),
('cleanAutomatically','False'),('cleanAtTime','00:30:00'),('cleanWhenLarger','1024'),('cleanVacuum','False'),('storePeriod','15'),
('archiveAutomatically','False');
UPDATE parameters p SET value = m.v FROM must m WHERE p.chanel_id IS NULL AND p.name = m.n;
INSERT INTO parameters(name, value, chanel_id)
SELECT x.n, x.v, NULL FROM (SELECT * FROM must UNION ALL SELECT * FROM dflt) x
WHERE NOT EXISTS (SELECT 1 FROM parameters p WHERE p.chanel_id IS NULL AND p.name = x.n);
SQL
st start "запуск ИШ"
systemctl enable igate >/dev/null 2>&1
systemctl restart igate || die "служба igate не запустилась"
wait_engine 120 || die "ИШ запущен, но движок не перешёл в Running за 2 минуты. Журнал ИШ: $DATA_DIR/logs-svc/log.txt"
case "$(j file)" in /var/lib/bj/igate-upload/*) rm -f "$(j file)" ;; esac # загруженный архив больше не нужен (с образа — остаётся)
st done "ИШ $(igate_version) установлен и работает; дальше — канал контура (TEST3 или боевой)" ;;
contour)
contour=$(j contour); crypto=$(j crypto); dep=$(j deponent); prof=$(j profile)
crypto=${crypto:-gost}
IFS='|' read -r CODE NAME EP FP TYPE <<<"$(contour_params "$contour" "$crypto")" || true
[ -n "${CODE:-}" ] || err "неизвестный контур: $contour/$crypto"
[[ "$dep" =~ ^[A-Z]{2}[0-9]{10}$ ]] || err "депозитарный код — вида MC0000000000"
[[ -z "$prof" || "$prof" =~ ^[A-Za-z0-9_-]+$ ]] || err "недопустимое имя профиля Валидаты"
[ -x "$IGATE_DIR/igate-svc/igate-svc" ] || err "ИШ не установлен"
# ключ этого контура ставили раньше, чем канал: профиль запомнен установкой ключа
if [ -z "$prof" ] && [ -s "$HOME_DIR/.bj-profile-$CODE" ]; then prof=$(tr -cd 'A-Za-z0-9_-' < "$HOME_DIR/.bj-profile-$CODE"); fi
DIR=$(exchange_dir "$contour"); install -d -o "$SVC_USER" -g "$SVC_USER" -m 755 "$DIR"
psqlq -v code="$CODE$dep" -v name="$NAME" -v ep="$EP" -v fp="$FP" -v typ="$TYPE" -v dep="$dep" -v prof="$prof" -v dir="$DIR" <<'SQL' || err "канал не записан в базу ИШ"
BEGIN;
INSERT INTO channels(name, code, type, enable)
SELECT :'name', :'code', 'WSL', true WHERE NOT EXISTS (SELECT 1 FROM channels WHERE code = :'code');
CREATE TEMP TABLE want(n text, v text) ON COMMIT DROP;
INSERT INTO want VALUES
('enable','True'),('wslEndpoint',:'ep'),('cryptography.type',:'typ'),('cryptography.pincode',NULL),
('cryptography.clientCertificateSerialNumber',''),('repositoryCode',:'dep'),('fetchInterval','00:00:10'),('attemptInterval','30000'),
('sendAttempts','3'),('maxPartSize','500'),('loadOldMessagesDepth','3'),('isIncomingEnabled','True'),('isOutgoingEnabled','True'),
('isTransitTerminalChannel','False'),('useDirectories','True'),('dir',:'dir'),('inboxDirName','INBOX'),('outboxDirName','OUTBOX'),
('sentDirName','SENT'),('errorDirName','ERRORS'),('archive1042sDirName','Archives1042S'),('enableLockFile','True'),
('enableAutoResponse','True'),('enable1042ReportProcessing','True'),('RenameOutgoingFiles','True'),('generateReceivedPackageInfo','True'),
('generateSentPackageInfo','False'),('moveReceiptsToSentFolder','False'),('applyAddHashOfPackageToFolder','False'),
('ignorePackageDirectoryStructure','True'),('checkReceivedPackageNsdSign','False'),('checkReceivedPackageSenderSign','False'),
('autoUpdateTransitMember','False'),('automaticcalyLoadCrls','False'),('autoInPkgReportOffload','False'),('monitoringThreshold','00:00:01'),
('autoLoadCrlsTime',NULL),('fetchThreadCount',NULL),('forceCryPackageEncryption',NULL),('inPkgReportDirectory',NULL),
('inPkgReportOffloadInterval',NULL),('maxPackagesPerJob',NULL),('nsdCertificateSerialNumbers',''),('pkiDecryptMode',NULL),('pkiEncryptMode',NULL),
('pkiSignMode',NULL),('pkiVerifyMode',NULL),('receiveProcThreadCount',NULL),('sendProcThreadCount',NULL),('updateTransitMemberListTime',NULL),
('wslTlsCACertFingerprint',:'fp'),('wslTlsCertDN','*.nsd.ru'),('wslTlsCertChainLen','2'),('isM2M','True');
-- Необязательные параметры канала — NULL, как их пишет интерфейс ИШ. Пустую строку ИШ разбирает как число или
-- интервал, падает и молча выкидывает канал («There are no active channels», API: «more than one Channel with same
-- code») — так было на чистом сервере .29 (06.10.2026). Каналы, записанные прежними версиями bj, чинятся здесь же.
UPDATE parameters SET value = NULL
WHERE chanel_id IS NOT NULL AND value = '' AND name IN ('autoLoadCrlsTime','cryptography.pincode','fetchThreadCount',
'forceCryPackageEncryption','inPkgReportDirectory','inPkgReportOffloadInterval','maxPackagesPerJob','pkiDecryptMode','pkiEncryptMode',
'pkiSignMode','pkiVerifyMode','receiveProcThreadCount','sendProcThreadCount','updateTransitMemberListTime');
UPDATE parameters SET value = 'Archives1042S' WHERE chanel_id IS NOT NULL AND name = 'archive1042sDirName' AND value LIKE '/%/Archives1042S';
-- существующие значения канала не трогаем, кроме контурных (адрес, отпечаток, криптография, депозитарный код)
UPDATE parameters p SET value = w.v FROM want w, channels c
WHERE c.code = :'code' AND p.chanel_id = c.id AND p.name = w.n
AND w.n IN ('wslEndpoint','wslTlsCACertFingerprint','wslTlsCertDN','wslTlsCertChainLen','cryptography.type','repositoryCode','isM2M');
INSERT INTO parameters(name, value, chanel_id)
SELECT w.n, w.v, c.id FROM want w, channels c
WHERE c.code = :'code' AND NOT EXISTS (SELECT 1 FROM parameters p WHERE p.chanel_id = c.id AND p.name = w.n);
-- профиль Валидаты (ключ этого контура): задан — пишем; не задан — оставляем прежний или пустой до установки ключа
INSERT INTO parameters(name, value, chanel_id)
SELECT 'cryptography.profile', '', c.id FROM channels c
WHERE c.code = :'code' AND NOT EXISTS (SELECT 1 FROM parameters p WHERE p.chanel_id = c.id AND p.name = 'cryptography.profile');
UPDATE parameters p SET value = :'prof' FROM channels c
WHERE :'prof' <> '' AND c.code = :'code' AND p.chanel_id = c.id AND p.name = 'cryptography.profile';
-- канал включён, только когда для контура установлен ключ (профиль Валидаты): без ключа ИШ его не поднимет
UPDATE channels c SET enable = COALESCE((SELECT value <> '' FROM parameters p WHERE p.chanel_id = c.id AND p.name = 'cryptography.profile'), false)
WHERE c.code = :'code';
UPDATE parameters p SET value = CASE WHEN c.enable THEN 'True' ELSE 'False' END FROM channels c
WHERE c.code = :'code' AND p.chanel_id = c.id AND p.name = 'enable';
COMMIT;
SQL
en=$(psqlq -v code="$CODE$dep" <<<"select enable from channels where code = :'code'")
if [ -n "${BJ_IGATE_NORESTART:-}" ]; then ok "канал $CODE$dep записан (без перезапуска, enable=$en)"; exit 0; fi
systemctl restart igate >/dev/null 2>&1 || err "служба igate не перезапустилась"
if wait_engine 120; then
if [ "$en" = t ]; then ok "канал $CODE$dep ($NAME, $EP) настроен и включён, ИШ работает"
else ok "канал $CODE$dep ($NAME, $EP) записан и пока выключен — включится после установки ключа этого контура"; fi
else
err "канал $CODE$dep записан, но движок ИШ не перешёл в Running — см. журнал ИШ $DATA_DIR/logs-svc/log.txt (отпечаток УЦ, адрес контура, ключ)"
fi ;;
# папки обмена шлюза по сети (SMB): для программ организации, которые кладут/забирают файлы через ИШ. bj это не
# нужно. Блок общих папок в smb.conf — между метками bj; заведённые вручную [igate_exchange] не трогаем.
share)
CONF=/etc/samba/smb.conf; B='# >>> bj: папки шлюза НРД'; E='# <<< bj: папки шлюза НРД'
on=$(j enabled); su=$(j user); sp=$(j password)
manual=false; [ -f "$CONF" ] && sed "/^$B/,/^$E/d" "$CONF" | grep -q '^\[igate_exchange\]' && manual=true
if [ "${on:-}" = status ] || [ -z "${on:-}" ]; then
mine=false; [ -f "$CONF" ] && grep -q "^$B" "$CONF" && mine=true
u=$( [ "$mine" = true ] && sed -n "/^$B/,/^$E/p" "$CONF" | sed -n 's/^ *valid users *= *//p' | head -1 )
[ "$manual" = true ] && [ -z "$u" ] && u=$(sed -n '/^\[igate_exchange\]/,/^\[/p' "$CONF" | sed -n 's/^ *valid users *= *//p' | head -1)
# куда подключаться из проводника: адрес и имя сервера, открытые папки (test3 / prod)
ip=$(hostname -I 2>/dev/null | awk '{print $1}'); hn=$(hostname -s 2>/dev/null)
sh=""; [ "$mine" = true ] && for n in $(sed -n "/^$B/,/^$E/p" "$CONF" | sed -n 's/^\[\(.*\)\]$/\1/p'); do sh="$sh${sh:+,}$(q "$n")"; done
[ "$manual" = true ] && [ -z "$sh" ] && sh=$(q igate_exchange)
echo "{\"ok\":true,\"on\":$( [ "$mine" = true ] || [ "$manual" = true ] && echo true || echo false),\"manual\":$manual,\"user\":$(q "$u"),\"smbd\":$(q "$(systemctl is-active smbd 2>/dev/null)"),\"ip\":$(q "$ip"),\"host\":$(q "$hn"),\"shares\":[$sh]}"; exit 0
fi
[ "$manual" = true ] && err "папка igate_exchange уже открыта вручную в $CONF — bj её не меняет; чтобы управлять из bj, уберите её из smb.conf"
if [ "$on" != true ]; then
[ -f "$CONF" ] && sed -i "/^$B/,/^$E/d" "$CONF"
systemctl reload smbd >/dev/null 2>&1 || true
ok "доступ к папкам шлюза по сети закрыт"; exit 0
fi
[[ "$su" =~ ^[a-z][a-z0-9_-]{2,31}$ ]] || err "логин — латинские строчные буквы, цифры, _ и -, от 3 символов, начинается с буквы"
case "$su" in root|igate|bj|postgres) err "логин $su занят системой — выберите другой" ;; esac
if ! command -v smbd >/dev/null 2>&1; then
DEBIAN_FRONTEND=noninteractive apt-get install -y -q samba >/dev/null 2>&1 || err "не удалось установить Samba (apt-get install samba) — нужен доступ к репозиторию"
fi
id "$su" >/dev/null 2>&1 || useradd --system --no-create-home --shell /usr/sbin/nologin "$su" || err "пользователь $su не создан"
if [ -n "$sp" ]; then
[ ${#sp} -ge 8 ] || err "пароль — не короче 8 символов"
printf '%s\n%s\n' "$sp" "$sp" | smbpasswd -s -a "$su" >/dev/null 2>&1 || err "пароль для $su не задан (smbpasswd)"
smbpasswd -e "$su" >/dev/null 2>&1
elif ! pdbedit -L 2>/dev/null | grep -q "^$su:"; then
err "задайте пароль для $su"
fi
[ -f "$CONF" ] && sed -i "/^$B/,/^$E/d" "$CONF"
{ echo "$B"
for c in test3 prod; do d=$(exchange_dir "$c"); [ -d "$d" ] || continue
n=igate_exchange; [ "$c" = prod ] && n=igate_exchange_prod
printf '[%s]\n comment = Шлюз НРД: папки обмена %s (INBOX/OUTBOX/SENT/ERRORS)\n path = %s\n browseable = yes\n read only = no\n valid users = %s\n force user = %s\n force group = %s\n' "$n" "$c" "$d" "$su" "$SVC_USER" "$SVC_USER"
done
echo "$E"; } >> "$CONF"
testparm -s "$CONF" >/dev/null 2>&1 || { sed -i "/^$B/,/^$E/d" "$CONF"; err "smb.conf после правки не проходит проверку (testparm) — правка отменена"; }
systemctl enable smbd >/dev/null 2>&1; systemctl restart smbd >/dev/null 2>&1 || err "служба smbd не перезапустилась"
ok "папки шлюза открыты по сети для $su: \\\\$(hostname -I | awk '{print $1}')\\igate_exchange" ;;
*) err "неизвестная команда: $cmd" ;;
esac