Установка: приветственный экран консоли (bj-welcome: адрес веб-входа и состояние, всегда), ядро с образа без ключа продукта (--bundle), ключ продукта на первой веб-странице, повтор вопроса при каждой загрузке

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
This commit is contained in:
zuevav
2026-09-23 08:53:13 +03:00
parent 925c516e1e
commit 492e226e54
9 changed files with 263 additions and 25 deletions
+1 -1
View File
@@ -6,5 +6,5 @@ import "embed"
// Files — linux/sbin/* (в /usr/local/sbin) и systemd/*.service (в /etc/systemd/system).
//
//go:embed linux/sbin/* systemd/bj-server.service systemd/bj-crypto.service systemd/bj-keymedia.service systemd/bj-npr.service systemd/bj-quikexport.service systemd/bj-lk.service
//go:embed linux/sbin/* systemd/bj-server.service systemd/bj-crypto.service systemd/bj-keymedia.service systemd/bj-npr.service systemd/bj-quikexport.service systemd/bj-lk.service systemd/bj-welcome.service systemd/bj-welcome.timer
var Files embed.FS
+36 -17
View File
@@ -1,41 +1,60 @@
#!/bin/bash
# Первая загрузка сервера, установленного с образа Bridge&Join: на экране сервера спрашивает ключ
# продукта и запускает bj-installer. После успешной установки служба выключается; адрес первичной
# настройки и одноразовый код остаются на экране входа (/etc/issue.d/bj-setup.issue).
#!/bin/sh
# Первая загрузка сервера, установленного с образа Bridge&Join. Спрашивает ключ продукта на консоли;
# без ключа ставит ядро bj с образа (/var/lib/bj/bundle), а ключ вводится в браузере на первой
# странице настройки. Пока bj не установлен, вопрос повторяется при каждой загрузке (с таймаутом),
# адрес и подсказки всегда есть на экране входа (bj-welcome).
set -u
MARK=/var/lib/bj/.bj/installed-from-iso
SERVER=https://lic.zetit.ru
CHANNEL=stable
BUNDLE=/var/lib/bj/bundle
[ -s /etc/bj/channel ] && CHANNEL=$(tr -d ' \n' < /etc/bj/channel)
finish() {
mkdir -p "$(dirname "$MARK")" && date -Iseconds > "$MARK"
systemctl disable bj-firstboot.service >/dev/null 2>&1
[ -x /usr/local/sbin/bj-welcome ] && /usr/local/sbin/bj-welcome
}
urls() { ip -4 -o addr show scope global 2>/dev/null | awk '{split($4,a,"/"); printf " http://%s:8080%s\n", a[1], "'"$1"'"}'; }
clear
printf '\n Bridge&Join: установка на сервер\n ────────────────────────────────────────────────\n\n'
printf ' Debian установлен. Осталось установить bj.\n'
printf ' Нужен ключ продукта (BJ-XXXX-XXXX-XXXX): он есть в портале клиента %s/portal/\n\n' "$SERVER"
for i in $(seq 1 60); do
printf '\n Bridge&Join: установка на сервер\n ────────────────────────────────────────────────────────────\n'
printf ' Debian установлен. Осталось установить bj.\n\n'
if [ -f "$BUNDLE/manifest.json" ]; then
printf ' Ключ продукта (BJ-XXXX-XXXX-XXXX) есть в портале клиента %s/portal/.\n' "$SERVER"
printf ' Вводить его здесь необязательно: нажмите Enter — ядро bj установится с образа,\n а ключ вы введёте в браузере на первой странице настройки (там его удобно вставить).\n\n'
else
printf ' Нужен ключ продукта (BJ-XXXX-XXXX-XXXX): он есть в портале клиента %s/portal/\n\n' "$SERVER"
fi
for i in $(seq 1 30); do
curl -fsS -m 5 -o /dev/null "$SERVER/healthz" && break
[ "$i" = 1 ] && printf ' Жду сеть и связь с %s…\n' "$SERVER"
sleep 2
done
while true; do
read -r -p " Ключ продукта (пусто — установить позже): " CODE
CODE=$(printf '%s' "$CODE" | tr -d ' ' | tr '[:lower:]' '[:upper:]')
CODE=""
if [ -f "$BUNDLE/manifest.json" ]; then
read -t 120 -r -p " Ключ продукта (Enter — установить с образа, ключ ввести в браузере): " CODE || true
else
read -t 120 -r -p " Ключ продукта (Enter — установить позже): " CODE || true
fi
CODE=$(printf '%s' "${CODE:-}" | tr -d ' ' | tr '[:lower:]' '[:upper:]')
if [ -z "$CODE" ]; then
printf '\n Установка bj отложена. Позже выполните: sudo bj-installer install --code КЛЮЧ-ПРОДУКТА\n\n'
finish
if [ -f "$BUNDLE/manifest.json" ]; then
if /usr/local/sbin/bj-installer install --bundle "$BUNDLE" --server "$SERVER" --channel "$CHANNEL"; then
finish
printf '\n Ключ продукта спросит первая страница настройки в браузере:\n'; urls /first-run; printf '\n'
read -t 60 -r -p " Нажмите Enter, чтобы перейти к экрану входа. " _ || true
exit 0
fi
printf '\n Установка с образа не завершилась. Подробности выше; повторить:\n sudo bj-installer install --bundle %s\n\n' "$BUNDLE"
sleep 5; exit 0
fi
printf '\n Установка bj отложена. Вопрос повторится при следующей загрузке; вручную:\n sudo bj-installer install --code КЛЮЧ-ПРОДУКТА\n\n'
sleep 3
exit 0
fi
if /usr/local/sbin/bj-installer install --code "$CODE" --server "$SERVER" --channel "$CHANNEL"; then
finish
read -r -p " Нажмите Enter, чтобы перейти к экрану входа. " _
read -t 60 -r -p " Нажмите Enter, чтобы перейти к экрану входа. " _ || true
exit 0
fi
printf '\n Установка не завершилась. Проверьте ключ продукта и сеть и попробуйте ещё раз.\n\n'
+15 -1
View File
@@ -1,7 +1,8 @@
#!/bin/sh
# Сборка образа ISO Bridge&Join на основе официального Debian 13 netinst.
#
# deploy/iso/build-iso.sh <debian-13.x-amd64-netinst.iso> <bj-installer> <версия> [канал] [выходной.iso]
# deploy/iso/build-iso.sh <debian-13.x-amd64-netinst.iso> <bj-installer> <версия> [канал] [выходной.iso] [каталог релиза]
# Каталог релиза (dist с manifest.json) — ядро bj кладётся в образ: установка без ключа продукта, ключ вводится в браузере.
#
# В образ добавляются: файл ответов установщику (preseed.cfg), bj-installer и служба первой загрузки.
# Загрузочное меню BIOS (isolinux) и UEFI (grub) по умолчанию запускает установку с этими ответами.
@@ -13,6 +14,7 @@ INSTALLER=${2:?файл bj-installer}
VERSION=${3:?версия образа, например 2026.10.1}
CHANNEL=${4:-stable}
OUT=${5:-bj-debian13-$VERSION.iso}
BUNDLE=${6:-}
HERE=$(cd "$(dirname "$0")" && pwd)
command -v xorriso >/dev/null || { echo "нужен xorriso: apt-get install xorriso"; exit 1; }
WORK=$(mktemp -d)
@@ -25,6 +27,18 @@ install -m 0755 "$HERE/bj-firstboot" "$WORK/bj/bj-firstboot"
cp "$HERE/bj-firstboot.service" "$WORK/bj/bj-firstboot.service"
echo "$CHANNEL" > "$WORK/bj/channel"
echo "$VERSION" > "$WORK/bj/version"
# приветственный экран консоли (адрес веб-входа) — ставится ещё до установки bj
install -m 0755 "$HERE/../linux/sbin/bj-welcome" "$WORK/bj/bj-welcome"
cp "$HERE/../systemd/bj-welcome.service" "$HERE/../systemd/bj-welcome.timer" "$WORK/bj/"
# ядро bj с образа: подписанный манифест и файлы компонентов ядра (bj-server, bj-component-install)
if [ -n "$BUNDLE" ]; then
[ -s "$BUNDLE/manifest.json" ] || { echo "в $BUNDLE нет manifest.json"; exit 1; }
mkdir -p "$WORK/bj/bundle"; cp "$BUNDLE/manifest.json" "$WORK/bj/bundle/"
for f in $(python3 -c 'import json,sys;m=json.load(open(sys.argv[1]));m=m.get("manifest",m);print(" ".join(a["file"] for a in m["artifacts"] if a["name"] in ("bj-server","bj-component-install")))' "$BUNDLE/manifest.json"); do
cp "$BUNDLE/$f" "$WORK/bj/bundle/"
done
echo "в образ добавлено ядро bj: $(ls "$WORK/bj/bundle" | tr '\n' ' ')"
fi
BOOT="auto=true priority=high preseed/file=/cdrom/preseed/bj.cfg language=ru country=RU locale=ru_RU.UTF-8 keyboard-configuration/xkb-keymap=ru"
+4 -1
View File
@@ -1,7 +1,8 @@
#_preseed_V1
# Ответы установщику Debian 13 для образа Bridge&Join (deploy/iso/build-iso.sh).
# Спрашиваются только пароли, диск (если их несколько) и подтверждение стирания; остальное задано здесь.
# После установки служба bj-firstboot на экране сервера спросит ключ продукта и установит bj.
# После установки служба bj-firstboot на экране сервера спросит ключ продукта (или установит ядро с образа —
# ключ тогда вводится в браузере); bj-welcome показывает адрес веб-входа на экране входа в консоль.
# Язык, клавиатура, время
d-i debian-installer/language string ru
@@ -69,6 +70,8 @@ d-i preseed/late_command string \
install -m 0644 /cdrom/bj/bj-firstboot.service /target/etc/systemd/system/bj-firstboot.service; \
mkdir -p /target/etc/bj; \
if [ -f /cdrom/bj/channel ]; then install -m 0644 /cdrom/bj/channel /target/etc/bj/channel; fi; \
if [ -d /cdrom/bj/bundle ]; then mkdir -p /target/var/lib/bj/bundle && cp -r /cdrom/bj/bundle/. /target/var/lib/bj/bundle/; fi; \
if [ -f /cdrom/bj/bj-welcome ]; then install -m 0755 /cdrom/bj/bj-welcome /target/usr/local/sbin/bj-welcome; install -m 0644 /cdrom/bj/bj-welcome.service /target/etc/systemd/system/; install -m 0644 /cdrom/bj/bj-welcome.timer /target/etc/systemd/system/; in-target systemctl enable bj-welcome.service bj-welcome.timer; fi; \
in-target systemctl enable bj-firstboot.service
d-i finish-install/reboot_in_progress note
+66
View File
@@ -0,0 +1,66 @@
#!/bin/sh
# bj-welcome — приветственный экран на консоли до входа (/etc/issue.d/10-bj.issue):
# адреса веб-входа и состояние bj. Вызывается службой bj-welcome при загрузке и по таймеру,
# а также установщиком после шагов, меняющих состояние. Читает только локальные файлы.
set -u
ISSUE=/etc/issue.d/10-bj.issue
BJ=/opt/bj/bj-server
SETUP=/var/lib/bj/.bj/setup.json
USERS=/var/lib/bj/.bj/users.json
FIRSTRUN=/var/lib/bj/.bj/first-run.json
PORT=8080
HOST=$(hostname 2>/dev/null || echo bj)
ADDRS=$(ip -4 -o addr show scope global 2>/dev/null | awk '{split($4,a,"/"); print a[1]}' | tr '\n' ' ')
[ -z "$ADDRS" ] && ADDRS="<адрес не получен: проверьте сеть>"
url() { for a in $ADDRS; do case "$a" in \<*) printf ' http://%s:%s%s\n' "<IP сервера>" "$PORT" "$1";; *) printf ' http://%s:%s%s\n' "$a" "$PORT" "$1";; esac; done; }
lic=""
if [ -r "$SETUP" ]; then
lic=$(python3 - "$SETUP" 2>/dev/null <<'PY'
import json,sys
try:
d=json.load(open(sys.argv[1])); l=d.get("license",{})
print("да" if l.get("key") else "нет", l.get("last_msg","")[:60])
except Exception: print("")
PY
)
fi
{
printf '\n ┌────────────────────────────────────────────────────────────────┐\n'
printf ' │ Bridge&Join (bj) — шлюз переводов ценных бумаг M2M / НРД │\n'
printf ' └────────────────────────────────────────────────────────────────┘\n'
printf ' Сервер: %s Адреса: %s\n\n' "$HOST" "$ADDRS"
if [ ! -x "$BJ" ]; then
printf ' Состояние: bj НЕ УСТАНОВЛЕН.\n'
if [ -f /var/lib/bj/bundle/manifest.json ]; then
printf ' Установка с образа (ключ продукта — потом, в браузере):\n sudo bj-installer install --bundle /var/lib/bj/bundle\n'
printf ' Или с ключом продукта: sudo bj-installer install --code BJ-XXXX-XXXX-XXXX\n'
else
printf ' Установка: sudo bj-installer install --code BJ-XXXX-XXXX-XXXX (ключ — в портале клиента)\n'
fi
elif ! grep -q '"login"' "$USERS" 2>/dev/null; then
printf ' Состояние: bj установлен, ПЕРВИЧНАЯ НАСТРОЙКА НЕ ВЫПОЛНЕНА.\n'
printf ' Откройте в браузере на своём компьютере:\n'; url /first-run
if [ -f "$FIRSTRUN" ]; then
printf ' Введите одноразовый код, показанный установщиком. Код потерян или истёк —\n новый код: sudo bj-installer first-run-code\n'
else
printf ' Нужен одноразовый код: sudo bj-installer first-run-code\n'
fi
case "$lic" in да*) ;; *) printf ' Ключ продукта ещё не введён — его спросит первая страница.\n';; esac
else
printf ' Состояние: bj работает. Вход в веб-интерфейс:\n'; url /admin
case "$lic" in
да*) printf ' Лицензия: %s\n' "${lic#да }";;
*) printf ' Лицензия НЕ АКТИВИРОВАНА: введите ключ продукта в «Настройка → Лицензия».\n';;
esac
fi
printf '\n Вход в консоль сервера — учётной записью ОС (для обслуживания). Веб-вход — по адресу выше.\n\n'
} > "$ISSUE.tmp"
if ! cmp -s "$ISSUE.tmp" "$ISSUE" 2>/dev/null; then
mv -f "$ISSUE.tmp" "$ISSUE"
# обновить приглашение на tty1, если там никто не вошёл
if command -v systemctl >/dev/null && ! who 2>/dev/null | grep -qE '^\S+\s+tty1\b'; then
systemctl try-restart getty@tty1.service 2>/dev/null || true
fi
else
rm -f "$ISSUE.tmp"
fi
+12
View File
@@ -0,0 +1,12 @@
[Unit]
Description=Bridge&Join: приветственный экран на консоли (адрес веб-входа и состояние)
After=network-online.target
Wants=network-online.target
Before=getty@tty1.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/bj-welcome
[Install]
WantedBy=multi-user.target
+10
View File
@@ -0,0 +1,10 @@
[Unit]
Description=Bridge&Join: обновление приветственного экрана (адреса, состояние)
[Timer]
OnBootSec=1min
OnUnitActiveSec=2min
AccuracySec=30s
[Install]
WantedBy=timers.target