Slice 1a of the self-update story. Every HTML/CSS/shell-script/systemd-
unit payload that used to live as a triple-quoted string constant inside
webinstaller/app.py now lives as a real file under furtka/assets/:
furtka/assets/Caddyfile
furtka/assets/VERSION (new — matches pyproject.toml)
furtka/assets/www/{index.html, settings/index.html, style.css, status.json}
furtka/assets/bin/{furtka-status, furtka-welcome}
furtka/assets/systemd/furtka-{api,reconcile,status,welcome}.service
furtka/assets/systemd/furtka-status.timer
The installer now pulls each file from disk via _read_asset(). Byte-for-
byte identical output at install time — a fresh-ISO install should land
the same files in the same places with the same contents, verified by
tests/test_webinstaller_assets.py which reconstructs each base64 blob
and asserts equality against the on-disk asset.
iso/build.sh also copies furtka/assets/ next to the webinstaller source
at /opt/furtka/assets on the live ISO so _resolve_assets_dir() finds
them with a "next to me" lookup. In dev the same function walks two
levels up to the repo copy, so pytest works without any env vars.
furtka-status.sh drops the /etc/furtka/version TODO — it now reads
/opt/furtka/VERSION directly, which Slice 1b will upgrade to
/opt/furtka/current/VERSION once the symlink layout lands.
_FURTKA_WRAPPER_SH (the 5-line /usr/local/bin/furtka shim) stays inline;
it's tiny and not asset-shaped.
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>
MENU TITLE in the syslinux box now reads "Furtka" instead of
"Arch Linux", and the per-entry HELP line at the bottom speaks of
"Furtka Live Installer" / "install Furtka" instead of the upstream
Arch strings. Same sed-not-overlay approach we already use for the
menu entry labels.
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>
The "Drive list includes /dev/loop0 and /dev/sr0" rough-edge bullet
claimed the filter hadn't been added yet, but it has — `drives.py`'s
`parse_lsblk_output` skips everything with `TYPE != disk`, so loop
and rom devices never reach the picker. Tested.
Replaced with a note about the remaining real footgun: on bare-metal
installs, the USB stick the user booted from is `TYPE=disk` and would
show up alongside the actual install target, so a user could pick
their boot media by mistake. Not urgent while we test in VMs (the ISO
is a CD-ROM there, already filtered), but flagged so it's visible
when bare-metal testing starts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds `.forgejo/workflows/build-iso.yml` that runs `./iso/build.sh` and
uploads the resulting ISO as a `furtka-iso` artifact (retained 14 days).
Triggers on `push: branches: [main]` and `workflow_dispatch` only —
feature branches don't pay the 15-20 min build cost. `concurrency`
cancels older runs of the same ref so only the most recent push
produces an artifact.
This is what Robert asked for: push change → download ISO from the
Forgejo run → test without needing a laptop to build.
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>
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>
These two cost us real time tonight — SeaBIOS failing at ldlinux.c32,
then OVMF rejecting our unsigned GRUB with "Access Denied" until we
disabled Secure Boot in the firmware setup menu. Also flagged the
silent browser-upload truncation and the two known drive-list bugs
surfaced during the first live boot.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>