Образ: меню загрузки по-русски в оформлении Bridge&Join (GRUB в графическом режиме, тема grub-theme/; без графики — те же пункты по-английски), в BIOS пункт «Загрузиться с жёсткого диска»; экран установки на консоли вместо журнала установщика Ubuntu — этапы по-русски и полоса хода (bj-install-progress из early-commands; при ошибке или сбое экрана возвращается стандартный экран Ubuntu); quiet при загрузке установщика
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014FsEKyJrYF69Cj9ZXzFLRi
@@ -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 += '<span style="color:%s;font-weight:%s">%s</span>' % (color, weight, t)
|
||||
html.append(h)
|
||||
return ('<!doctype html><meta charset="utf-8"><body style="margin:0;background:#000">'
|
||||
'<pre style="margin:0;padding:8px;font:16px/1 \'DejaVu Sans Mono\',monospace;color:#ddd;background:#000">'
|
||||
+ "\n".join(html) + "</pre>")
|
||||
|
||||
|
||||
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())
|
||||
@@ -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; }
|
||||
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -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
|
||||
|
After Width: | Height: | Size: 245 B |
|
After Width: | Height: | Size: 247 B |
|
After Width: | Height: | Size: 250 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 250 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 253 B |
|
After Width: | Height: | Size: 247 B |
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||