[INSTALL SPEED] Configure target keyboard without temporary boot (#93)
* Configure target keyboard without temporary boot * Slim the offline keyboard configuration to what Omarchy consumes Drop the Xorg 00-keyboard.conf writer: the installed system only reads the XKB* settings in vconsole.conf (via detect-keyboard-layout.sh, which copies XKBLAYOUT/XKBVARIANT into Hyprland's kb_layout), and nothing on an Omarchy system reads xorg.conf.d. systemd-firstboot already writes those vconsole settings. Drop the set_keyboard_language monkey-patch: passing an empty kb_layout to minimal_installation takes archinstall's documented skip path (no nspawn boot), and the keymap is configured offline right after instead. Append the preserved FONT= line instead of reproducing localectl's exact file layout, and slim the tests to the behavior that matters: XKB settings present, font preserved, every configurator keymap valid, and archinstall-compatible invalid/empty handling. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: David Heinemeier Hansson <david@hey.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
4 files changed
+146
-2
No files matched your search
@@ -0,0 +1,62 @@
|
||||
"""Offline target keyboard configuration.
|
||||
|
||||
archinstall's set_keyboard_language boots the installed system in a
|
||||
systemd-nspawn container just to run localectl. systemd-firstboot --root
|
||||
produces the part of that output Omarchy actually consumes — KEYMAP for the
|
||||
console plus the XKB* settings in vconsole.conf that omarchy's
|
||||
detect-keyboard-layout.sh copies into Hyprland's kb_layout — without booting
|
||||
anything. The Xorg 00-keyboard.conf that localectl also writes is not
|
||||
generated: nothing on an Omarchy system reads it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def configure_keyboard(target: Path, language: str) -> bool:
|
||||
"""Write the console keymap into a mounted target without booting it.
|
||||
|
||||
Returns False for layouts localectl doesn't know (the configurator offers
|
||||
two, ba and khmer), matching archinstall: warn and keep the default.
|
||||
"""
|
||||
if not language.strip():
|
||||
return True
|
||||
|
||||
result = subprocess.run(
|
||||
["localectl", "--no-pager", "list-keymaps"],
|
||||
check=False,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
detail = result.stderr.strip() or "localectl returned an error"
|
||||
raise RuntimeError(f"Unable to list keyboard layouts: {detail}")
|
||||
if language.lower() not in {layout.lower() for layout in result.stdout.splitlines()}:
|
||||
return False
|
||||
|
||||
# systemd-firstboot --force rewrites vconsole.conf wholesale, dropping the
|
||||
# FONT= line archinstall's set_vconsole wrote earlier.
|
||||
vconsole_path = target / "etc" / "vconsole.conf"
|
||||
font = None
|
||||
if vconsole_path.exists():
|
||||
font = next(
|
||||
(line for line in vconsole_path.read_text().splitlines() if line.startswith("FONT=")),
|
||||
None,
|
||||
)
|
||||
|
||||
result = subprocess.run(
|
||||
["systemd-firstboot", f"--root={target}", f"--keymap={language}", "--force"],
|
||||
check=False,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
detail = result.stderr.strip() or "systemd-firstboot returned an error"
|
||||
raise RuntimeError(f"Unable to configure keyboard layout {language}: {detail}")
|
||||
|
||||
if font:
|
||||
vconsole_path.write_text(vconsole_path.read_text() + font + "\n")
|
||||
|
||||
return True
|
||||
@@ -27,11 +27,13 @@ import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
import time
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from . import archinstall_adapter as arch
|
||||
from .context import InstallContext
|
||||
from .ui import info
|
||||
from .keyboard import configure_keyboard
|
||||
from .ui import error, info
|
||||
|
||||
|
||||
# Package targets are written by builder/build-iso.sh. Stable ISOs use the
|
||||
@@ -246,6 +248,10 @@ def arch_install_system(ctx: InstallContext) -> None:
|
||||
_mask_mkinitcpio_pacman_hooks(ctx)
|
||||
try:
|
||||
info("› installing base system (mkinitcpio deferred to final Limine UKI build)")
|
||||
# An empty kb_layout makes archinstall's set_keyboard_language skip
|
||||
# booting the target in a container just to run localectl; the
|
||||
# keymap is configured offline right after instead.
|
||||
kb_layout = config.locale_config.kb_layout if config.locale_config else ""
|
||||
installer.minimal_installation(
|
||||
optional_repositories=(
|
||||
config.mirror_config.optional_repositories
|
||||
@@ -253,10 +259,16 @@ def arch_install_system(ctx: InstallContext) -> None:
|
||||
),
|
||||
mkinitcpio=False,
|
||||
hostname=config.hostname,
|
||||
locale_config=config.locale_config,
|
||||
locale_config=(
|
||||
replace(config.locale_config, kb_layout="")
|
||||
if config.locale_config else None
|
||||
),
|
||||
pacman_config=config.pacman_config,
|
||||
)
|
||||
|
||||
if not configure_keyboard(installer.target, kb_layout):
|
||||
error(f"Invalid keyboard language specified: {kb_layout}")
|
||||
|
||||
if config.mirror_config:
|
||||
installer.set_mirrors(mirror_handler, config.mirror_config, on_target=True)
|
||||
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import importlib.util
|
||||
import re
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MODULE_PATH = ROOT / "configs/airootfs/usr/share/omarchy-iso/orchestrator/keyboard.py"
|
||||
SPEC = importlib.util.spec_from_file_location("installer_keyboard", MODULE_PATH)
|
||||
KEYBOARD = importlib.util.module_from_spec(SPEC)
|
||||
assert SPEC.loader
|
||||
SPEC.loader.exec_module(KEYBOARD)
|
||||
|
||||
CONFIGURATOR = (ROOT / "configs/airootfs/root/configurator").read_text()
|
||||
KEYBOARD_BLOCK = re.search(r"keyboards=\$'(.*?)'\n choice=", CONFIGURATOR, re.DOTALL)
|
||||
assert KEYBOARD_BLOCK
|
||||
SUPPORTED_KEYMAPS = [
|
||||
line.split("|", 1)[1]
|
||||
for line in KEYBOARD_BLOCK.group(1).splitlines()
|
||||
]
|
||||
UNSUPPORTED_KEYMAPS = {"ba", "khmer"}
|
||||
|
||||
|
||||
class KeyboardConfigurationTest(unittest.TestCase):
|
||||
def target(self, directory: str) -> Path:
|
||||
target = Path(directory)
|
||||
(target / "etc").mkdir()
|
||||
(target / "etc/vconsole.conf").write_text(
|
||||
"KEYMAP=us\nFONT=default8x16\n"
|
||||
)
|
||||
return target
|
||||
|
||||
def test_writes_keymap_and_xkb_settings_and_preserves_font(self):
|
||||
# XKBLAYOUT is load-bearing: omarchy's detect-keyboard-layout.sh copies
|
||||
# it into Hyprland's kb_layout on the installed system.
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
target = self.target(directory)
|
||||
self.assertTrue(KEYBOARD.configure_keyboard(target, "us"))
|
||||
lines = (target / "etc/vconsole.conf").read_text().splitlines()
|
||||
self.assertIn("KEYMAP=us", lines)
|
||||
self.assertIn("XKBLAYOUT=us", lines)
|
||||
self.assertEqual(lines.count("FONT=default8x16"), 1)
|
||||
|
||||
def test_all_configurator_keymaps_are_known_to_localectl(self):
|
||||
for keymap in SUPPORTED_KEYMAPS:
|
||||
with self.subTest(keymap=keymap), tempfile.TemporaryDirectory() as directory:
|
||||
target = self.target(directory)
|
||||
configured = KEYBOARD.configure_keyboard(target, keymap)
|
||||
self.assertEqual(configured, keymap not in UNSUPPORTED_KEYMAPS)
|
||||
if configured:
|
||||
lines = (target / "etc/vconsole.conf").read_text().splitlines()
|
||||
self.assertIn(f"KEYMAP={keymap}", lines)
|
||||
self.assertEqual(lines.count("FONT=default8x16"), 1)
|
||||
|
||||
def test_unknown_and_empty_keymaps_match_archinstall_behavior(self):
|
||||
for keymap, expected in (("definitely-not-a-keymap", False), ("", True)):
|
||||
with self.subTest(keymap=keymap), tempfile.TemporaryDirectory() as directory:
|
||||
target = self.target(directory)
|
||||
self.assertEqual(KEYBOARD.configure_keyboard(target, keymap), expected)
|
||||
self.assertEqual(
|
||||
(target / "etc/vconsole.conf").read_text(),
|
||||
"KEYMAP=us\nFONT=default8x16\n",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user