diff --git a/deploy/iso/bj-install-progress b/deploy/iso/bj-install-progress new file mode 100644 index 0000000..b713e8c --- /dev/null +++ b/deploy/iso/bj-install-progress @@ -0,0 +1,274 @@ +#!/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). Сама установка идёт как шла: этот экран только +# читает её события из журнала (SUBIQUITY_EVENT_TYPE / SUBIQUITY_CONTEXT_NAME) и состояние сервера +# установщика (/run/subiquity/server-state). +# +# Ошибка установки — экран говорит об этом и возвращает стандартный экран установщика Ubuntu (он +# показывает подробности и открывает командную строку); то же, если сам этот экран упадёт +# (обёртка в early-commands). +# +# Шрифт консоли установщика (subiquity.psf, 512 знаков): есть кириллица, рамки, ✓ ▶ ✗ █ ░ · •; +# нет ○ ◐ ⚠ ▓ — их не использовать. +# +# Проверка без установки: bj-install-progress --demo <файл событий JSON> [--html кадр.html] + +import json +import math +import os +import select +import shutil +import subprocess +import sys +import time + +STAGES = [ + # заголовок, вес в общей полосе, типичная длительность этапа (с) — для плавной полосы внутри этапа + ("Подготовка", 4, 25), + ("Разметка диска", 4, 20), + ("Копирование системы на диск", 34, 180), + ("Настройка системы и загрузчика", 32, 180), + ("Пакеты и обновления безопасности", 18, 150), + ("Компоненты Bridge&Join", 8, 20), +] +DONE_STAGE = len(STAGES) +STATE_FILE = os.environ.get("BJ_PROGRESS_STATE", "/run/subiquity/server-state") # переменная — только для проверки +CLIENT_UNIT = "snap.subiquity.subiquity-service.service" + +# цвета консоли Linux +RESET, BOLD, DIM = "\033[0m", "\033[1m", "\033[2m" +GREEN, CYAN, RED, WHITE, GREY = "\033[32m", "\033[36m", "\033[31m", "\033[97m", "\033[90m" + + +def stage_of(name, message, state): + """Этап по событию установщика (имя контекста и сообщение) и состоянию сервера; -1 — не меняет этап.""" + n, m = name or "", message or "" + if state == "DONE": + return DONE_STAGE + if state == "LATE_COMMANDS" or n.startswith("subiquity/Late/run"): + return 5 + if state == "UU_RUNNING" or "/postinstall" in n: + return 4 + if "stage-curthooks" in n or "curthooks step" in m or "swap step" in m: + return 3 + if "stage-extract" in n or "extract step" in m: + return 2 + if "stage-partitioning" in n or "partitioning step" in m or "formatting step" in m: + return 1 + return -1 + + +class Progress: + def __init__(self): + self.stage = 0 + self.stage_started = time.time() + self.started = time.time() + self.state = "" + self.error = False + self.detail = "" + + def feed(self, ev): + """Событие журнала (JSON journalctl).""" + name = ev.get("SUBIQUITY_CONTEXT_NAME", "") + msg = (ev.get("MESSAGE") or "").strip() + etype = ev.get("SUBIQUITY_EVENT_TYPE", "") + if etype == "error": + self.detail = msg[:200] + st = stage_of(name, msg, "") + self.advance(st) + if self.stage == 4 and etype == "start": + if "/install_" in name and ": installing " in msg: + self.detail = "ставится " + msg.split(": installing ", 1)[1] + elif "updates" in msg: + self.detail = "загрузка и установка обновлений безопасности" + + def advance(self, st): + if st > self.stage: + self.stage, self.stage_started, self.detail = st, time.time(), "" + + def set_state(self, state): + self.state = state + if state == "ERROR": + self.error = True + self.advance(stage_of("", "", state)) + + def percent(self): + if self.stage >= DONE_STAGE: + return 100 + done = sum(w for _, w, _ in STAGES[: self.stage]) + _, w, tau = STAGES[self.stage] + f = min(0.95, 1 - math.exp(-(time.time() - self.stage_started) / tau)) + return min(99, int(done + w * f)) + + +def mmss(sec): + sec = int(max(0, sec)) + return "%d:%02d" % (sec // 60, sec % 60) + + +def frame(p, cols, rows): + """Кадр: список строк, каждая — список кусков (текст, цвет).""" + W = 66 + inner = W - 2 + lines = [] + + def row(parts=()): + parts, text = list(parts), "" + for i, (t, c) in enumerate(parts): # строка длиннее рамки — обрезать, рамка не должна съезжать + if len(text) + len(t) > inner: + parts = parts[:i] + [(t[: inner - len(text)], c)] + text += parts[-1][0] + break + text += t + pad = inner - len(text) + lines.append([("║", GREY)] + list(parts) + [(" " * max(0, pad), ""), ("║", GREY)]) + + lines.append([("╔" + "═" * inner + "╗", GREY)]) + row() + row([(" Bridge&Join", BOLD + WHITE)]) + row([(" шлюз, объединяющий системы финансовой организации", DIM)]) + row() + row([(" Установка сервера", BOLD)]) + row() + for i, (title, _, _) in enumerate(STAGES): + if i < p.stage: + row([(" ✓ ", GREEN), (title, "")]) + elif i == p.stage and p.error: + row([(" ✗ ", RED), (title, BOLD + RED)]) + elif i == p.stage: + t = mmss(time.time() - p.stage_started) + gap = inner - 6 - len(title) - len(t) - 3 + row([(" ▶ ", CYAN), (title, BOLD + CYAN), (" " * max(1, gap), ""), (t, DIM)]) + else: + row([(" · ", GREY), (title, GREY)]) + row() + if p.error: + row([(" ✗ Установка не удалась.", BOLD + RED)]) + row([(" Сейчас откроется экран установщика Ubuntu", "")]) + row([(" с подробностями и командной строкой.", "")]) + else: + pct = p.percent() + bar_w = 46 + full = int(bar_w * pct / 100) + row([(" ", ""), ("█" * full, CYAN), ("░" * (bar_w - full), GREY), (" %3d %%" % pct, BOLD)]) + row([(" " + p.detail, DIM)] if p.detail and p.stage < DONE_STAGE else []) # подпись под полосой + row() + if p.stage >= DONE_STAGE: + row([(" ✓ Установка завершена — сервер перезагружается.", BOLD + GREEN)]) + row([(" После загрузки на этом экране будет адрес для входа.", "")]) + else: + row([(" Не выключайте сервер: он перезагрузится сам и покажет", "")]) + row([(" на этом экране адрес для входа в браузере.", "")]) + row() + lines.append([("╚" + "═" * inner + "╝", GREY)]) + foot = "прошло " + mmss(time.time() - p.started) + lines.append([(" " * max(0, (W - len(foot)) // 2) + foot, GREY)]) + # по центру экрана + left = max(0, (cols - W) // 2) + top = max(0, (rows - len(lines)) // 2) + return [[("", "")]] * top + [[(" " * left, "")] + ln for ln in lines] + + +def to_ansi(fr): + out = ["\033[H\033[2J"] + for ln in fr: + out.append("".join((c + t + RESET) if c else t for t, c in ln) + "\n") + return "".join(out) + + +def to_html(fr): + css = {GREEN: "#4e9a06", CYAN: "#06989a", RED: "#cc0000", GREY: "#888", DIM: "#9a9a9a", BOLD: "", WHITE: "#fff"} + html = [] + for ln in fr: + h = "" + for t, c in ln: + t = t.replace("&", "&").replace("<", "<") + if not c: + h += t + continue + color = next((css[k] for k in (GREEN, CYAN, RED, GREY, WHITE, DIM) if k in c), "#ddd") + weight = "700" if BOLD in c else "400" + h += '%s' % (color, weight, t) + html.append(h) + return ('
' + '' + + "\n".join(html) + "") + + +def read_state(): + try: + with open(STATE_FILE) as f: + return f.read().strip() + except OSError: + return "" + + +def demo(path, html_out): + """Прогон по записанным событиям: кадр в HTML (для проверки вида без установки).""" + p = Progress() + for line in open(path, encoding="utf-8"): + line = line.strip() + if not line: + continue + ev = json.loads(line) + if "STATE" in ev: + p.set_state(ev["STATE"]) + else: + p.feed(ev) + if "ELAPSED" in ev: # сдвиг времени для наглядности кадра + p.stage_started -= ev["ELAPSED"] + p.started -= ev["ELAPSED"] * 3 + fr = frame(p, 100, 37) + if html_out: + open(html_out, "w", encoding="utf-8").write(to_html(fr)) + else: + sys.stdout.write(to_ansi(fr)) + + +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) + sys.stdout.write("\033[?25l") # без мигающего курсора + p = Progress() + j = subprocess.Popen(["journalctl", "-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"" + error_at = 0.0 + while True: + r, _, _ = select.select([j.stdout], [], [], 1.0) + if r: + chunk = os.read(j.stdout.fileno(), 65536) + if not chunk and j.poll() is not None: + return 1 # журнал недоступен — обёртка вернёт экран установщика + buf += chunk + while b"\n" in buf: + line, buf = buf.split(b"\n", 1) + try: + p.feed(json.loads(line.decode("utf-8", "replace"))) + except ValueError: + pass + p.set_state(read_state()) + cols, rows = shutil.get_terminal_size((100, 37)) + sys.stdout.write(to_ansi(frame(p, cols, rows))) + sys.stdout.flush() + if p.error: + if not error_at: + error_at = time.time() + elif time.time() - error_at > 6: + j.terminate() + sys.stdout.write("\033[?25h\033[H\033[2J") + return 2 # обёртка запустит экран установщика Ubuntu: подробности и командная строка + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/deploy/iso/build-iso-ubuntu.sh b/deploy/iso/build-iso-ubuntu.sh index 0787882..9e3882e 100755 --- a/deploy/iso/build-iso-ubuntu.sh +++ b/deploy/iso/build-iso-ubuntu.sh @@ -28,6 +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" # экран установки на консоли 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" @@ -91,21 +92,53 @@ HASH=$(openssl passwd -6 "$(head -c 32 /dev/urandom | base64)") sed "s#@@PASSHASH@@#$HASH#" "$HERE/ubuntu/user-data.tmpl" > "$WORK/nocloud/user-data" : > "$WORK/nocloud/meta-data" -# меню загрузки: первый пункт — установка Bridge&Join +# меню загрузки: первый пункт — установка Bridge&Join. В графическом режиме — по-русски, с оформлением +# grub-theme/ (в текстовом режиме GRUB кириллицы нет — тогда те же пункты по-английски). xorriso -osirrox on -indev "$SRC" -extract /boot/grub/grub.cfg "$WORK/grub/grub.orig" >/dev/null 2>&1 +mkdir -p "$WORK/boot/grub/themes/bj" +cp "$HERE"/grub-theme/theme.txt "$HERE"/grub-theme/*.pf2 "$HERE"/grub-theme/*.png "$WORK/boot/grub/themes/bj/" +# пункты меню: $1 — ru|en +menu() { + if [ "$1" = ru ]; then + T1="Установить Bridge&Join $VERSION — стирает весь диск"; T2="Установить Ubuntu Server вручную, без Bridge&Join" + T3="Загрузиться со следующего диска"; T4="Настройки UEFI"; T5="Проверка памяти"; T6="Загрузиться с жёсткого диска" + else + T1="Install Bridge&Join $VERSION - ERASES THE WHOLE DISK"; T2="Try or Install Ubuntu Server" + T3="Boot from next volume"; T4="UEFI Firmware Settings"; T5="Test memory"; T6="Boot from first hard disk" + fi + echo "menuentry --hotkey=b '$T1' {" + echo " set gfxpayload=keep" + echo " linux /casper/vmlinuz quiet autoinstall ds=nocloud\;s=/cdrom/nocloud/ ---" + echo " initrd /casper/initrd" + echo "}" + # прочие пункты образа Ubuntu — кроме ядра HWE (его файлы из образа удаляются); в BIOS ещё «с жёсткого + # диска» — если образ остался подключённым после установки (в UEFI для этого есть «со следующего диска») + sed '/^set timeout/d;/^loadfont/d;/^set menu_color/d' "$WORK/grub/grub.orig" \ + | awk '/^menuentry "Ubuntu Server with the HWE kernel"/{skip=1} skip&&/^}/{skip=0;next} !skip' \ + | sed "s|^menuentry \"Try or Install Ubuntu Server\"|menuentry '$(sedrepl "$T2")'|;s|^menuentry 'Boot from next volume'|menuentry '$(sedrepl "$T3")'|;s|^menuentry 'UEFI Firmware Settings'|menuentry '$(sedrepl "$T4")'|;s|^menuentry 'Test memory'|menuentry '$(sedrepl "$T6")' {\\n\\tset root=(hd0)\\n\\tchainloader +1\\n}\\nmenuentry '$(sedrepl "$T5")'|" +} +# текст для правой части s|…|…| в sed: & и | — буквально +sedrepl() { printf '%s' "$1" | sed 's/[&|\\]/\\&/g'; } { echo "set default=0" # меню ждёт выбора: выбор пункта установки и есть подтверждение стирания диска (без таймера образ, # случайно оставленный подключённым, не переустановит систему сам) echo "set timeout=-1" echo "loadfont unicode" - echo "menuentry --hotkey=b 'Install Bridge&Join $VERSION - ERASES THE WHOLE DISK' {" - echo " set gfxpayload=keep" - echo " linux /casper/vmlinuz autoinstall ds=nocloud\;s=/cdrom/nocloud/ ---" - echo " initrd /casper/initrd" - echo "}" - # пункт с ядром HWE убираем — его файлы из образа удаляются - sed '/^set timeout/d;/^loadfont/d' "$WORK/grub/grub.orig" | awk '/^menuentry "Ubuntu Server with the HWE kernel"/{skip=1} skip&&/^}/{skip=0;next} !skip' + echo "set menu_color_normal=white/black" + echo "set menu_color_highlight=black/light-gray" + echo "insmod all_video" + echo "insmod gfxterm" + echo "insmod gfxmenu" + echo "insmod png" + echo "set gfxmode=1024x768,auto" + echo "if terminal_output gfxterm; then" + for f in "$WORK"/boot/grub/themes/bj/*.pf2; do echo " loadfont /boot/grub/themes/bj/$(basename "$f")"; done + echo " set theme=/boot/grub/themes/bj/theme.txt" + menu ru + echo "else" + menu en + echo "fi" } > "$WORK/grub/grub.cfg" # md5sum.txt: установщик Ubuntu при загрузке сверяет файлы образа по этому списку (casper-md5check); @@ -121,14 +154,14 @@ xorriso -osirrox on -indev "$SRC" -extract /md5sum.txt "$WORK/md5.orig" >/dev/nu grep -E '\./casper/install-sources\.yaml$' "$WORK/md5.orig" fi echo "$(md5sum < "$WORK/grub/grub.cfg" | cut -d' ' -f1) ./boot/grub/grub.cfg" - (cd "$WORK" && find bj nocloud -type f | sort | while read -r f; do echo "$(md5sum < "$f" | cut -d' ' -f1) ./$f"; done) + (cd "$WORK" && find bj nocloud boot/grub/themes -type f | sort | while read -r f; do echo "$(md5sum < "$f" | cut -d' ' -f1) ./$f"; done) } > "$WORK/md5sum.txt" rm -f "$OUT" xorriso -indev "$SRC" -outdev "$OUT" -boot_image any replay \ -volid "BJ_UBUNTU22" \ -map "$WORK/bj" /bj -map "$WORK/nocloud" /nocloud \ - -map "$WORK/grub/grub.cfg" /boot/grub/grub.cfg \ + -map "$WORK/grub/grub.cfg" /boot/grub/grub.cfg -map "$WORK/boot/grub/themes" /boot/grub/themes \ -map "$WORK/md5sum.txt" /md5sum.txt $LMAP \ -rm_r /casper/hwe-vmlinuz /casper/hwe-initrd /casper/ubuntu-server-minimal.ubuntu-server.installer.generic-hwe.squashfs $LRM -- \ -end >/dev/null 2>"$WORK/xorriso.log" || { cat "$WORK/xorriso.log"; exit 1; } diff --git a/deploy/iso/grub-theme/background.png b/deploy/iso/grub-theme/background.png new file mode 100644 index 0000000..460734b Binary files /dev/null and b/deploy/iso/grub-theme/background.png differ diff --git a/deploy/iso/grub-theme/bj-mono-16.pf2 b/deploy/iso/grub-theme/bj-mono-16.pf2 new file mode 100644 index 0000000..c593210 Binary files /dev/null and b/deploy/iso/grub-theme/bj-mono-16.pf2 differ diff --git a/deploy/iso/grub-theme/bj-sans-16.pf2 b/deploy/iso/grub-theme/bj-sans-16.pf2 new file mode 100644 index 0000000..8a6fd10 Binary files /dev/null and b/deploy/iso/grub-theme/bj-sans-16.pf2 differ diff --git a/deploy/iso/grub-theme/bj-sans-20.pf2 b/deploy/iso/grub-theme/bj-sans-20.pf2 new file mode 100644 index 0000000..494975c Binary files /dev/null and b/deploy/iso/grub-theme/bj-sans-20.pf2 differ diff --git a/deploy/iso/grub-theme/bj-sans-bold-20.pf2 b/deploy/iso/grub-theme/bj-sans-bold-20.pf2 new file mode 100644 index 0000000..8e68063 Binary files /dev/null and b/deploy/iso/grub-theme/bj-sans-bold-20.pf2 differ diff --git a/deploy/iso/grub-theme/bj-sans-bold-36.pf2 b/deploy/iso/grub-theme/bj-sans-bold-36.pf2 new file mode 100644 index 0000000..8824774 Binary files /dev/null and b/deploy/iso/grub-theme/bj-sans-bold-36.pf2 differ diff --git a/deploy/iso/grub-theme/make-theme.sh b/deploy/iso/grub-theme/make-theme.sh new file mode 100755 index 0000000..10bd3d8 --- /dev/null +++ b/deploy/iso/grub-theme/make-theme.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Пересобрать оформление меню загрузки образа (шрифты GRUB и картинки) — результат в этом же каталоге. +# Нужны grub-mkfont (grub-common), шрифты DejaVu и ImageMagick (convert). theme.txt правится руками. +set -eu +cd "$(dirname "$0")" +D=/usr/share/fonts/truetype/dejavu +# латиница, кириллица, тире, точка, кавычки, стрелки — больше в меню ничего нет +R=0x20-0x7E,0xA0-0xFF,0x400-0x45F,0x2010-0x2027,0x2190-0x2193,0x2039-0x203A +grub-mkfont -r $R -s 36 -o bj-sans-bold-36.pf2 $D/DejaVuSans-Bold.ttf +grub-mkfont -r $R -s 20 -o bj-sans-bold-20.pf2 $D/DejaVuSans-Bold.ttf +grub-mkfont -r $R -s 20 -o bj-sans-20.pf2 $D/DejaVuSans.ttf +grub-mkfont -r $R -s 16 -o bj-sans-16.pf2 $D/DejaVuSans.ttf +grub-mkfont -r $R -s 16 -o bj-mono-16.pf2 $D/DejaVuSansMono.ttf +# фон: тёмный, чуть светлее к центру +convert -size 320x240 radial-gradient:'#172036'-'#0a0e18' -depth 8 PNG24:background.png # GRUB растянет на экран; PNG24 — палитру GRUB не читает +# подсветка выбранного пункта: 9 частей рамки одного цвета (углы 4×4) +for p in nw ne sw se; do convert -size 4x4 xc:'#2f55d4' PNG24:select_$p.png; done +for p in n s; do convert -size 1x4 xc:'#2f55d4' PNG24:select_$p.png; done +for p in w e; do convert -size 4x1 xc:'#2f55d4' PNG24:select_$p.png; done +convert -size 1x1 xc:'#2f55d4' PNG24:select_c.png +ls -la diff --git a/deploy/iso/grub-theme/select_c.png b/deploy/iso/grub-theme/select_c.png new file mode 100644 index 0000000..2653f20 Binary files /dev/null and b/deploy/iso/grub-theme/select_c.png differ diff --git a/deploy/iso/grub-theme/select_e.png b/deploy/iso/grub-theme/select_e.png new file mode 100644 index 0000000..f63a577 Binary files /dev/null and b/deploy/iso/grub-theme/select_e.png differ diff --git a/deploy/iso/grub-theme/select_n.png b/deploy/iso/grub-theme/select_n.png new file mode 100644 index 0000000..e06a4aa Binary files /dev/null and b/deploy/iso/grub-theme/select_n.png differ diff --git a/deploy/iso/grub-theme/select_ne.png b/deploy/iso/grub-theme/select_ne.png new file mode 100644 index 0000000..a088a4f Binary files /dev/null and b/deploy/iso/grub-theme/select_ne.png differ diff --git a/deploy/iso/grub-theme/select_nw.png b/deploy/iso/grub-theme/select_nw.png new file mode 100644 index 0000000..a088a4f Binary files /dev/null and b/deploy/iso/grub-theme/select_nw.png differ diff --git a/deploy/iso/grub-theme/select_s.png b/deploy/iso/grub-theme/select_s.png new file mode 100644 index 0000000..65732b8 Binary files /dev/null and b/deploy/iso/grub-theme/select_s.png differ diff --git a/deploy/iso/grub-theme/select_se.png b/deploy/iso/grub-theme/select_se.png new file mode 100644 index 0000000..a088a4f Binary files /dev/null and b/deploy/iso/grub-theme/select_se.png differ diff --git a/deploy/iso/grub-theme/select_sw.png b/deploy/iso/grub-theme/select_sw.png new file mode 100644 index 0000000..a088a4f Binary files /dev/null and b/deploy/iso/grub-theme/select_sw.png differ diff --git a/deploy/iso/grub-theme/select_w.png b/deploy/iso/grub-theme/select_w.png new file mode 100644 index 0000000..f63a577 Binary files /dev/null and b/deploy/iso/grub-theme/select_w.png differ diff --git a/deploy/iso/grub-theme/theme.txt b/deploy/iso/grub-theme/theme.txt new file mode 100644 index 0000000..ed0bf68 --- /dev/null +++ b/deploy/iso/grub-theme/theme.txt @@ -0,0 +1,66 @@ +# Bridge&Join — оформление меню загрузки образа (GRUB, графический режим). +# Шрифты и картинки — make-theme.sh; в шрифтах только латиница, кириллица, тире и стрелки. +title-text: "" +desktop-image: "background.png" +desktop-image-scale-method: "stretch" +desktop-color: "#0d1220" +terminal-font: "DejaVu Sans Mono Regular 16" + ++ label { + left = 0 + top = 13% + width = 100% + height = 48 + align = "center" + text = "Bridge&Join" + font = "DejaVu Sans Bold 36" + color = "#ffffff" +} ++ label { + left = 0 + top = 13%+54 + width = 100% + height = 24 + align = "center" + text = "шлюз, объединяющий системы финансовой организации" + font = "DejaVu Sans Regular 16" + color = "#8d98ad" +} ++ boot_menu { + left = 50%-380 + top = 31% + width = 760 + height = 260 + item_font = "DejaVu Sans Regular 20" + selected_item_font = "DejaVu Sans Bold 20" + item_color = "#c3cad6" + selected_item_color = "#ffffff" + item_height = 44 + item_padding = 16 + item_spacing = 6 + icon_width = 0 + icon_height = 0 + item_icon_space = 0 + selected_item_pixmap_style = "select_*.png" + scrollbar = false +} ++ label { + left = 50%-380 + top = 31%+272 + width = 760 + height = 24 + align = "center" + text = "Первый пункт устанавливает Bridge&Join и стирает весь диск сервера" + font = "DejaVu Sans Regular 16" + color = "#f0a35e" +} ++ label { + left = 0 + top = 90% + width = 100% + height = 24 + align = "center" + text = "↑ ↓ — выбрать пункт, Enter — запустить" + font = "DejaVu Sans Regular 16" + color = "#8d98ad" +} diff --git a/deploy/iso/ubuntu/user-data.tmpl b/deploy/iso/ubuntu/user-data.tmpl index 03af5fc..527a46c 100644 --- a/deploy/iso/ubuntu/user-data.tmpl +++ b/deploy/iso/ubuntu/user-data.tmpl @@ -25,6 +25,12 @@ autoinstall: layout: name: direct updates: security + # Экран установки Bridge&Join на консоли вместо технического журнала установщика Ubuntu + # (deploy/iso/bj-install-progress): этапы по-русски и полоса хода. Экран сам останавливает показ + # журнала установщика; упадёт или установка не удастся — вернётся стандартный экран Ubuntu. + # Команда всегда завершается успешно: экран не должен мешать установке. + 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' 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