furtka/iso
Daniel Maksymilian Syrnicki 7151d411c7 feat(installer): friendlier wizard, retry after a failed install, no boot stall on multi-NIC boards
- error state gets a 'Try again' button (settings are kept) plus links back
  to the disk / settings pages; the page reloads into it instead of only
  recolouring the bar
- names are lowercased instead of rejected; system account names (root,
  caddy, docker, …) are refused with a clear message
- plain wording: 'Server name' with a live http://<name>.local preview,
  'Pick a disk' with an explicit erase warning, a readable confirm table
  (language label, disk size/type) and a Back button
- the password hint says it is also the SSH / console password; the
  language hint says the wizard itself is English
- wizard pages redirect to the progress page while an install is running
- progress page: aria-live phase, aria-valuenow updates, 'lost connection'
  note, 'fine to close this page'
- .local fallback on the reboot page: router device list, IP may change
- live console banner says a network cable with internet is required
- target gets a systemd-networkd-wait-online --any drop-in: boards with a
  second unplugged NIC (or Ethernet + Wi-Fi) waited up to 2 min at boot

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wq1hzUp4ZmCKHDuLKCnu73
2026-09-28 13:21:55 +02:00
..
overlay feat(installer): friendlier wizard, retry after a failed install, no boot stall on multi-NIC boards 2026-09-28 13:21:55 +02:00
build.sh fix(console): always show the IP fallback on the welcome banner 2026-08-25 15:18:39 +02:00
README.md fix(updater): start newly linked timers and link units via current; bench test report 2026-08-26 09:46:13 +02:00

Live ISO build

Builds a bootable Arch-based live ISO that auto-starts the Flask webinstaller from ../webinstaller/ on boot. User plugs in a USB, boots, and the installer wizard comes up on http://proksi.local:5000 (or the raw IP shown on the console).

Runnable locally (below) or through Forgejo Actions — .forgejo/workflows/build-iso.yml builds on every push to main and on manual workflow_dispatch. The ISO lands as an artifact named furtka-iso, retained for 14 days. Feature branches don't trigger the ISO build; see memory/project_ci_branching for why.

Run a build locally

Needs a host with Docker. Disk space required: ~15 GB scratch during the build, ~1.5 GB for the final ISO.

./iso/build.sh

Output ISO ends up in iso/out/furtka-<date>-x86_64.iso. Around 3–10 min on a 4-core VM. First run is slower because it pulls archlinux:latest and all packages from upstream.

The script re-execs itself inside a privileged archlinux:latest container. That's so mkarchiso has root + loop-mount access without polluting the host — Ubuntu hosts don't ship archiso natively anyway.

What gets baked in

The build starts from Arch's stock releng profile (the same one used to build the official Arch ISO), then overlays our customizations from overlay/:

Overlay file Effect
overlay/packages.extra Appended to the package list. Adds python, python-flask, avahi, nss-mdns
overlay/profiledef.sh Appended to profiledef.sh. Renames the ISO to furtka-* with a dated version
overlay/airootfs/opt/furtka/ Directory where webinstaller/ is copied at build time
overlay/airootfs/etc/hostname Live-ISO hostname (proksi) so mDNS advertises the installer as proksi.local
overlay/airootfs/etc/issue Welcome banner on the TTY pointing users at http://proksi.local:5000
overlay/airootfs/usr/local/bin/furtka-update-issue Rewrites /etc/issue at runtime so the banner always carries a fallback line: the DHCP-assigned IP(s), or a "no IP yet — check cable / DHCP" hint until one arrives. Only touches the file (and agetty --reloads) when the text changed
overlay/airootfs/etc/systemd/system/ furtka-webinstaller.service (Flask on :5000) + furtka-issue.service (banner-updater, first run at boot) in multi-user.target.wants/, and furtka-issue.timer (re-runs the updater every 5 s) in timers.target.wants/

The systemd service runs flask --app app run --host 0.0.0.0 --port 5000 under /opt/furtka. The 0.0.0.0 binding is important — the Flask default is localhost-only, which wouldn't be reachable from another machine on the LAN.

mDNS is wired: avahi-daemon + nss-mdns come from packages.extra, the live ISO's hostname is proksi, and as soon as systemd-networkd-wait-online fires the installer is reachable at http://proksi.local:5000. The raw IP still shows on the console for fallback — some Windows clients need the Bonjour service for .local to resolve at all.

