fix: wire webinstaller to archinstall 4.x config schema

Walking-skeleton install on a real VM surfaced two archinstall 4.x
schema breakages that the wizard hit only at runtime:

- `use_entire_disk` was removed as a `config_type`. Now builds a full
  `default_layout` disk_config by calling `suggest_single_disk_layout`
  (forced ext4 + no separate /home, which bypasses its interactive
  prompts) and serializing the returned DeviceModification.
- Credentials keys renamed to plaintext sentinels: `!root-password`
  and `!password`. Users with neither `!password` nor `enc_password`
  are silently dropped by `User.parse_arguments` — which is why the
  first real install booted but wouldn't log in.

Also rolls in Robert's UX feedback quick-wins: `(Recommended)` prefix
on the default boot entry across GRUB/syslinux/systemd-boot, and
less-jargon hints on the step-1 hostname/username fields. iso/README
loses three stale bullets that described pre-15b876c behaviour.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Daniel Maksymilian Syrnicki 2026-04-14 17:00:39 +02:00
parent 15b876c70a
commit 51cdf460d9
4 changed files with 58 additions and 13 deletions

View file

@ -49,8 +49,5 @@ mDNS (`proksi.local`) via avahi is installed but not yet wired. First milestone
## Known rough edges ## Known rough edges
- **Disk space**: the first time you build on a fresh host, the squashfs/xorriso steps need ~15 GB free. If the host's LVM-root is smaller, `xorriso` silently dies at the very end with "Image size exceeds free space on media". - **Disk space**: the first time you build on a fresh host, the squashfs/xorriso steps need ~15 GB free. If the host's LVM-root is smaller, `xorriso` silently dies at the very end with "Image size exceeds free space on media".
- **Flask `/` route** returns "Hello World" instead of redirecting to `/install/step1`. Harmless but surprising; will be cleaned up when we wire up screens 48.
- **No HTTPS yet**. The Furtka plan is "local CA + green padlock on `https://proksi.local`" — that's a later milestone. For now, plain HTTP. - **No HTTPS yet**. The Furtka plan is "local CA + green padlock on `https://proksi.local`" — that's a later milestone. For now, plain HTTP.
- **archinstall is not invoked**. The wizard collects input but doesn't write to disk yet. Still a walking skeleton, not an installer.
- **Drive list includes `/dev/loop0` and `/dev/sr0`**. `/dev/loop0` is the live ISO's own squashfs mounted in RAM; `/dev/sr0` is the CD-ROM itself. Both appear as install targets, which is wrong. Filter lives in `webinstaller/drives.py` and hasn't been added yet. - **Drive list includes `/dev/loop0` and `/dev/sr0`**. `/dev/loop0` is the live ISO's own squashfs mounted in RAM; `/dev/sr0` is the CD-ROM itself. Both appear as install targets, which is wrong. Filter lives in `webinstaller/drives.py` and hasn't been added yet.
- **GRUB menu still says "Arch Linux install medium"**. We inherit releng's bootloader config. Cosmetic, fix when we care about end-user polish.

View file

@ -55,6 +55,16 @@ find "$PROFILE_WORK/grub" "$PROFILE_WORK/syslinux" "$PROFILE_WORK/efiboot" \
| xargs -0 sed -i \ | xargs -0 sed -i \
-e 's/Arch Linux install medium/Furtka Live Installer/g' -e 's/Arch Linux install medium/Furtka Live Installer/g'
# Mark the default entry as (Recommended) so first-time users know which to
# pick. Targets the main entry only — speech/accessibility variants stay
# unlabeled to avoid suggesting they're the normal choice.
sed -i 's/^title Furtka Live Installer (%ARCH%, UEFI)$/title (Recommended) Furtka Live Installer (%ARCH%, UEFI)/' \
"$PROFILE_WORK/efiboot/loader/entries/01-archiso-linux.conf"
sed -i 's/^MENU LABEL Furtka Live Installer (%ARCH%, BIOS)$/MENU LABEL (Recommended) Furtka Live Installer (%ARCH%, BIOS)/' \
"$PROFILE_WORK/syslinux/archiso_sys-linux.cfg"
sed -i "/--id 'archlinux'/s/menuentry \"Furtka Live Installer/menuentry \"(Recommended) Furtka Live Installer/" \
"$PROFILE_WORK/grub/grub.cfg" "$PROFILE_WORK/grub/loopback.cfg"
mkdir -p "$PROFILE_WORK/airootfs/opt/furtka" mkdir -p "$PROFILE_WORK/airootfs/opt/furtka"
cp -a "$REPO_ROOT/webinstaller/." "$PROFILE_WORK/airootfs/opt/furtka/" cp -a "$REPO_ROOT/webinstaller/." "$PROFILE_WORK/airootfs/opt/furtka/"
rm -rf "$PROFILE_WORK/airootfs/opt/furtka/__pycache__" rm -rf "$PROFILE_WORK/airootfs/opt/furtka/__pycache__"

