Кабинет: веб-установщик и управление пользователями в ядре

- установщик в самом кабинете: до настройки любая страница ведёт в /install; три шага —
  проверка сервера (каталог данных, https), связь с ядром (режим, адрес, ключ; связь
  проверяется до сохранения, в режиме ожидания страница ждёт подключения ядра),
  администратор кабинета (заводится в ядре через /api/lk/admin/bootstrap, только пока
  администраторов нет); настройки в data/lk.json 0600, окружение перекрывает файл;
  после установки установщик закрыт
- настройки кабинета можно менять на ходу: Server за мьютексом, один mux на всё время
- ключ модуля больше не кладётся в архив: его вводят в установщике; на странице настроек
  ключ показан целиком 10 минут после создания, потом маска
- ядро: карточка «Пользователи кабинета» в настройках — список, заведение, смена пароля
  (гасит сессии), блокировка, права сотрудника, разрешение поручений, удаление; письма
  не отправляются
- инструкция /admin/help/lk дополнена шагами установщика и разделом о подходящем хостинге
- тесты: TestInstaller, TestNotConfigured, TestSetupPageLKUsersAndLink; установщик
  проверен вживую вторым экземпляром против боевого ядра

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
zuevav
2026-09-18 09:30:47 +03:00
parent e1bf7c440f
commit 222d48e740
14 changed files with 977 additions and 105 deletions
+46 -15
View File
@@ -49,6 +49,18 @@ func lkNewAPIKey() (string, error) {
return "lk-" + hex.EncodeToString(b), nil
}
// lkKeyRevealUntil — до какого момента ключ на странице настроек показывается целиком:
// сразу после создания администратору нужно переписать его в установщик кабинета.
var lkKeyRevealUntil time.Time
// lkKeyForPage — ключ для страницы настроек: целиком сразу после создания, потом маска.
func lkKeyForPage(key string) string {
if key != "" && time.Now().Before(lkKeyRevealUntil) {
return key
}
return maskSecret(key)
}
// maskSecret — показать ключ так, чтобы его нельзя было списать со скриншота,
// но можно было сверить с тем, что стоит у кабинета.
func maskSecret(v string) string {
@@ -84,18 +96,26 @@ func lkDistFiles(s Settings, coreURL string) ([]struct {
// ядро подключается к кабинету само по имени этого сервера.
mode, urlLine = "agent", "# BJ_URL не нужен: ядро подключается к кабинету само"
}
env := fmt.Sprintf(`# Настройки кабинета клиента (bj-lk). Файл читает служба, права 0600.
// Ключ в архив НЕ кладём: его вводят в веб-установщике кабинета. Так архив можно
// хранить и пересылать, а секрет живёт только в двух местах — в ядре и в файле
// настроек кабинета с правами 0600.
env := fmt.Sprintf(`# Настройки кабинета клиента (bj-lk) для установки службой. Файл читает служба, права 0600.
# Проще — ничего здесь не менять: после запуска откройте адрес кабинета в браузере,
# запустится установщик и сам спросит способ связи и ключ.
# Режим связи с ядром: direct — кабинет обращается к ядру; agent — ядро подключается сюда.
BJ_MODE=%s
# Адрес ядра bj, к которому обращается кабинет (только для режима direct):
%s
# Ключ доступа модуля — тот же, что в настройках ядра, раздел «Личный кабинет».
BJ_KEY=%s
# Ключ доступа модуля — из настроек ядра, раздел «Личный кабинет». Оставьте пустым,
# чтобы ввести его в установщике; заполните — если ставите без браузера.
BJ_KEY=
# Адрес и порт, на которых кабинет слушает (за ним обычно стоит nginx с TLS):
LISTEN=127.0.0.1:8099
# Заголовок кабинета:
LK_TITLE=%s
`, mode, urlLine, s.LK.APIKey, title)
# Каталог данных (файл настроек установщика):
LK_DATA=/var/lib/bj-lk
`, mode, urlLine, title)
unit := `[Unit]
Description=Личный кабинет клиента депозитария (bj-lk)
@@ -116,7 +136,7 @@ NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/log/bj-lk
ReadWritePaths=/var/log/bj-lk /var/lib/bj-lk
[Install]
WantedBy=multi-user.target
@@ -129,6 +149,7 @@ DIR=$(cd "$(dirname "$0")" && pwd)
id bjlk >/dev/null 2>&1 || useradd --system --no-create-home --shell /usr/sbin/nologin bjlk
install -d -m 755 /opt/bj-lk
install -d -m 750 -o bjlk -g bjlk /var/log/bj-lk
install -d -m 700 -o bjlk -g bjlk /var/lib/bj-lk
install -d -m 750 /etc/bj-lk
install -m 755 "$DIR/bj-lk" /opt/bj-lk/bj-lk
if [ -f /etc/bj-lk/lk.env ]; then
@@ -142,7 +163,8 @@ systemctl enable --now bj-lk
sleep 2
systemctl --no-pager --full status bj-lk | head -n 12
echo
echo "Проверка: curl -s http://127.0.0.1:8099/health"
echo "Дальше откройте адрес кабинета в браузере — запустится установщик."
echo "Проверка службы: curl -s http://127.0.0.1:8099/health"
`
readme := fmt.Sprintf(`Личный кабинет клиента депозитария (bj-lk)
@@ -160,10 +182,19 @@ echo "Проверка: curl -s http://127.0.0.1:8099/health"
install.sh — установка одной командой
README.txt — этот файл
Установка:
Установка — как у обычного веб-приложения:
1. Распакуйте архив на сервере кабинета.
2. sudo ./install.sh
3. Проверьте: curl -s http://127.0.0.1:8099/health — должно вернуться "ok":true.
2. sudo ./install.sh — заводит службу; либо просто запустите ./bj-lk в своём
каталоге, если хостинг позволяет держать программу запущенной.
3. Откройте адрес кабинета в браузере. Запустится установщик: проверит сервер,
спросит способ связи с ядром и ключ доступа, проверит связь и предложит
завести администратора кабинета. Настройки он сохранит в файл lk.json
(каталог LK_DATA, права 0600). Ключа в этом архиве нет — берите его из
настроек ядра, раздел «Личный кабинет».
4. Проверка службы: curl -s http://127.0.0.1:8099/health — "ok":true.
Установка без браузера: заполните BJ_KEY (и BJ_URL для режима direct) в lk.env и
перезапустите службу — окружение перекрывает файл установщика.
Настройки (файл /etc/bj-lk/lk.env):
BJ_URL — адрес ядра bj: %s
@@ -200,14 +231,13 @@ echo "Проверка: curl -s http://127.0.0.1:8099/health"
• наружу открыт только 443 сервера кабинета;
• в режиме direct кабинет обращается к ядру на %s — разрешите этот доступ
в межсетевом экране; в режиме agent входящий доступ к ядру не нужен вовсе;
• ключ доступа хранится только в /etc/bj-lk/lk.env (права 0600, владелец root);
• ключ можно сменить в настройках ядра, после чего заменить его в lk.env и
перезапустить службу;
• ключ доступа хранится только в файле настроек кабинета (lk.json или lk.env,
права 0600); в этом архиве его нет;
• ключ можно сменить в настройках ядра, после чего удалить lk.json и пройти
установщик заново (или вписать новый ключ в lk.env) и перезапустить службу;
• кабинет не хранит данные клиентов: ни базы, ни файлов — всё берётся у ядра
на время запроса. Шрифты и оформление вшиты в программу, сторонние сайты
страница не запрашивает;
• ВАЖНО: в этом архиве лежит боевой ключ доступа. После установки удалите
архив с рабочего компьютера и не пересылайте его почтой или в мессенджере.
Обновления. Кабинет обновляется отдельно от ядра: новую версию скачивайте на
странице настроек ядра, раздел «Личный кабинет», и ставьте тем же install.sh —
@@ -290,7 +320,8 @@ func (h *setupHandlers) lkSaveDist(w http.ResponseWriter, r *http.Request) {
return
}
s.APIKey = key
msg = "Создан новый ключ доступа кабинета. Скачайте дистрибутив заново или впишите ключ в /etc/bj-lk/lk.env и перезапустите службу кабинета."
lkKeyRevealUntil = time.Now().Add(10 * time.Minute) // показать целиком один раз, чтобы переписать в установщик
msg = "Создан новый ключ доступа кабинета. Он показан целиком ниже в течение 10 минут — введите его в установщике кабинета (или в lk.env) и перезапустите службу кабинета, если она уже работала."
case "clearkey":
s.APIKey = ""
msg = "Ключ удалён: кабинет больше не сможет обращаться к ядру."