build.sh also deletes releng's getty@tty1.service.d/autologin.conf. With root auto-logged-in, tty1 belongs to a shell and agetty --reload can't redraw the banner, so the IP line was a snapshot of whatever DHCP had done by the time getty started. Without autologin tty1 sits at the login prompt showing the live banner; for a shell type root + Enter (releng's root has no password) — the banner says so.

Test flow

  1. Build: ./iso/build.sh
  2. Copy the ISO to your Proxmox host's ISO storage (typically /var/lib/vz/template/iso/). Browser uploads of 1.5 GB truncate silently — prefer scp over the Proxmox WebUI.
  3. Create a VM with:
    • 2 vCPU, 4 GB RAM, 20 GB disk (empty)
    • BIOS: OVMF (UEFI), add EFI Disk on local-lvm. SeaBIOS fails to load ldlinux.c32 from our ISO; only the UEFI path works reliably.
    • Secure Boot disabled. Our GRUB isn't signed, so Secure Boot rejects it with Access Denied. Either boot into OVMF setup (Esc during boot) → Device Manager → Secure Boot Configuration → Attempt Secure Boot [ ] → F10 → reboot. Or remove the EFI Disk and re-add it with "Pre-Enroll keys" unchecked.
    • CD-ROM attached with the Furtka ISO
    • Boot order: CD before disk
    • Network: same bridge as your LAN, DHCP
  4. Start the VM. Wait ~30 s for boot.
  5. Find its IP in Proxmox's VM summary (or your router's DHCP table)
  6. Open http://<vm-ip>:5000 — the existing 3-screen wizard should be there

Real hardware (what differs from the VM flow)

Learned on the first bare-metal run, see docs/hardware-test-2026-08.md:

  • BIOS boxes work. The hybrid ISO boots via syslinux on legacy firmware (you'll see the stock Arch splash — only the menu labels are branded), and the installer detects the missing /sys/firmware/efi and picks GRUB + MBR.
  • Pull the USB stick before "restart now". Most BIOSes boot USB before the disk; the done page says so above the button since 26.20-alpha.
  • Disks with a previous OS are fine since 26.20-alpha. The live ISO auto-activates old LVM/RAID/swap; webinstaller/diskprep.py releases the disk before archinstall partitions it. On 26.19 and earlier you'd hit unable to inform the kernel of the change … in use at 12 % — clear it from the tty1 root shell with dmsetup remove <node> for every dm node shown by lsblk, then wipefs -a /dev/sdX and retry.
  • Slow NICs. The console banner's IP fallback line is timer-driven since 26.20-alpha; on 26.19 it only appeared if DHCP beat getty.
  • Shell on the live ISO: since 26.20-alpha tty1 stays at the login prompt (banner refreshes live); log in as root, no password. sshd runs; set a root password with passwd to reach it over the network.

What you see after install + reboot

Once archinstall finishes and you click Reboot now, the VM comes up into the installed system. No more port :5000 — the wizard ISO is gone. Instead:

  • Console: agetty shows Furtka is ready. Open http://<hostname>.local … with the IP fallback underneath.
  • Browser at http://<hostname>.local (default http://furtka.local — the form's default hostname is furtka; only the live-installer ISO uses proksi): Caddy-served landing page with three live status tiles (uptime, Docker version, free disk) refreshed every 30 s by furtka-status.timer. HTTPS is opt-in (26.15-alpha) — flip the toggle in /settings to switch on Caddy's tls internal on :443, then trust rootCA.crt from /settings to clear browser warnings.
  • SSH: ssh <user>@<hostname>.local works; docker ps works without sudo because the user is in the docker group.

This is a demo shell — no Authentik, no app store yet. The landing page lives at /srv/furtka/www/, served by Caddy on :80 per /etc/caddy/Caddyfile. All of this is written into the target by webinstaller/app.py's _post_install_commands via archinstall's custom_commands.

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".
  • Live-installer wizard is still HTTP-only. http://proksi.local:5000 during install has no TLS; once the box reboots, Caddy can serve tls internal on :443 if the user opts in via /settings (26.15-alpha), but bringing TLS to the wizard itself is a later milestone.