From bd24a0db9899315322106e89510841143aa761d5 Mon Sep 17 00:00:00 2001 From: zuevav Date: Mon, 5 Oct 2026 21:12:56 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A3=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0=20=D1=81=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B7=D0=B0=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=B4=D0=B0=D0=BB=D0=B0=20=D0=BD=D0=B0=20=D1=88?= =?UTF-8?q?=D0=B0=D0=B3=D0=B5=20=C2=AB=D0=9A=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D1=8B=C2=BB=20=D0=BF=D1=80=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=BE=D0=B9=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83?= =?UTF-8?q?=D0=B7=D0=BA=D0=B5=20(=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=85=D0=BE=D0=B4=D0=B8=D0=BB):=20=D1=81?= =?UTF-8?q?=D0=BB=D1=83=D0=B6=D0=B1=D0=B0=20=D0=B2=D0=BF=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D1=8B=D0=B5=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=8F=D0=B4=D1=80=D0=B0?= =?UTF-8?q?=20=D0=B2=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D0=BB=D0=B0=D1=81=D1=8C?= =?UTF-8?q?=20=D1=80=D0=B0=D0=BD=D1=8C=D1=88=D0=B5,=20=D1=87=D0=B5=D0=BC?= =?UTF-8?q?=20=D0=BF=D0=BE=D1=8F=D0=B2=D0=BB=D1=8F=D0=BB=D1=81=D1=8F=20?= =?UTF-8?q?=D0=B5=D1=91=20unit-=D1=84=D0=B0=D0=B9=D0=BB=20(systemctl=20ena?= =?UTF-8?q?ble=20bj-server=20=E2=80=94=20=C2=ABUnit=20file=20does=20not=20?= =?UTF-8?q?exist=C2=BB);=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=B2?= =?UTF-8?q?=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D0=B5=D1=82=D1=81=D1=8F,=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=B5=D1=81=D0=BB=D0=B8?= =?UTF-8?q?=20unit-=D1=84=D0=B0=D0=B9=D0=BB=20=D0=B5=D1=81=D1=82=D1=8C.=20?= =?UTF-8?q?Java=2017:=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20=D0=BF=D0=BE=20ap?= =?UTF-8?q?t-cache=20=D0=B1=D0=B5=D0=B7=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B2?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=20(=D0=BD=D0=B0=20=D1=80=D1=83=D1=81=D1=81?= =?UTF-8?q?=D0=BA=D0=BE=D0=B9=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=20=C2=AB=D0=9A=D0=B0=D0=BD=D0=B4=D0=B8=D0=B4=D0=B0=D1=82:?= =?UTF-8?q?=C2=BB=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=C2=ABCandidate?= =?UTF-8?q?:=C2=BB=20=E2=80=94=20=D1=81=D1=82=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=8C=20Java=2011).=20=D0=A0=D0=B0=D1=81=D0=BA?= =?UTF-8?q?=D0=BB=D0=B0=D0=B4=D0=BA=D0=B0=20=D0=BA=D0=BE=D0=BD=D1=81=D0=BE?= =?UTF-8?q?=D0=BB=D0=B8:=20toggle=20=D0=B1=D0=B5=D0=B7=20=D0=BB=D0=B8?= =?UTF-8?q?=D1=88=D0=BD=D0=B5=D0=B3=D0=BE=20=C2=ABgrp:=C2=BB=20(=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=83=D1=87=D0=B0=D0=BB=D0=BE=D1=81=D1=8C=20grp:gr?= =?UTF-8?q?p:alt=5Fshift=5Ftoggle,=20Alt+Shift=20=D0=BD=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D0=BB).=20?= =?UTF-8?q?=D0=92=D0=BE=D0=BF=D1=80=D0=BE=D1=81=20=D0=BE=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=BD=D0=BE=D0=BC=20=D0=B0=D0=B4?= =?UTF-8?q?=D1=80=D0=B5=D1=81=D0=B5=20=E2=80=94=20=D0=BE=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=20=D1=86=D0=B8=D1=84=D1=80=D0=BE=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi --- cmd/bj-installer/main.go | 2 +- deploy/iso/bj-firstboot | 5 +++-- deploy/iso/ubuntu/user-data.tmpl | 2 +- internal/release/install.go | 14 +++++++++++++- internal/release/install_test.go | 8 ++++++++ 5 files changed, 26 insertions(+), 5 deletions(-) diff --git a/cmd/bj-installer/main.go b/cmd/bj-installer/main.go index b968c87..f4ada69 100644 --- a/cmd/bj-installer/main.go +++ b/cmd/bj-installer/main.go @@ -282,7 +282,7 @@ func javaPkg(debs []string) string { } return "default-jre-headless" } - out, err := run("apt-cache", "policy", "openjdk-17-jre-headless") + out, err := runEnv(append(os.Environ(), "LC_ALL=C"), "apt-cache", "policy", "openjdk-17-jre-headless") // «Candidate:» — без перевода if err == nil && strings.Contains(out, "Candidate:") && !strings.Contains(out, "Candidate: (none)") { return "openjdk-17-jre-headless" } diff --git a/deploy/iso/bj-firstboot b/deploy/iso/bj-firstboot index 358aa06..9412254 100755 --- a/deploy/iso/bj-firstboot +++ b/deploy/iso/bj-firstboot @@ -98,8 +98,9 @@ setup_accounts if [ ! -f /var/lib/bj/.bj/network-asked ] && [ -x /usr/local/sbin/bj-network ] && command -v netplan >/dev/null; then mkdir -p /var/lib/bj/.bj && date -Iseconds > /var/lib/bj/.bj/network-asked printf '\n Сейчас адрес сервера: %s\n' "$(ip -4 -o addr show scope global 2>/dev/null | awk '{print $4}' | tr '\n' ' ')" - read -t 60 -r -p " Задать постоянный адрес (рекомендуется)? [д/Н]: " ANS < /dev/tty || ANS="" - case "$ANS" in [дДyY]*) /usr/local/sbin/bj-network < /dev/tty ;; esac + # ответ цифрой: консоль начинает в английской раскладке, «д» без переключения не набрать + read -t 60 -r -p " Задать постоянный адрес (рекомендуется)? 1 — да, Enter — нет: " ANS < /dev/tty || ANS="" + case "$ANS" in 1*|[дДyYlL]*) /usr/local/sbin/bj-network < /dev/tty ;; esac fi clear printf '\n Bridge&Join: установка на сервер\n ────────────────────────────────────────────────────────────\n' diff --git a/deploy/iso/ubuntu/user-data.tmpl b/deploy/iso/ubuntu/user-data.tmpl index 9b62eb6..22a4a57 100644 --- a/deploy/iso/ubuntu/user-data.tmpl +++ b/deploy/iso/ubuntu/user-data.tmpl @@ -17,7 +17,7 @@ autoinstall: keyboard: layout: us,ru variant: "," - toggle: grp:alt_shift_toggle + toggle: alt_shift_toggle # установщик сам допишет «grp:» (с ним получалось «grp:grp:…», переключение не работало) timezone: Europe/Moscow identity: hostname: bj-server diff --git a/internal/release/install.go b/internal/release/install.go index 16c3ce3..2870249 100644 --- a/internal/release/install.go +++ b/internal/release/install.go @@ -145,7 +145,9 @@ func InstallStaged(staging string, opt InstallOptions) (res InstallResult) { if err != nil { return fail("служба %s: %v", j.c.Unit, err) } - if added || fresh { + // модуль впервые на сервере — его службу включить, но только если unit-файл уже есть: при установке + // с образа ядро ставится раньше своих служб (их кладёт следующий шаг установщика и включает сам) + if added || (fresh && unitInstalled(opt.Root, j.c.Unit)) { newUnits = append(newUnits, j.c.Unit) } } @@ -201,6 +203,16 @@ func ensureUnit(root string, units fs.FS, unit string) (bool, error) { return true, os.WriteFile(dst, b, 0o644) } +// unitInstalled — есть ли unit-файл службы (свой или из пакета). +func unitInstalled(root, unit string) bool { + for _, d := range []string{"/etc/systemd/system", "/lib/systemd/system", "/usr/lib/systemd/system"} { + if _, err := os.Stat(filepath.Join(root, d, unit+".service")); err == nil { + return true + } + } + return false +} + func contains(list []string, v string) bool { for _, x := range list { if x == v { diff --git a/internal/release/install_test.go b/internal/release/install_test.go index b700306..d3c2dfd 100644 --- a/internal/release/install_test.go +++ b/internal/release/install_test.go @@ -77,6 +77,14 @@ func TestInstallSplitModuleWithUnit(t *testing.T) { t.Fatalf("повторная установка не должна включать службу: %+v", res) } + // Установка ядра с образа: файла службы ещё нет (его кладёт следующий шаг установщика) — включать нечего, + // и это не ошибка (иначе systemctl enable падает: «Unit file … does not exist»). + root3 := t.TempDir() + res = InstallStaged(stageNPR(t, priv, `{"names":["bj-npr"],"new":["bj-npr"]}`), InstallOptions{Root: root3, PubKey: pub}) + if !res.OK || len(res.Enabled) != 0 { + t.Fatalf("без unit-файла служба не включается: %+v", res) + } + // Установка с образа: unit-файл уже положен (выключенным), файла модуля нет — служба включается. root2 := t.TempDir() if err := os.MkdirAll(filepath.Join(root2, "/etc/systemd/system"), 0o755); err != nil {