feat: walking-skeleton live ISO that boots into the Flask wizard
Some checks are pending
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
CI / validate-json (push) Waiting to run
CI / markdown-links (push) Waiting to run

iso/build.sh runs mkarchiso inside a privileged archlinux container,
overlays our customizations onto Arch's stock releng profile
(systemd unit that launches Flask on 0.0.0.0:5000, the webinstaller
under /opt/furtka, extra packages for python/flask/avahi), and drops
a hybrid BIOS/UEFI ISO in iso/out/.

Verified end to end: Proxmox VM (OVMF, Secure Boot off) boots the ISO,
DHCP's onto the LAN, and serves screens 1-3 of the existing wizard at
http://<vm-ip>:5000/install/step1. This is the first point at which
Furtka is something you can run instead of something you can read about.

Two known drive-list bugs surfaced while testing (/dev/loop0 and
/dev/sr0 appear as install targets) — captured in the README roadmap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Daniel Maksymilian Syrnicki 2026-04-13 23:55:58 +02:00
parent 03b2b7d451
commit a535debf2e
9 changed files with 150 additions and 2 deletions

1
.gitignore vendored
View file

@ -7,3 +7,4 @@ __pycache__/
# Real credentials must never be committed — use the .example files
archinstall/user_credentials.json
iso/out/

View file

