21d5f33394
Часть модуля onec-back, по умолчанию выключена, включается в настройках («Проверка поручений в 1С»). Исходящий перевод после блокировки бумаг в МНП ждёт решения 1С и в НРД не уходит, пока 1С не подтвердит каждое поручение; при отказе bj снимает поручения в QUIK с причиной из 1С и отклоняет перевод в журнале и кабинете клиента. - onec.instr / onec.instr_queue / onec.set_decision в базе bj: 1С читает очередь и пишет решение под логином onec_bj (только эти права); чтение очереди отмечает связь с 1С. - bj-onec-admin (root, через bj-keymedia): логин onec_bj, вход только с IP 1С по TLS (pg_hba.conf), listen_addresses, ufw; revoke закрывает вход. - onecLoop: отправка подтверждённых, отказы, снятие клиентом в QUIK, поручения из терминала, сроки решения и потеря связи — оповещения. - Карточка заявки: блок «Проверка 1С», решение оператора вместо 1С. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn
105 lines
6.6 KiB
Bash
Executable File
105 lines
6.6 KiB
Bash
Executable File
#!/bin/bash
|
||
# bj-onec-admin — вход 1С в базу PostgreSQL bj для проверки поручений в 1С (через root-демон bj-keymedia).
|
||
# access — логин onec_bj с паролем, вход в базу bj только с IP сервера 1С и только по TLS:
|
||
# свои строки в начале pg_hba.conf (# bj-onec), PostgreSQL слушает адрес сервера bj,
|
||
# с которого виден сервер 1С (перезапуск PostgreSQL — только если этот адрес не слушался),
|
||
# порт 5432 в ufw для IP 1С, если ufw включён. BJ_EXTRA: {"password":"…","ips":"10.0.0.5/32,…"}
|
||
# revoke — закрыть вход: NOLOGIN, строки # bj-onec из pg_hba.conf и правила ufw убрать
|
||
# Права на объекты onec.* выдаёт сам bj (он их владелец).
|
||
set -uo pipefail
|
||
q() { python3 -c "import json,sys;print(json.dumps(sys.argv[1],ensure_ascii=False))" "$1"; }
|
||
ok() { echo "{\"ok\":true,\"output\":$(q "$1")${2:-}}"; exit 0; }
|
||
err() { echo "{\"ok\":false,\"error\":$(q "$1")}"; exit 0; }
|
||
x() { python3 -c "import json,os,sys;print(json.loads(os.environ.get('BJ_EXTRA') or '{}').get(sys.argv[1],''))" "$1" 2>/dev/null; }
|
||
cd /
|
||
psqlq() { runuser -u postgres -- psql -X -q -At -v ON_ERROR_STOP=1 "$@"; }
|
||
LOGIN=onec_bj
|
||
DB=bj
|
||
MARK='# bj-onec'
|
||
|
||
hba=$(psqlq -d postgres -c 'show hba_file' 2>&1) || err "PostgreSQL недоступен: $hba"
|
||
[ -f "$hba" ] || err "не найден pg_hba.conf ($hba)"
|
||
|
||
# hba_set "строки" — заменить наш блок (пусто — убрать) в начале pg_hba.conf
|
||
hba_set() {
|
||
[ -f "$hba.bak-bj-onec" ] || cp -a "$hba" "$hba.bak-bj-onec"
|
||
python3 - "$hba" "$MARK" "$1" <<'PY' || return 1
|
||
import sys
|
||
p, mark, new = sys.argv[1], sys.argv[2], sys.argv[3]
|
||
old = [l for l in open(p).read().split("\n") if not l.rstrip().endswith(mark)]
|
||
add = [l for l in new.split("\n") if l.strip()]
|
||
open(p, "w").write("\n".join(add + old).rstrip("\n") + "\n")
|
||
PY
|
||
}
|
||
|
||
case "${1:-}" in
|
||
access)
|
||
pw=$(x password); ips=$(x ips)
|
||
[[ "$pw" =~ ^[A-Za-z0-9]{20,64}$ ]] || err "пароль: 20–64 латинских букв и цифр"
|
||
list=()
|
||
for ip in ${ips//,/ }; do
|
||
[[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/(2[4-9]|3[0-2])$ ]] || err "недопустимый адрес 1С: $ip"
|
||
list+=("$ip")
|
||
done
|
||
[ ${#list[@]} -gt 0 ] || err "не указан IP-адрес сервера 1С"
|
||
[ "$(psqlq -d postgres -c 'show ssl')" = on ] || err "в PostgreSQL выключено шифрование (ssl = off) — 1С подключается только по TLS"
|
||
psqlq -d postgres -c "select 1 from pg_database where datname='$DB'" | grep -q 1 || err "нет базы $DB"
|
||
# логин: только вход, без прав на создание чего-либо; пароль — через переменную psql (не в командной строке)
|
||
printf '%s\n' \
|
||
"DO \$\$BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$LOGIN') THEN CREATE ROLE $LOGIN; END IF; END\$\$;" \
|
||
"ALTER ROLE $LOGIN WITH LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 5 PASSWORD :'pw';" \
|
||
"ALTER ROLE $LOGIN SET statement_timeout = '30s';" \
|
||
"ALTER ROLE $LOGIN SET search_path = onec;" \
|
||
| psqlq -d postgres -v pw="$pw" >/dev/null 2>&1 || err "логин $LOGIN не заведён"
|
||
# в схеме public базы bj создавать объекты может только bj (у PUBLIC право снимаем)
|
||
psqlq -d "$DB" -c "GRANT USAGE, CREATE ON SCHEMA public TO bj" -c "REVOKE CREATE ON SCHEMA public FROM PUBLIC" >/dev/null 2>&1 \
|
||
|| err "права на схему public в базе $DB не изменены"
|
||
lines=""
|
||
for ip in "${list[@]}"; do lines+="hostssl $DB $LOGIN $ip md5 $MARK"$'\n'; done
|
||
lines+="host all $LOGIN 0.0.0.0/0 reject $MARK"$'\n'"host all $LOGIN ::/0 reject $MARK"
|
||
hba_set "$lines" || err "не удалось записать $hba"
|
||
# адрес сервера bj, с которого виден сервер 1С
|
||
first=${list[0]%/*}
|
||
src=$(ip -o route get "$first" 2>/dev/null | sed -n 's/.* src \([0-9.]*\).*/\1/p' | head -1)
|
||
[ -n "$src" ] || err "нет маршрута до сервера 1С ($first)"
|
||
cur=$(psqlq -d postgres -c 'show listen_addresses')
|
||
restart=no
|
||
case ",${cur// /}," in
|
||
*,\*,*|*,"$src",*|*,0.0.0.0,*) ;;
|
||
*) new="${cur// /},$src"
|
||
printf '%s\n' "ALTER SYSTEM SET listen_addresses = :'v';" | psqlq -d postgres -v v="$new" >/dev/null 2>&1 || err "listen_addresses не изменён"
|
||
restart=yes ;;
|
||
esac
|
||
if [ "$restart" = yes ]; then
|
||
systemctl restart postgresql >/dev/null 2>&1 || err "PostgreSQL не перезапустился (listen_addresses = $new)"
|
||
for i in $(seq 1 30); do psqlq -d postgres -c 'select 1' >/dev/null 2>&1 && break; sleep 1; done
|
||
else
|
||
psqlq -d postgres -c 'select pg_reload_conf()' >/dev/null 2>&1 || err "PostgreSQL не перечитал pg_hba.conf"
|
||
fi
|
||
fw=""
|
||
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
|
||
for ip in "${list[@]}"; do ufw allow from "$ip" to any port 5432 proto tcp comment bj-onec >/dev/null 2>&1; done
|
||
fw="; порт 5432 открыт в ufw"
|
||
fi
|
||
port=$(psqlq -d postgres -c 'show port')
|
||
note="логин $LOGIN, вход с ${list[*]} по TLS, сервер $src:$port"
|
||
[ "$restart" = yes ] && note+="; PostgreSQL теперь слушает $src (перезапущен)"
|
||
ok "$note$fw" ",\"host\":$(q "$src"),\"port\":$(q "$port")"
|
||
;;
|
||
revoke)
|
||
psqlq -d postgres -c "DO \$\$BEGIN IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '$LOGIN') THEN ALTER ROLE $LOGIN NOLOGIN; END IF; END\$\$;" >/dev/null 2>&1 \
|
||
|| err "вход логином $LOGIN не закрыт"
|
||
# открытые сеансы 1С — завершить
|
||
psqlq -d postgres -c "select pg_terminate_backend(pid) from pg_stat_activity where usename = '$LOGIN'" >/dev/null 2>&1
|
||
hba_set "" || err "не удалось записать $hba"
|
||
psqlq -d postgres -c 'select pg_reload_conf()' >/dev/null 2>&1
|
||
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
|
||
while n=$(ufw status numbered 2>/dev/null | grep 'bj-onec' | head -1 | sed -n 's/^\[ *\([0-9]*\)\].*/\1/p') && [ -n "$n" ]; do
|
||
ufw --force delete "$n" >/dev/null 2>&1 || break
|
||
done
|
||
fi
|
||
ok "вход логином $LOGIN закрыт"
|
||
;;
|
||
*) err "команда: access | revoke" ;;
|
||
esac
|