Загрузочное меню ISO было оформлено в стиле Virtuality
- графическая тема GRUB (фон, подсветка выбранного пункта, полоса автозапуска) в image/grub, подключается в build-iso.sh; при отсутствии модулей GRUB меню остаётся текстовым; - пункты меню переведены: «Установить Virtuality X.Y.Z», «Загрузить с другого диска», «Настройки UEFI», «Проверить память»; - tests/render_html.mjs рендерит HTML в PNG (для фона темы). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019LEUXMcdZo7b87mjkUsPgg
@@ -216,20 +216,41 @@ import sys
|
||||
path, unattended, version = sys.argv[1], sys.argv[2] == "1", sys.argv[3]
|
||||
text = open(path).read()
|
||||
# /autoinstall.yaml applies to every entry, so all Ubuntu entries become Virtuality entries.
|
||||
suffix = " (UNATTENDED: erases disk)" if unattended else ""
|
||||
text = text.replace('menuentry "Try or Install Ubuntu Server"', 'menuentry "Install Virtuality %s%s"' % (version, suffix), 1)
|
||||
text = text.replace('menuentry "Ubuntu Server with the HWE kernel"', 'menuentry "Install Virtuality %s with the HWE kernel%s"' % (version, suffix), 1)
|
||||
if "Install Virtuality" not in text:
|
||||
suffix = " — автоматически, диск будет стёрт" if unattended else ""
|
||||
text = text.replace('menuentry "Try or Install Ubuntu Server"', 'menuentry "Установить Virtuality %s%s"' % (version, suffix), 1)
|
||||
text = text.replace('menuentry "Ubuntu Server with the HWE kernel"', 'menuentry "Установить Virtuality %s (новое ядро HWE)%s"' % (version, suffix), 1)
|
||||
text = text.replace("menuentry 'Boot from next volume'", "menuentry 'Загрузить с другого диска'", 1)
|
||||
text = text.replace("menuentry 'UEFI Firmware Settings'", "menuentry 'Настройки UEFI'", 1)
|
||||
text = text.replace("menuentry 'Test memory'", "menuentry 'Проверить память'", 1)
|
||||
if "Установить Virtuality" not in text:
|
||||
raise SystemExit("unexpected grub.cfg menu titles")
|
||||
if unattended:
|
||||
text = re.sub(r"^(\s*linux\s+\S+\s+)---", r"\1autoinstall ---", text, flags=re.M)
|
||||
text = re.sub(r"^set timeout=\d+", "set default=0\nset timeout=10", text, count=1, flags=re.M)
|
||||
open(path, "w").write(text)
|
||||
text = re.sub(r"^set timeout=\d+\n", "", text, count=1, flags=re.M)
|
||||
text = text.replace("loadfont unicode\n", "", 1)
|
||||
# Branded graphical menu; if a module or the font is missing GRUB falls back to the plain text menu.
|
||||
theme = """insmod all_video
|
||||
insmod gfxterm
|
||||
insmod png
|
||||
insmod gfxmenu
|
||||
set gfxmode=1024x768,auto
|
||||
if loadfont /boot/grub/fonts/unicode.pf2; then
|
||||
terminal_output gfxterm
|
||||
set theme=/boot/grub/themes/virtuality/theme.txt
|
||||
export theme
|
||||
fi
|
||||
set default=0
|
||||
set timeout=10
|
||||
"""
|
||||
open(path, "w").write(theme + text)
|
||||
PYEOF
|
||||
new_md5="$(md5sum "${WORK_DIR}/grub.cfg" | cut -d' ' -f1)"
|
||||
sed -i "s|^[0-9a-f]\{32\} \./boot/grub/grub.cfg$|${new_md5} ./boot/grub/grub.cfg|" "${WORK_DIR}/md5sum.txt"
|
||||
|
||||
# ---------------------------------------------------------- repack
|
||||
THEME_DIR="${WORK_DIR}/theme"
|
||||
mkdir -p "$THEME_DIR"
|
||||
cp "${IMAGE_DIR}"/grub/theme.txt "${IMAGE_DIR}"/grub/*.png "$THEME_DIR"/
|
||||
say "Writing ${OUTPUT_ISO}"
|
||||
rm -f "$OUTPUT_ISO"
|
||||
chmod -R a+rX,go-w "$PAYLOAD"
|
||||
@@ -238,6 +259,7 @@ if ! xorriso -indev "$UBUNTU_ISO" -outdev "$OUTPUT_ISO" \
|
||||
-map "$PAYLOAD" /virtuality \
|
||||
-map "${WORK_DIR}/autoinstall.yaml" /autoinstall.yaml \
|
||||
-map "${WORK_DIR}/grub.cfg" /boot/grub/grub.cfg \
|
||||
-map "${THEME_DIR}" /boot/grub/themes/virtuality \
|
||||
-map "${WORK_DIR}/md5sum.txt" /md5sum.txt \
|
||||
-boot_image any replay > "${WORK_DIR}/xorriso.log" 2>&1; then
|
||||
tail -n 30 "${WORK_DIR}/xorriso.log" >&2
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Тема загрузочного меню
|
||||
|
||||
`theme.txt` — тема GRUB (gfxmenu) для установочного ISO, `background.png` — фон 1024×768,
|
||||
`select_*.png` — подсветка выбранного пункта.
|
||||
|
||||
Фон рендерится из `background.html` (тот же стиль, что у страницы входа панели):
|
||||
|
||||
```bash
|
||||
NODE_PATH=$(npm root -g) node tests/render_html.mjs image/grub/background.html image/grub/background.png 1024 768
|
||||
```
|
||||
|
||||
Пиксмапы подсветки — однотонные RGBA 6×6 (110, 96, 255, 150).
|
||||
@@ -0,0 +1,19 @@
|
||||
<!doctype html><html><head><meta charset="utf-8"><style>
|
||||
html,body{margin:0;width:1024px;height:768px;overflow:hidden;font-family:Inter,"Segoe UI",Roboto,Helvetica,Arial,sans-serif}
|
||||
body{background:radial-gradient(900px 520px at 8% 0%, rgba(123,107,255,.55), transparent 60%), radial-gradient(760px 520px at 100% 100%, rgba(47,115,240,.45), transparent 55%), #0b0a1f;color:#fff;position:relative}
|
||||
.grid{position:absolute;inset:0;background-image:linear-gradient(rgba(255,255,255,.045) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.045) 1px,transparent 1px);background-size:48px 48px;-webkit-mask-image:radial-gradient(ellipse at 30% 30%,#000 15%,transparent 72%)}
|
||||
.brand{position:absolute;left:72px;top:64px;display:flex;align-items:center;gap:18px}
|
||||
.mark{width:64px;height:64px;border-radius:18px;background:linear-gradient(135deg,#7b6bff 0%,#5a48f5 45%,#2f73f0 100%);box-shadow:0 14px 40px -10px rgba(90,72,245,.9), inset 0 1px 0 rgba(255,255,255,.3);display:grid;place-items:center}
|
||||
.mark svg{width:38px;height:38px}
|
||||
.name{font-size:38px;font-weight:700;letter-spacing:-.02em;line-height:1}
|
||||
.name small{display:block;margin-top:8px;font-size:16px;font-weight:500;color:rgba(255,255,255,.62);letter-spacing:0}
|
||||
.tag{position:absolute;left:72px;top:176px;font-size:30px;font-weight:600;letter-spacing:-.02em;line-height:1.25;max-width:620px}
|
||||
.tag span{background:linear-gradient(90deg,#c4bcff,#8fb8ff);-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||
.foot{position:absolute;left:72px;bottom:56px;color:rgba(255,255,255,.5);font-size:15px}
|
||||
.hint{position:absolute;right:72px;bottom:56px;color:rgba(255,255,255,.5);font-size:15px;text-align:right}
|
||||
</style></head><body><div class="grid"></div>
|
||||
<div class="brand"><div class="mark"><svg viewBox="0 0 24 24" fill="none"><path d="M4.5 5.5 12 19l7.5-13.5" stroke="#fff" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/><path d="M9 5.5h6" stroke="#fff" stroke-opacity=".55" stroke-width="2.6" stroke-linecap="round"/></svg></div><div class="name">Virtuality<small>Платформа виртуализации</small></div></div>
|
||||
<div class="tag">Ваши серверы.<br><span>Под полным контролем.</span></div>
|
||||
<div class="foot">Установщик · KVM · QEMU · libvirt</div>
|
||||
<div class="hint">↑ ↓ — выбор пункта, Enter — запуск</div>
|
||||
</body></html>
|
||||
|
После Ширина: | Высота: | Размер: 151 KiB |
|
После Ширина: | Высота: | Размер: 75 B |
|
После Ширина: | Высота: | Размер: 75 B |
|
После Ширина: | Высота: | Размер: 75 B |
|
После Ширина: | Высота: | Размер: 75 B |
|
После Ширина: | Высота: | Размер: 75 B |
|
После Ширина: | Высота: | Размер: 75 B |
|
После Ширина: | Высота: | Размер: 75 B |
|
После Ширина: | Высота: | Размер: 75 B |
|
После Ширина: | Высота: | Размер: 75 B |
@@ -0,0 +1,41 @@
|
||||
desktop-image: "background.png"
|
||||
desktop-image-scale-method: "stretch"
|
||||
desktop-color: "#0b0a1f"
|
||||
title-text: ""
|
||||
terminal-font: "Unicode Regular"
|
||||
terminal-left: "7%"
|
||||
terminal-top: "36%"
|
||||
terminal-width: "86%"
|
||||
terminal-height: "50%"
|
||||
terminal-border: "0"
|
||||
|
||||
+ boot_menu {
|
||||
left = 7%
|
||||
top = 36%
|
||||
width = 60%
|
||||
height = 42%
|
||||
item_font = "Unicode Regular"
|
||||
item_color = "#b9b8cf"
|
||||
selected_item_color = "#ffffff"
|
||||
item_height = 36
|
||||
item_padding = 10
|
||||
item_spacing = 6
|
||||
icon_width = 0
|
||||
icon_height = 0
|
||||
scrollbar = false
|
||||
selected_item_pixmap_style = "select_*.png"
|
||||
}
|
||||
|
||||
+ progress_bar {
|
||||
id = "__timeout__"
|
||||
left = 7%
|
||||
top = 86%
|
||||
width = 60%
|
||||
height = 24
|
||||
font = "Unicode Regular"
|
||||
text_color = "#ffffff"
|
||||
fg_color = "#7b6bff"
|
||||
bg_color = "#1c1a36"
|
||||
border_color = "#2a2748"
|
||||
text = "Автоматический запуск через %d с"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createRequire } from 'node:module';
|
||||
const { chromium } = createRequire(import.meta.url)('playwright');
|
||||
// Render an HTML file to PNG. Usage: node tests/render_html.mjs in.html out.png [width] [height]
|
||||
const [,, html, out, width = '1024', height = '768'] = process.argv;
|
||||
const browser = await chromium.launch();
|
||||
const page = await browser.newPage({ viewport: { width: +width, height: +height }, deviceScaleFactor: 1 });
|
||||
await page.goto('file://' + (html.startsWith('/') ? html : process.cwd() + '/' + html));
|
||||
await page.waitForTimeout(300);
|
||||
await page.screenshot({ path: out, type: 'png' });
|
||||
await browser.close();
|
||||