@ -16,6 +16,7 @@ This project uses calendar versioning: `YY.N-stage` (e.g. `26.0-alpha` = 2026, r
### Added
- **Forgejo Actions runner** live on Proxmox VM (`forge-runner-01`, Ubuntu 24.04) with DinD sidecar — CI green end-to-end. Setup scripts in `ops/forgejo-runner/`.
- **Walking-skeleton live ISO** (`iso/build.sh`). Overlays an Arch `releng` profile with Flask + the webinstaller, bakes a systemd unit that auto-starts the wizard on boot, produces a hybrid BIOS/UEFI ISO via `mkarchiso` in a privileged `archlinux:latest` container. Tested booting under OVMF in Proxmox — wizard screens 13 respond at `http://<vm-ip>:5000`.
## [26.0-alpha] - 2026-04-13

View file

@ -106,8 +106,12 @@ None of these nail the "your dad can set this up" experience. The installer wiza
- [x] Release process + CI — CalVer tags, conventional commits, Forgejo Actions (ruff, pytest, JSON, link checks), `26.0-alpha` tagged
- [x] Forgejo runner live on Proxmox VM (`forge-runner-01`, Ubuntu 24.04, Docker + DinD sidecar) — setup captured in [docs/runner-setup.md](docs/runner-setup.md) + [ops/forgejo-runner/](ops/forgejo-runner/)
- [ ] **Publish `26.0-alpha` Forgejo Release** — [releases/new](https://forgejo.sourcegate.online/daniel/furtka/releases/new), paste CHANGELOG section, tick Pre-release *(Daniel, next session)*
- [ ] **Base OS bootable image** — Robert gets a minimal Arch image that boots, runs Docker, serves the installer webapp at `https://proksi.local` *(next blocker)*
- [ ] Installer wizard screens S5S8 (domain, SSL, diagnostic, confirm)
- [x] **Walking-skeleton live ISO**`iso/build.sh` produces a hybrid BIOS/UEFI Arch-based ISO that boots in a Proxmox VM, DHCP's onto the LAN, and serves the Flask webinstaller on `:5000`. Screens 13 work end-to-end. Build infra in [`iso/`](iso/).
- [ ] **Drop /dev/loop0 + /dev/sr0 from drive list** — the live ISO's own squashfs and the CD-ROM both show up as install targets. Simple filter in `webinstaller/drives.py`.
- [ ] **Rebrand GRUB menu** — the ISO still boots as "Arch Linux install medium". Cosmetic, fix when we start caring about end-user-facing polish.
- [ ] **Base OS post-install** — what Furtka actually looks like *after* the wizard writes config + reboots: Caddy + Authentik + app store. Robert's area.
- [ ] Installer wizard screens S4S8 (user/password, domain, SSL, diagnostic, confirm) + actually invoking `archinstall` on the chosen disk
- [ ] `https://proksi.local` via mDNS + local CA (currently only raw-IP HTTP)
- [ ] Caddy + Authentik wired into first-boot bootstrap
- [ ] Managed gateway infrastructure — `ns1/ns2.furtka.org` + DNS-01 wildcard automation
- [ ] First containerized service (Nextcloud?) with auto-SSO + auto-subdomain

52
iso/README.md Normal file
View file

@ -0,0 +1,52 @@
# 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://<vm-ip>:5000`.
Directly runnable; CI integration comes later once the build is stable.
## Run a build
Needs a host with Docker. Disk space required: ~15 GB scratch during the build, ~1.5 GB for the final ISO.
```bash
./iso/build.sh
```
Output ISO ends up in `iso/out/furtka-<date>-x86_64.iso`. Around 310 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/systemd/system/` | Contains `furtka-webinstaller.service` + a symlink into `multi-user.target.wants/` so it auto-starts on boot |
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 (`proksi.local`) via avahi is installed but not yet wired. First milestone is just "boot → browser → wizard at raw IP". Naming comes next.
## Test flow
1. Build: `./iso/build.sh`
2. Copy the ISO to your Proxmox host's ISO storage (typically `/var/lib/vz/template/iso/`)
3. Create a VM with:
- 2 vCPU, 4 GB RAM, 20 GB disk (empty)
- 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
## 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".
- **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.
- **archinstall is not invoked**. The wizard collects input but doesn't write to disk yet. Still a walking skeleton, not an installer.

62
iso/build.sh Executable file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env bash
# Build a Furtka live ISO.
#
# From the repo root or from iso/ on any host with Docker:
# ./iso/build.sh
#
# The build runs inside a privileged `archlinux:latest` container because
# mkarchiso needs root + loop mounts + an Arch package manager, which
# Ubuntu doesn't provide natively. Output ISO goes to iso/out/.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
OUT_DIR="$SCRIPT_DIR/out"
if [[ "${FURTKA_ISO_INNER:-0}" != "1" ]]; then
mkdir -p "$OUT_DIR"
echo "==> Launching build container"
exec docker run --rm --privileged \
-v "$REPO_ROOT:/work" \
-w /work \
-e FURTKA_ISO_INNER=1 \
archlinux:latest \
bash /work/iso/build.sh
fi
# ---- inside the container from here on ----
echo "==> Syncing pacman, installing archiso"
pacman -Syu --noconfirm --needed archiso
PROFILE_SRC="/usr/share/archiso/configs/releng"
PROFILE_WORK="/tmp/furtka-profile"
BUILD_WORK="/tmp/furtka-build"
OUT_IN_CONTAINER="/work/iso/out"
rm -rf "$PROFILE_WORK" "$BUILD_WORK"
cp -a "$PROFILE_SRC" "$PROFILE_WORK"
echo "==> Overlaying Furtka customizations"
cat "$SCRIPT_DIR/overlay/packages.extra" >> "$PROFILE_WORK/packages.x86_64"
cat "$SCRIPT_DIR/overlay/profiledef.sh" >> "$PROFILE_WORK/profiledef.sh"
cp -a "$SCRIPT_DIR/overlay/airootfs/." "$PROFILE_WORK/airootfs/"
mkdir -p "$PROFILE_WORK/airootfs/opt/furtka"
cp -a "$REPO_ROOT/webinstaller/." "$PROFILE_WORK/airootfs/opt/furtka/"
rm -rf "$PROFILE_WORK/airootfs/opt/furtka/__pycache__"
mkdir -p "$PROFILE_WORK/airootfs/etc/systemd/system/avahi-daemon.service.d"
ln -sf /usr/lib/systemd/system/avahi-daemon.service \
"$PROFILE_WORK/airootfs/etc/systemd/system/multi-user.target.wants/avahi-daemon.service"
echo "==> Building ISO (mkarchiso)"
mkdir -p "$OUT_IN_CONTAINER"
mkarchiso -v -w "$BUILD_WORK" -o "$OUT_IN_CONTAINER" "$PROFILE_WORK"
echo
echo "==> Done. ISO(s) in $OUT_IN_CONTAINER (on host: iso/out/):"
ls -lh "$OUT_IN_CONTAINER"

View file

@ -0,0 +1,14 @@
[Unit]
Description=Furtka Live Installer (Flask)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/opt/furtka
ExecStart=/usr/bin/python -m flask --app app run --host 0.0.0.0 --port 5000
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1 @@
../furtka-webinstaller.service

View file

@ -0,0 +1,4 @@
python
python-flask
avahi
nss-mdns

View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Overrides for releng's profiledef.sh — only the fields we want to change.
# build.sh sources releng's original first, then this file, so these win.
iso_name="furtka"
iso_label="FURTKA_$(date +%Y%m)"
iso_publisher="Furtka <https://furtka.org>"
iso_application="Furtka Live Installer"
iso_version="$(date +%Y.%m.%d)"