feat: webinstaller writes archinstall config + execs install, styled
Wires the live-ISO wizard from "shows three screens" to "actually invokes
archinstall on the chosen disk", plus first-pass styling so it stops looking
like raw <h1>/<form>.
Webinstaller flow:
- S1 form gains username/password/password2/language with server-side
validation (hostname/username regex, ≥8 char password, match check).
- /install/run writes user_configuration.json + user_credentials.json
(creds 0600) to FURTKA_STATE_DIR (default /tmp/furtka), then execs
`archinstall --config … --creds … --silent` as a backgrounded subprocess.
- /install/log renders the subprocess output via meta-refresh polling.
- FURTKA_DRY_RUN=1 short-circuits the exec for testing.
- archinstall flag names verified against `archinstall --help` in an
archlinux container before committing.
Drive list:
- drives.py now filters via `lsblk … -o NAME,SIZE,TYPE` keeping TYPE=disk,
so the live ISO's own squashfs (loop) and CD-ROM (rom) stop appearing
as install targets.
Boot menu:
- iso/build.sh sed-rebrands "Arch Linux install medium" →
"Furtka Live Installer" across grub/, syslinux/, and efiboot/loader/
entries. Verified zero leftovers against the current releng profile.
Styling:
- static/style.css adopts the website's design tokens (palette,
typography, gate-mark accent), with light + dark via prefers-color-scheme.
- New base.html with header (gate SVG + FURTKA·INSTALLER wordmark + step
indicator) and footer; all install templates extend it.
- Drive picker uses radio cards with score chip; overview uses a summary
table and a destructive "wipe drive" button.
Tests: 17 pass (4 new in test_app.py covering validation + config builders,
2 new in test_drives.py covering the lsblk filter). Ruff clean.
README roadmap updated to mark these done and explicitly defer the
26.0-alpha release until archinstall actually completes end-to-end on a VM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:54:49 +02:00
|
|
|
from app import (
|
|
|
|
|
build_archinstall_config,
|
|
|
|
|
build_archinstall_creds,
|
|
|
|
|
validate_step1,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_validate_step1_accepts_good_input():
|
|
|
|
|
errors, values = validate_step1(
|
|
|
|
|
{
|
|
|
|
|
"hostname": "furtka",
|
|
|
|
|
"username": "daniel",
|
|
|
|
|
"password": "topsecretpw",
|
|
|
|
|
"password2": "topsecretpw",
|
|
|
|
|
"language": "de",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
assert errors == []
|
|
|
|
|
assert values == {
|
|
|
|
|
"hostname": "furtka",
|
|
|
|
|
"username": "daniel",
|
|
|
|
|
"password": "topsecretpw",
|
|
|
|
|
"language": "de",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_validate_step1_collects_all_errors():
|
|
|
|
|
errors, _ = validate_step1(
|
|
|
|
|
{
|
|
|
|
|
"hostname": "BAD!",
|
|
|
|
|
"username": "1bad",
|
|
|
|
|
"password": "short",
|
|
|
|
|
"password2": "mismatch",
|
|
|
|
|
"language": "xx",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
assert len(errors) == 5
|
|
|
|
|
|
|
|
|
|
|
2026-04-14 18:29:42 +02:00
|
|
|
def test_build_archinstall_config_uses_selected_locale(monkeypatch):
|
|
|
|
|
# build_disk_config imports archinstall lazily; archinstall isn't
|
|
|
|
|
# installed in CI (only runs on the live ISO), so stub it out.
|
|
|
|
|
import app as app_module
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(app_module, "build_disk_config", lambda d: {"stubbed_device": d})
|
|
|
|
|
|
feat: webinstaller writes archinstall config + execs install, styled
Wires the live-ISO wizard from "shows three screens" to "actually invokes
archinstall on the chosen disk", plus first-pass styling so it stops looking
like raw <h1>/<form>.
Webinstaller flow:
- S1 form gains username/password/password2/language with server-side
validation (hostname/username regex, ≥8 char password, match check).
- /install/run writes user_configuration.json + user_credentials.json
(creds 0600) to FURTKA_STATE_DIR (default /tmp/furtka), then execs
`archinstall --config … --creds … --silent` as a backgrounded subprocess.
- /install/log renders the subprocess output via meta-refresh polling.
- FURTKA_DRY_RUN=1 short-circuits the exec for testing.
- archinstall flag names verified against `archinstall --help` in an
archlinux container before committing.
Drive list:
- drives.py now filters via `lsblk … -o NAME,SIZE,TYPE` keeping TYPE=disk,
so the live ISO's own squashfs (loop) and CD-ROM (rom) stop appearing
as install targets.
Boot menu:
- iso/build.sh sed-rebrands "Arch Linux install medium" →
"Furtka Live Installer" across grub/, syslinux/, and efiboot/loader/
entries. Verified zero leftovers against the current releng profile.
Styling:
- static/style.css adopts the website's design tokens (palette,
typography, gate-mark accent), with light + dark via prefers-color-scheme.
- New base.html with header (gate SVG + FURTKA·INSTALLER wordmark + step
indicator) and footer; all install templates extend it.
- Drive picker uses radio cards with score chip; overview uses a summary
table and a destructive "wipe drive" button.
Tests: 17 pass (4 new in test_app.py covering validation + config builders,
2 new in test_drives.py covering the lsblk filter). Ruff clean.
README roadmap updated to mark these done and explicitly defer the
26.0-alpha release until archinstall actually completes end-to-end on a VM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:54:49 +02:00
|
|
|
cfg = build_archinstall_config(
|
|
|
|
|
{
|
|
|
|
|
"hostname": "h",
|
|
|
|
|
"username": "u",
|
|
|
|
|
"password": "pw12345678",
|
|
|
|
|
"language": "pl",
|
|
|
|
|
"boot_drive": "/dev/sda",
|
|
|
|
|
}
|
|
|
|
|
)
|
2026-04-14 18:29:42 +02:00
|
|
|
assert cfg["disk_config"] == {"stubbed_device": "/dev/sda"}
|
feat: webinstaller writes archinstall config + execs install, styled
Wires the live-ISO wizard from "shows three screens" to "actually invokes
archinstall on the chosen disk", plus first-pass styling so it stops looking
like raw <h1>/<form>.
Webinstaller flow:
- S1 form gains username/password/password2/language with server-side
validation (hostname/username regex, ≥8 char password, match check).
- /install/run writes user_configuration.json + user_credentials.json
(creds 0600) to FURTKA_STATE_DIR (default /tmp/furtka), then execs
`archinstall --config … --creds … --silent` as a backgrounded subprocess.
- /install/log renders the subprocess output via meta-refresh polling.
- FURTKA_DRY_RUN=1 short-circuits the exec for testing.
- archinstall flag names verified against `archinstall --help` in an
archlinux container before committing.
Drive list:
- drives.py now filters via `lsblk … -o NAME,SIZE,TYPE` keeping TYPE=disk,
so the live ISO's own squashfs (loop) and CD-ROM (rom) stop appearing
as install targets.
Boot menu:
- iso/build.sh sed-rebrands "Arch Linux install medium" →
"Furtka Live Installer" across grub/, syslinux/, and efiboot/loader/
entries. Verified zero leftovers against the current releng profile.
Styling:
- static/style.css adopts the website's design tokens (palette,
typography, gate-mark accent), with light + dark via prefers-color-scheme.
- New base.html with header (gate SVG + FURTKA·INSTALLER wordmark + step
indicator) and footer; all install templates extend it.
- Drive picker uses radio cards with score chip; overview uses a summary
table and a destructive "wipe drive" button.
Tests: 17 pass (4 new in test_app.py covering validation + config builders,
2 new in test_drives.py covering the lsblk filter). Ruff clean.
README roadmap updated to mark these done and explicitly defer the
26.0-alpha release until archinstall actually completes end-to-end on a VM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:54:49 +02:00
|
|
|
assert cfg["hostname"] == "h"
|
|
|
|
|
assert cfg["locale_config"]["locale"] == "pl_PL.UTF-8"
|
2026-04-14 18:29:42 +02:00
|
|
|
# Users moved out of config into creds once we adopted archinstall 4.x's
|
|
|
|
|
# `!password` sentinel; config only carries a gpasswd in custom_commands
|
|
|
|
|
# so the user lands in the docker group after docker is pacstrapped.
|
|
|
|
|
assert "users" not in cfg
|
2026-04-14 19:51:50 +02:00
|
|
|
assert cfg["custom_commands"][0] == "gpasswd -a u docker"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_build_archinstall_config_includes_post_install_bootstrap(monkeypatch):
|
|
|
|
|
# The installed system should come up with a Furtka landing page at
|
|
|
|
|
# http://<hostname>.local. That means caddy + avahi pacstrapped, the
|
|
|
|
|
# matching services enabled, a Caddyfile + index.html written into the
|
|
|
|
|
# target rootfs, and nss-mdns spliced into nsswitch.conf.
|
|
|
|
|
import app as app_module
|
|
|
|
|
|
|
|
|
|
monkeypatch.setattr(app_module, "build_disk_config", lambda d: {"stubbed_device": d})
|
|
|
|
|
|
|
|
|
|
cfg = build_archinstall_config(
|
|
|
|
|
{
|
|
|
|
|
"hostname": "heimserver",
|
|
|
|
|
"username": "u",
|
|
|
|
|
"password": "pw12345678",
|
|
|
|
|
"language": "en",
|
|
|
|
|
"boot_drive": "/dev/sda",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for pkg in ("caddy", "avahi", "nss-mdns"):
|
|
|
|
|
assert pkg in cfg["packages"]
|
|
|
|
|
for svc in ("caddy", "avahi-daemon", "furtka-welcome", "furtka-status.timer"):
|
|
|
|
|
assert svc in cfg["services"]
|
|
|
|
|
|
|
|
|
|
joined = "\n".join(cfg["custom_commands"])
|
|
|
|
|
for path in (
|
|
|
|
|
"/etc/caddy/Caddyfile",
|
|
|
|
|
"/srv/furtka/www/index.html",
|
|
|
|
|
"/srv/furtka/www/style.css",
|
|
|
|
|
"/srv/furtka/www/status.json",
|
|
|
|
|
"/usr/local/bin/furtka-status",
|
|
|
|
|
"/usr/local/bin/furtka-welcome",
|
|
|
|
|
"/etc/systemd/system/furtka-status.service",
|
|
|
|
|
"/etc/systemd/system/furtka-status.timer",
|
|
|
|
|
"/etc/systemd/system/furtka-welcome.service",
|
|
|
|
|
):
|
|
|
|
|
assert path in joined, f"expected {path} to be written by custom_commands"
|
|
|
|
|
|
|
|
|
|
assert "mdns_minimal" in joined
|
|
|
|
|
assert "nsswitch.conf" in joined
|
|
|
|
|
# The chosen hostname is pinned into the static HTML at install-time.
|
|
|
|
|
assert "s/__HOSTNAME__/heimserver/g" in joined
|
feat: webinstaller writes archinstall config + execs install, styled
Wires the live-ISO wizard from "shows three screens" to "actually invokes
archinstall on the chosen disk", plus first-pass styling so it stops looking
like raw <h1>/<form>.
Webinstaller flow:
- S1 form gains username/password/password2/language with server-side
validation (hostname/username regex, ≥8 char password, match check).
- /install/run writes user_configuration.json + user_credentials.json
(creds 0600) to FURTKA_STATE_DIR (default /tmp/furtka), then execs
`archinstall --config … --creds … --silent` as a backgrounded subprocess.
- /install/log renders the subprocess output via meta-refresh polling.
- FURTKA_DRY_RUN=1 short-circuits the exec for testing.
- archinstall flag names verified against `archinstall --help` in an
archlinux container before committing.
Drive list:
- drives.py now filters via `lsblk … -o NAME,SIZE,TYPE` keeping TYPE=disk,
so the live ISO's own squashfs (loop) and CD-ROM (rom) stop appearing
as install targets.
Boot menu:
- iso/build.sh sed-rebrands "Arch Linux install medium" →
"Furtka Live Installer" across grub/, syslinux/, and efiboot/loader/
entries. Verified zero leftovers against the current releng profile.
Styling:
- static/style.css adopts the website's design tokens (palette,
typography, gate-mark accent), with light + dark via prefers-color-scheme.
- New base.html with header (gate SVG + FURTKA·INSTALLER wordmark + step
indicator) and footer; all install templates extend it.
- Drive picker uses radio cards with score chip; overview uses a summary
table and a destructive "wipe drive" button.
Tests: 17 pass (4 new in test_app.py covering validation + config builders,
2 new in test_drives.py covering the lsblk filter). Ruff clean.
README roadmap updated to mark these done and explicitly defer the
26.0-alpha release until archinstall actually completes end-to-end on a VM.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:54:49 +02:00
|
|
|
|
|
|
|
|
|
2026-04-14 18:29:42 +02:00
|
|
|
def test_build_archinstall_creds_uses_archinstall_sentinel_keys():
|
|
|
|
|
creds = build_archinstall_creds({"username": "u", "password": "pw12345678"})
|
|
|
|
|
assert creds["!root-password"] == "pw12345678"
|
|
|
|
|
assert creds["users"] == [
|
|
|
|
|
{
|
|
|
|
|
"username": "u",
|
|
|
|
|
"!password": "pw12345678",
|
|
|
|
|
"sudo": True,
|
|
|
|
|
"groups": [],
|
|
|
|
|
}
|
|
|
|
|
]
|