View file

@ -57,24 +57,52 @@ def validate_step1(form):
return errors, values return errors, values
def build_disk_config(boot_drive):
# archinstall 4.x dropped the `use_entire_disk` shortcut — `default_layout`
# now requires fully-specified partitions. We call suggest_single_disk_layout
# with ext4 + no separate /home, which short-circuits its interactive prompts.
import asyncio
from archinstall.lib.disk.device_handler import device_handler
from archinstall.lib.disk.disk_menu import suggest_single_disk_layout
from archinstall.lib.models.device import (
DiskLayoutConfiguration,
DiskLayoutType,
FilesystemType,
)
device_handler.load_devices()
device = device_handler.get_device(Path(boot_drive))
if device is None:
raise RuntimeError(f"archinstall could not resolve device {boot_drive!r}")
device_mod = asyncio.run(
suggest_single_disk_layout(
device,
filesystem_type=FilesystemType.Ext4,
separate_home=False,
)
)
layout = DiskLayoutConfiguration(
config_type=DiskLayoutType.Default,
device_modifications=[device_mod],
)
return layout.json()
def build_archinstall_config(s): def build_archinstall_config(s):
return { return {
"archinstall-language": "English", "archinstall-language": "English",
"timezone": "Europe/Berlin", "timezone": "Europe/Berlin",
"ntp": True, "ntp": True,
"bootloader": "Systemd-boot", "bootloader": "Systemd-boot",
"disk_config": { "disk_config": build_disk_config(s["boot_drive"]),
"config_type": "use_entire_disk",
"device": s["boot_drive"],
"filesystem": "ext4",
},
"hostname": s["hostname"], "hostname": s["hostname"],
"kernels": ["linux"], "kernels": ["linux"],
"packages": ["docker", "docker-compose", "vim", "git", "htop", "curl"], "packages": ["docker", "docker-compose", "vim", "git", "htop", "curl"],
"profile": {"type": "server"}, "profile": {"type": "server"},
"services": ["docker"], "services": ["docker"],
"network_config": {"type": "iso"}, "network_config": {"type": "iso"},
"users": [{"username": s["username"], "sudo": True, "groups": ["docker"]}],
"ssh": True, "ssh": True,
"audio_config": None, "audio_config": None,
"locale_config": { "locale_config": {
@ -85,9 +113,19 @@ def build_archinstall_config(s):
def build_archinstall_creds(s): def build_archinstall_creds(s):
# archinstall 4.x expects `!root-password` and `!password` (plaintext
# sentinels). Users with neither `!password` nor `enc_password` are
# silently dropped by User.parse_arguments — hence login failures.
return { return {
"root_password": s["password"], "!root-password": s["password"],
"users": [{"username": s["username"], "password": s["password"]}], "users": [
{
"username": s["username"],
"!password": s["password"],
"sudo": True,
"groups": ["docker"],
}
],
} }

View file

@ -20,12 +20,12 @@
<div class="field"> <div class="field">
<label for="hostname">Hostname</label> <label for="hostname">Hostname</label>
<input type="text" id="hostname" name="hostname" required value="{{ values.hostname }}" autocomplete="off" /> <input type="text" id="hostname" name="hostname" required value="{{ values.hostname }}" autocomplete="off" />
<span class="hint">Lowercase letters, digits, hyphens. Used on the local network.</span> <span class="hint">The name your server shows up as on your home network. Lowercase letters, numbers, and dashes only.</span>
</div> </div>
<div class="field"> <div class="field">
<label for="username">Admin username</label> <label for="username">Admin username</label>
<input type="text" id="username" name="username" required value="{{ values.username }}" autocomplete="username" /> <input type="text" id="username" name="username" required value="{{ values.username }}" autocomplete="username" />
<span class="hint">Linux user account with sudo + docker access.</span> <span class="hint">Your login name on the server. You'll use this to sign in after installation.</span>
</div> </div>
<div class="field"> <div class="field">
<label for="password">Password</label> <label for="password">Password</label>