Replace the numeric "score N" pill with a Recommended badge on the
auto-selected drive plus size/type/health chips. The score itself
stays as the sort key, users just never see the raw number.
Why: Robert's 2026-04-14 wizard UX direction — less jargon, explain
Fachbegriffs, recommend defaults. A bare "score 35" gave users no
reason why one drive was picked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
End-to-end VM test today (2026-04-15) validated the resource manager
golden path but exposed four things blocking "dein-Vater-tauglich":
no way to configure an app without SSH+editor, no openssh, no nano,
keyboard stuck on US, and a samba healthcheck that cried wolf.
Resource-manager side:
- Manifest schema gains optional `settings` list (name/label/
description/type/required/default) and `description_long`.
- Bundled-app install opens a form rendered from the manifest;
submit carries values to `POST /api/apps/install` which writes
them into the new app's `.env` before the placeholder check runs.
- Installed apps grow an "Einstellungen" button that merges a
partial settings dict into the existing `.env` (unsubmitted
password fields = keep current), then reconciles to restart.
- New endpoints: `GET/POST /api/apps/<name>/settings`. Passwords
are never returned to the client.
- Fileshare manifest declares its SMB_USER/SMB_PASSWORD settings
in German with help text.
ISO side (so the next build is actually usable on the TTY):
- Add `openssh` to the package list + `sshd` to enabled services.
`archinstall: true` in 4.x did not install openssh-server.
- Add `nano` — `vim` was the only editor pitched at users, which
is brutal for first-timers (and was missing anyway).
- Keyboard layout follows the installer language (`de→de`, `pl→pl`,
`en→us`) instead of hardcoded `us`. A German user couldn't type
`/` or `-` at the console, making even `sudo nano` painful.
- Disable the dperson/samba healthcheck in the compose override —
it timed out on every probe while the share itself worked fine.
19 new tests (manifest parsing + settings-merge + two new API
endpoints over live HTTP); 94 total, format + lint clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds the management UI Daniel asked for end-of-session. Goes beyond
the original MVP scope (plan punted UI to v2) but the architecture
already supports it cleanly: stdlib http.server only, no new deps.
- furtka.api: minimal HTTP server. GET / serves a self-contained
HTML page (dark-mode card list, vanilla JS, no build step). GET
/api/apps + /api/bundled return JSON. POST /api/apps/{install,
remove} accept {"name": "..."} and call the same installer +
reconciler the CLI uses, so the placeholder-secret refusal and
per-app reconcile isolation flow through unchanged.
- furtka.cli: new `furtka serve` subcommand. Imports api lazily so
`furtka app list` / `reconcile` startup stays zero-cost.
- webinstaller: new furtka-api.service (Type=simple, restart on
failure, after reconcile). Caddyfile gets two new handle blocks
to reverse-proxy /api and /apps to localhost:7000. Landing page's
"App store coming soon" tile becomes a real "Manage installed apps
→" link to /apps.
- Bound to 127.0.0.1 by default; Caddy makes it LAN-reachable. The
UI shouts a "no auth, anyone on your LAN can install/remove" warning
at the top — Authentik integration is the proper fix later.
UX wrinkle worth noting: a placeholder-rejected install leaves the
app in /var/lib/furtka/apps/<name>/ (so the user can edit .env in
place). To re-trigger after editing, the Installed list now shows
both Reinstall and Remove buttons.
10 new tests: helper functions (list_installed, list_bundled with
hide-already-installed), install/remove endpoints with the no_docker
fixture, and two real-socket urllib smoke tests that boot the actual
HTTPServer on an ephemeral port and round-trip GET / + POST.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes the loop end-to-end. The ISO build now bundles the furtka/
package and the apps/ tree as a tarball; webinstaller hands it to
archinstall via custom_commands; the installed system gets the
`furtka` CLI, a boot-scan systemd unit, and the fileshare app
ready to install.
- iso/build.sh: stages furtka/ + apps/ into a tmpdir, drops
__pycache__, tarballs into airootfs/opt/furtka-resource-manager.tar.gz.
- webinstaller/app.py: _resource_manager_commands() reads the staged
payload at request-time, base64-encodes it into a single untar
command, and writes /usr/local/bin/furtka (PYTHONPATH wrapper, no
pip needed) + furtka-reconcile.service. Python pacstrapped so the
wrapper has an interpreter.
- Graceful degradation: dev box / CI without an ISO build has no
payload tarball, so those commands are skipped (logs a warning).
Tests cover both branches.
- furtka-reconcile.service is conditionally enabled only if the unit
file actually landed — keeps the systemctl enable line green when
the payload was absent.
- apps/fileshare/: first real Furtka app. dperson/samba on host
network, single named volume, .env.example with placeholder creds.
Manifest matches the schema locked in slice 1.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The post-reboot page told users to log in with the username and
password — but Furtka is browser-first; users aren't meant to touch
the TTY. Show the actual URL they should open instead, plus an mDNS
fallback hint.
Also pin the header SVG to width="24" height="24" so it can never
render at full viewport size, even if CSS somehow fails to load.
Belt-and-suspenders with the reboot-delay fix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The reboot route fired systemctl reboot in parallel with returning
the rebooting HTML. The browser's follow-up request for /static/style.css
was racing the shutdown — often the server was already gone, leaving
the page unstyled (inline SVG rendered at full viewBox size, filling
the screen). A small sleep gives the browser time to pull CSS + icons
before the network drops.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
systemd-boot is UEFI-only. Hardcoding it broke the install on
BIOS/legacy hosts with HardwareIncompatibilityError in
installer._add_systemd_bootloader. Detect via /sys/firmware/efi and
fall back to GRUB for BIOS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
archinstall runs `systemctl enable` over the `services` list *before*
custom_commands, so our own unit files (written in custom_commands)
didn't exist yet at enable-time and install aborted with
"Unit furtka-welcome.service does not exist". Keep `caddy` +
`avahi-daemon` in `services` since those are packaged units present
right after pacstrap; move `furtka-welcome` + `furtka-status.timer`
to a `systemctl enable` call appended to custom_commands so they fire
after the unit files land on disk.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Installs caddy + avahi + nss-mdns on the target and writes a small
landing page, live status tiles (uptime / docker version / free disk
via furtka-status.timer), and a console welcome banner — all via
archinstall's custom_commands so the payload travels with the
user_configuration.json. After reboot `http://<hostname>.local`
serves a Furtka-branded page on :80 instead of the bare Arch login.
No Authentik / no app store yet — demo shell for the real post-
install work (Robert's area).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three things are broken on origin/main as of 6114cb2, all found in one
red CI run:
- build-iso workflow couldn't reach docker. forgejo-runner's config
sets `docker_host: tcp://docker-in-docker:2375` but that env doesn't
propagate into job containers on `runs-on: ubuntu-latest`, and the
default job image has no docker CLI. Fix: pin `DOCKER_HOST` on the
job and apt-install `docker.io` before invoking `iso/build.sh`.
- Two tests asserted on the pre-4.x archinstall schema:
`creds["root_password"]` (now `!root-password`) and
`cfg["disk_config"]["device"]` / `cfg["users"]` (users moved to
creds; disk_config is now a full `default_layout` dict). Rewrote
the tests to reflect 4.x reality and monkeypatched `build_disk_config`
since its real body imports archinstall, which isn't on CI.
- Ruff flagged one line of `PROGRESS_PHASES` at 107 chars — collapsed
the column alignment. `ruff format` pulled in a couple of cosmetic
expansions in spawn_archinstall and the tests that had been drifting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two user-visible polish passes on top of the walking-skeleton install:
- Console welcome: live ISO's getty no longer shows the bare Arch prompt.
`/etc/hostname` is now `proksi` so avahi advertises `proksi.local`;
a systemd oneshot (`furtka-issue.service`, runs after
network-online.target) regenerates `/etc/issue` via
`/usr/local/bin/furtka-update-issue` to show both
`http://proksi.local:5000` (preferred, via mDNS — avahi and nss-mdns
are already in `packages.extra`) and the raw IP as a fallback for
networks where mDNS is flaky. `agetty --reload` nudges the already-
running login prompt to redraw.
- /install/log now polls a JSON endpoint (`/install/log.json`) every
3 s instead of meta-refresh, so expanding the collapsed log
`<details>` doesn't get eaten by the refresh. Noscript fallback
keeps the meta-refresh for JS-off users. When the install finishes,
the Done state shows a Reboot-now button that POSTs to
`/install/reboot` (guarded server-side to only reboot once status
is "done", so a panicked click mid-pacstrap can't brick the box).
A confirm() reminds the user to pull the USB / eject the ISO first.
End-to-end tested on a Proxmox VM 2026-04-14: boot → wizard →
archinstall → Done state → Reboot now → VM came back up → login as
created user → `docker ps` worked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two tangled changes to the install flow, batched because they're both
small and hit app.py:
1. Phase-based progress bar on /install/log. parse_install_progress()
scans the archinstall log for ordered phase markers ("Wiping
partitions", "Installing packages: ['base'", "Adding bootloader",
"Installation completed without any errors", …) and exposes
percent + user-facing phase label + status (running/done/error).
Template wraps the raw log in a collapsed <details> so the default
view stays calm; the meta-refresh stops once status is terminal.
If archinstall changes its stdout wording the bar stalls on the
last recognized phase — the install itself is unaffected.
2. Drop "docker" from the user's groups in creds and do the
`gpasswd -a <user> docker` via custom_commands instead.
archinstall creates users before pacstrapping the extras list, so
the docker group doesn't exist at user-create time —
caused the second real install to crash with
`gpasswd: group 'docker' does not exist`. custom_commands runs
at the very end, after docker is installed. Username is validated
by USERNAME_RE so no shell injection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
- Untrack archinstall/user_credentials.json; ship .example file and
add a root .gitignore so real creds stay out of git
- Fix SyntaxError in webinstaller/app.py (malformed "language" entry)
- Drop import-time lshw call in hardware.py
- Consolidate driveval/ and webinstaller/hardware.py into a single
webinstaller/drives.py with a list_scored_devices() API; step 2
now renders a proper <select> with name/size/score
- Replace dependancies.txt typo with webinstaller/requirements.txt
(Flask only — psutil was imported but unused)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>