From 669f771f92ac067fd612c045a1bf1e48b1bf10b2 Mon Sep 17 00:00:00 2001 From: zuevav Date: Mon, 5 Oct 2026 20:33:16 +0300 Subject: [PATCH] =?UTF-8?q?=D0=AD=D0=BA=D1=80=D0=B0=D0=BD=20=D1=83=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D0=B8:=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BF=D1=83=D1=81=D0=BA=20=D0=BA=D0=B0=D0=BA=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B6=D0=BD=D0=BE=20=D1=80=D0=B0=D0=BD=D1=8C=D1=88=D0=B5=20(bo?= =?UTF-8?q?otcmd=20cloud-init,=20early-commands=20=E2=80=94=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BF=D0=B0=D1=81=D0=BD=D0=BE=D0=B9)=20=D0=BE=D1=82=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=BC=20=D1=81=D0=BA=D1=80=D0=B8?= =?UTF-8?q?=D0=BF=D1=82=D0=BE=D0=BC=20=D1=81=20=D1=87=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D1=8B=D0=BC=20=D0=BE=D0=BA=D1=80=D1=83=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=D0=BC=20=D0=B8=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8C?= =?UTF-8?q?=D1=8E=20=D0=B2=20=D0=B6=D1=83=D1=80=D0=BD=D0=B0=D0=BB=20(journ?= =?UTF-8?q?alctl=20-t=20bj-install-progress),=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=B0=D0=B7=D0=B5=20systemd-run=20=E2=80=94?= =?UTF-8?q?=20=D0=BF=D1=80=D1=8F=D0=BC=D0=BE=D0=B9=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=83=D1=81=D0=BA;=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20=D0=B4?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=B8=D1=82=20=D0=BA=D0=BE=D0=BD=D1=81=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=20(=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20=D1=83=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D1=89=D0=B8=D0=BA=D0=B0=20Ubuntu?= =?UTF-8?q?=20=D0=BE=D1=81=D1=82=D0=B0=D0=BD=D0=B0=D0=B2=D0=BB=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=B5=D1=82=D1=81=D1=8F,=20=D0=BA=D0=BE=D0=B3=D0=B4?= =?UTF-8?q?=D0=B0=20=D0=B1=D1=8B=20=D0=BD=D0=B8=20=D0=BF=D0=BE=D1=8F=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=D1=81=D1=8F;=20=D0=BF=D1=80=D0=B8=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BA=D0=B5=20=D0=B8=20=D0=B7=D0=B0=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=82=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=D0=BE=D0=B7=D0=B2?= =?UTF-8?q?=D1=80=D0=B0=D1=89=D0=B0=D0=B5=D1=82=D1=81=D1=8F),=20=D0=BF?= =?UTF-8?q?=D0=B0=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20=E2=80=94=20=D0=B2=20?= =?UTF-8?q?=D0=B6=D1=83=D1=80=D0=BD=D0=B0=D0=BB;=20=D1=82=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=BE=20=D1=81=D0=BE=D0=B1=D1=8B=D1=82=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=82=D0=B5=D0=BA=D1=83=D1=89=D0=B5=D0=B9=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8;=20loglevel=3D3=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D0=B7=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B5?= =?UTF-8?q?=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D1=89=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0?= 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 --- deploy/iso/bj-install-progress | 49 +++++++++++++++++++++++----- deploy/iso/bj-install-progress-start | 23 +++++++++++++ deploy/iso/build-iso-ubuntu.sh | 4 +-- deploy/iso/ubuntu/user-data.tmpl | 14 +++++--- 4 files changed, 74 insertions(+), 16 deletions(-) create mode 100755 deploy/iso/bj-install-progress-start diff --git a/deploy/iso/bj-install-progress b/deploy/iso/bj-install-progress index b713e8c..e272166 100644 --- a/deploy/iso/bj-install-progress +++ b/deploy/iso/bj-install-progress @@ -1,8 +1,9 @@ #!/usr/bin/python3 # Экран установки Bridge&Join на первой консоли — вместо технического журнала установщика Ubuntu # («start: subiquity/Install/install/curtin_install/…»). Запускается из early-commands автоустановки -# (deploy/iso/ubuntu/user-data.tmpl) отдельной службой с выводом на tty1 и сам останавливает экран -# установщика Ubuntu (snap.subiquity.subiquity-service). Сама установка идёт как шла: этот экран только +# (deploy/iso/ubuntu/user-data.tmpl: bootcmd — как можно раньше, early-commands — запасной) через +# bj-install-progress-start отдельной службой с выводом на tty1 и держит консоль за собой: экран установщика +# Ubuntu (snap.subiquity.subiquity-service) останавливает, как только тот появится. Сама установка идёт как шла: этот экран только # читает её события из журнала (SUBIQUITY_EVENT_TYPE / SUBIQUITY_CONTEXT_NAME) и состояние сервера # установщика (/run/subiquity/server-state). # @@ -93,6 +94,8 @@ class Progress: if state == "ERROR": self.error = True self.advance(stage_of("", "", state)) + if self.stage == 0 and not self.detail.startswith("ставится"): + self.detail = "запуск установщика…" if state in ("", "STARTING_UP", "CLOUD_INIT_WAIT", "EARLY_COMMANDS") else "" def percent(self): if self.stage >= DONE_STAGE: @@ -227,19 +230,42 @@ def demo(path, html_out): sys.stdout.write(to_ansi(fr)) +LIVE = "BJ_PROGRESS_STATE" not in os.environ # настоящая установка (не проверка) +PSF = "/snap/subiquity/current/subiquity.psf" # шрифт консоли установщика: кириллица и рамки + + +def hold_console(state, font_set): + """Консоль — за этим экраном: экран установщика Ubuntu, если запустился, остановить (кроме ошибки и + запроса подтверждения — тогда он нужен). Шрифт с кириллицей — как только смонтирован snap установщика.""" + if not LIVE: + return font_set + if state not in ("ERROR", "NEEDS_CONFIRMATION"): + if subprocess.call(["systemctl", "is-active", "--quiet", CLIENT_UNIT]) == 0: + subprocess.call(["systemctl", "stop", "--no-block", CLIENT_UNIT], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + sys.stderr.write("bj-install-progress: экран установщика Ubuntu остановлен\n") + if not font_set and os.path.exists(PSF): + font_set = subprocess.call(["setfont", PSF], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) == 0 + return font_set + + def main(): if len(sys.argv) >= 3 and sys.argv[1] == "--demo": demo(sys.argv[2], sys.argv[4] if len(sys.argv) >= 5 and sys.argv[3] == "--html" else "") return 0 - if "BJ_PROGRESS_STATE" not in os.environ: - # экран установщика Ubuntu больше не нужен: события читаем сами - subprocess.call(["systemctl", "stop", CLIENT_UNIT], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - for psf in ("/snap/subiquity/current/subiquity.psf",): - if os.path.exists(psf): - subprocess.call(["setfont", psf], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + try: + return run() + except Exception: # упал — в журнал; обёртка вернёт экран установщика Ubuntu + import traceback + traceback.print_exc() + return 1 + + +def run(): + sys.stderr.write("bj-install-progress: экран установки запущен\n") + font_set = hold_console(read_state(), False) sys.stdout.write("\033[?25l") # без мигающего курсора p = Progress() - j = subprocess.Popen(["journalctl", "-o", "json", "-f", "-n", "all", "-q", + j = subprocess.Popen(["journalctl", "-b", "-o", "json", "-f", "-n", "all", "-q", "SUBIQUITY_EVENT_TYPE=start", "SUBIQUITY_EVENT_TYPE=finish", "SUBIQUITY_EVENT_TYPE=error"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, bufsize=0) buf = b"" @@ -258,6 +284,11 @@ def main(): except ValueError: pass p.set_state(read_state()) + font_set = hold_console(p.state, font_set) + if p.state == "NEEDS_CONFIRMATION": # установщик ждёт подтверждения — это делает его экран + j.terminate() + sys.stdout.write("\033[?25h\033[H\033[2J") + return 3 cols, rows = shutil.get_terminal_size((100, 37)) sys.stdout.write(to_ansi(frame(p, cols, rows))) sys.stdout.flush() diff --git a/deploy/iso/bj-install-progress-start b/deploy/iso/bj-install-progress-start new file mode 100755 index 0000000..be02399 --- /dev/null +++ b/deploy/iso/bj-install-progress-start @@ -0,0 +1,23 @@ +#!/bin/sh +# Запуск экрана установки Bridge&Join (bj-install-progress) на первой консоли. Вызывается дважды: из +# bootcmd cloud-init — как можно раньше, пока установщик ещё стартует, — и из early-commands, если первый +# раз не вышло. Всегда завершается успешно: экран не должен мешать установке. Что получилось — в журнал +# (journalctl -t bj-install-progress). +P=/cdrom/bj/bj-install-progress +log() { logger -t bj-install-progress "$*" 2>/dev/null || true; } +CLEAN="env -i PATH=/usr/sbin:/usr/bin:/sbin:/bin LANG=C.UTF-8" # без окружения snap установщика +if systemctl is-active --quiet bj-install-progress.service 2>/dev/null; then + exit 0 # уже показывается +fi +if ! command -v python3 >/dev/null 2>&1; then log "нет python3 — экран не запущен"; exit 0; fi +if [ ! -f "$P" ]; then log "нет $P — экран не запущен"; exit 0; fi +# упал экран — вернуть экран установщика Ubuntu +CMD="python3 -I $P || systemctl start snap.subiquity.subiquity-service.service" +if OUT=$($CLEAN systemd-run --unit=bj-install-progress --collect -p StandardOutput=tty -p StandardError=journal \ + -p TTYPath=/dev/tty1 -p TTYReset=yes -p Environment=LANG=C.UTF-8 /bin/sh -c "$CMD" 2>&1); then + log "экран запущен ($1): $OUT" +else + log "systemd-run не сработал ($1): $OUT — запускаю напрямую" + $CLEAN setsid -f /bin/sh -c "$CMD" /dev/tty1 2>>/run/bj-install-progress.err || log "прямой запуск не удался" +fi +exit 0 diff --git a/deploy/iso/build-iso-ubuntu.sh b/deploy/iso/build-iso-ubuntu.sh index 9e3882e..bd0ec42 100755 --- a/deploy/iso/build-iso-ubuntu.sh +++ b/deploy/iso/build-iso-ubuntu.sh @@ -28,7 +28,7 @@ mkdir -p "$WORK/bj/bundle" "$WORK/bj/validata" "$WORK/nocloud" "$WORK/grub" install -m 0755 "$INSTALLER" "$WORK/bj/bj-installer" install -m 0755 "$HERE/bj-firstboot" "$WORK/bj/bj-firstboot" -install -m 0755 "$HERE/bj-install-progress" "$WORK/bj/bj-install-progress" # экран установки на консоли +install -m 0755 "$HERE/bj-install-progress" "$HERE/bj-install-progress-start" "$WORK/bj/" # экран установки на консоли cp "$HERE/bj-firstboot.service" "$WORK/bj/" install -m 0755 "$HERE/../linux/sbin/bj-welcome" "$WORK/bj/bj-welcome" install -m 0755 "$HERE/../linux/sbin/bj-network" "$WORK/bj/bj-network" @@ -108,7 +108,7 @@ menu() { fi echo "menuentry --hotkey=b '$T1' {" echo " set gfxpayload=keep" - echo " linux /casper/vmlinuz quiet autoinstall ds=nocloud\;s=/cdrom/nocloud/ ---" + echo " linux /casper/vmlinuz quiet loglevel=3 autoinstall ds=nocloud\;s=/cdrom/nocloud/ ---" echo " initrd /casper/initrd" echo "}" # прочие пункты образа Ubuntu — кроме ядра HWE (его файлы из образа удаляются); в BIOS ещё «с жёсткого diff --git a/deploy/iso/ubuntu/user-data.tmpl b/deploy/iso/ubuntu/user-data.tmpl index 527a46c..afebbcc 100644 --- a/deploy/iso/ubuntu/user-data.tmpl +++ b/deploy/iso/ubuntu/user-data.tmpl @@ -5,6 +5,12 @@ # ERASES THE WHOLE DISK» в меню загрузки: меню ждёт без таймера (build-iso-ubuntu.sh). # Учётная запись root_bj (sudo) создаётся с неизвестным никому паролем — настоящие пароли # root_bj и user_bj задаёт bj-firstboot при первой загрузке и показывает один раз. +# Экран установки Bridge&Join на консоли вместо технического журнала установщика Ubuntu +# (deploy/iso/bj-install-progress): этапы по-русски и полоса хода. bootcmd cloud-init — самое раннее, что +# доступно образу: экран появляется, пока установщик ещё запускается. Упадёт экран или установка не +# удастся — вернётся стандартный экран Ubuntu. Команда всегда завершается успешно. +bootcmd: + - [sh, -c, "sh /cdrom/bj/bj-install-progress-start bootcmd || true"] autoinstall: version: 1 locale: ru_RU.UTF-8 @@ -25,12 +31,10 @@ autoinstall: layout: name: direct updates: security - # Экран установки Bridge&Join на консоли вместо технического журнала установщика Ubuntu - # (deploy/iso/bj-install-progress): этапы по-русски и полоса хода. Экран сам останавливает показ - # журнала установщика; упадёт или установка не удастся — вернётся стандартный экран Ubuntu. - # Команда всегда завершается успешно: экран не должен мешать установке. + # Экран установки Bridge&Join (bj-install-progress) — запасной запуск, если bootcmd не сработал + # (основной — bootcmd в начале файла). Всегда завершается успешно. early-commands: - - sh -c 'if command -v python3 >/dev/null && [ -f /cdrom/bj/bj-install-progress ]; then systemd-run --unit=bj-install-progress --collect -p StandardOutput=tty -p StandardError=journal -p TTYPath=/dev/tty1 -p TTYReset=yes /bin/sh -c "python3 /cdrom/bj/bj-install-progress || systemctl start snap.subiquity.subiquity-service.service" >/dev/null 2>&1; fi; true' + - sh /cdrom/bj/bj-install-progress-start early-commands || true late-commands: - mkdir -p /target/var/lib/bj /target/etc/bj /target/usr/local/sbin - cp -r /cdrom/bj/bundle /target/var/lib/bj/bundle