Commit graph

48 commits

Author SHA1 Message Date
cfc4c0b9c1 feat(furtka): resource-manager skeleton — manifest, scanner, CLI
Slice 1 of the Resource Manager (see docs/resource-manager.md +
plan in ~/.claude/plans/stateful-juggling-pike.md). Lays down the
read-only half: a JSON manifest schema with namespacing, a scanner
that walks /var/lib/furtka/apps/, and a `furtka` CLI with
`app list` and `reconcile --dry-run`. Reconciler / volume creation
/ docker compose calls land in the next slice.

- furtka.manifest: dataclass + load_manifest with required-field +
  type validation. volume_name() injects the furtka_<app>_<vol>
  namespace so apps can each declare a "data" volume without colliding.
- furtka.scanner: tolerant — broken manifest = ScanResult with error,
  not an exception. Lets reconcile log + skip rather than abort.
- furtka.cli: text + --json output. argparse with `app list` and
  `reconcile --dry-run`. main() returns int for clean exit codes.
- furtka.paths: FURTKA_APPS_DIR env override so tests don't need root.
- 19 new tests covering valid manifests, every validation branch,
  scanner edge cases (missing root, broken manifest, sort order), and
  the CLI subcommands.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 09:59:41 +02:00
28e82bfccb fix(webinstaller): point users at http://<hostname>.local after reboot
All checks were successful
Build ISO / build-iso (push) Successful in 16m54s
CI / lint (push) Successful in 25s
CI / test (push) Successful in 32s
CI / validate-json (push) Successful in 23s
CI / markdown-links (push) Successful in 13s
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>
2026-04-15 09:27:49 +02:00
8b00873da2 fix(webinstaller): delay reboot so the rebooting page can fetch CSS
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>
2026-04-15 09:24:05 +02:00
1d145f7f0c fix: pick bootloader based on firmware (BIOS → GRUB, UEFI → systemd-boot)
Some checks failed
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
Build ISO / build-iso (push) Has been cancelled
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>
2026-04-15 09:11:58 +02:00
54dd88d4c6 iso: brand syslinux menu header and BIOS help text
Some checks failed
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
Build ISO / build-iso (push) Has been cancelled
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>
2026-04-15 09:10:20 +02:00
3909ee781b style: ruff format webinstaller/app.py
All checks were successful
Build ISO / build-iso (push) Successful in 20m45s
CI / lint (push) Successful in 27s
CI / test (push) Successful in 32s
CI / validate-json (push) Successful in 23s
CI / markdown-links (push) Successful in 13s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 08:46:58 +02:00
8c56c036cb fix: enable Furtka units inside custom_commands, not services list
Some checks failed
Build ISO / build-iso (push) Successful in 16m44s
CI / lint (push) Failing after 25s
CI / test (push) Successful in 36s
CI / validate-json (push) Successful in 22s
CI / markdown-links (push) Successful in 29s
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>
2026-04-14 20:34:34 +02:00
8ed1d82fd3 feat: post-install bootstrap — land in Furtka after reboot
Some checks failed
Build ISO / build-iso (push) Successful in 16m47s
CI / lint (push) Failing after 32s
CI / test (push) Successful in 33s
CI / validate-json (push) Successful in 23s
CI / markdown-links (push) Successful in 13s
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>
2026-04-14 19:51:50 +02:00
dfdbdd69aa docs: sync README roadmap, runner-setup, and ops/ to today's reality
All checks were successful
Build ISO / build-iso (push) Successful in 17m13s
CI / lint (push) Successful in 26s
CI / test (push) Successful in 32s
CI / validate-json (push) Successful in 22s
CI / markdown-links (push) Successful in 13s
A lot moved since the last docs sweep. Catching everything up in one
batch so a newcomer (or future us) reading the repo isn't lied to.

**README.md roadmap:**
- Walking-skeleton live ISO: upgraded from "screens 1-3 work
  end-to-end" to "install runs to completion on a VM and the installed
  system logs in and runs `docker ps` without sudo".
- 26.0-alpha release: dropped the "deferred" note — its blocker
  (archinstall not completing) is gone; just needs a re-tag when we
  like the installer copy.
- Added an explicit "ISO-build in CI" line for the new
  `.forgejo/workflows/build-iso.yml`.
- Split the old "mDNS + local CA" item: mDNS is live (hostname baked
  in, avahi/nss-mdns in the image), HTTPS via local CA still open.
- Noted post-install reboot button, progress bar, archinstall 4.x
  schema work, console welcome, custom_commands docker group join in
  the wizard milestone bullet.

**docs/runner-setup.md:**
- Full rewrite for the docker-outside-of-docker architecture we
  actually run now (was still describing the DinD sidecar setup).
- Documents the `/data` symlink on the host that makes host-mode
  `-v /data/…:/work` resolve — the non-obvious piece that took the
  longest to nail down today.
- Describes the two runtime modes (`ubuntu-latest:docker://…` for CI,
  `self-hosted:host` for build-iso) and why each exists.
- Adds the `upload-artifact@v3` pin note — v4+ fails on Forgejo with
  `GHESNotSupportedError`.

**ops/forgejo-runner/compose.yml + config.yml:**
- Compose now matches what's actually running: DooD (no DinD sidecar),
  runs as root so apk can install nodejs + docker-cli at startup,
  /var/run/docker.sock bind-mounted.
- Config gets the three explicit label mappings and DooD
  `docker_host` + `valid_volumes`.

**.forgejo/workflows/build-iso.yml:**
- Added `paths-ignore` for docs/website/*.md so doc-only commits don't
  kick off 5-min ISO rebuilds. Code + ISO overlay changes still
  trigger.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 19:28:33 +02:00
05ef50f74e ci: pin upload-artifact to v3 — v4+ unsupported on forgejo
All checks were successful
Build ISO / build-iso (push) Successful in 17m29s
CI / lint (push) Successful in 25s
CI / test (push) Successful in 32s
CI / validate-json (push) Successful in 24s
CI / markdown-links (push) Successful in 12s
Forgejo Actions only speaks the GHES-compatible @actions/artifact
protocol; upload-artifact@v4+ insists on the newer API and fails with
`GHESNotSupportedError`. Pin to v3, which uses the old protocol that
Forgejo implements.

Good news: the ISO itself built end-to-end in ~5m on the runner
(DooD + /data symlink resolved the path-mismatch). Only the upload
failed, and this pins it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 19:10:16 +02:00
e27c98c927 ci: retrigger build-iso with /data path unified across container+host
Some checks failed
CI / validate-json (push) Waiting to run
CI / markdown-links (push) Waiting to run
Build ISO / build-iso (push) Failing after 5m50s
CI / lint (push) Successful in 29s
CI / test (push) Has been cancelled
2026-04-14 19:03:37 +02:00
fb7a503df9 ci: retrigger build-iso with matching container+host workspace paths
Some checks failed
Build ISO / build-iso (push) Failing after 4s
CI / lint (push) Successful in 25s
CI / test (push) Successful in 33s
CI / validate-json (push) Successful in 24s
CI / markdown-links (push) Successful in 12s
2026-04-14 19:00:10 +02:00
cb646776f7 ci: retrigger build-iso with docker-cli-enabled runner
Some checks failed
Build ISO / build-iso (push) Failing after 4s
CI / lint (push) Successful in 25s
CI / test (push) Successful in 32s
CI / validate-json (push) Successful in 23s
CI / markdown-links (push) Successful in 13s
2026-04-14 18:58:03 +02:00
944a4fe220 ci: retrigger build-iso with nodejs-enabled runner
Some checks failed
CI / test (push) Waiting to run
CI / markdown-links (push) Waiting to run
Build ISO / build-iso (push) Failing after 4s
CI / lint (push) Successful in 28s
CI / validate-json (push) Has been cancelled
2026-04-14 18:56:50 +02:00
a2f079fcf2 ci: retrigger build-iso now that node is on the runner host
Some checks failed
Build ISO / build-iso (push) Failing after 2s
CI / lint (push) Successful in 29s
CI / validate-json (push) Successful in 23s
CI / markdown-links (push) Successful in 12s
CI / test (push) Has been cancelled
2026-04-14 18:54:14 +02:00
cb0ffc217f ci: retrigger build-iso now that runner has self-hosted:host label mapping
Some checks failed
Build ISO / build-iso (push) Failing after 2s
CI / lint (push) Successful in 25s
CI / test (push) Successful in 32s
CI / validate-json (push) Successful in 25s
CI / markdown-links (push) Has been cancelled
2026-04-14 18:52:34 +02:00
e9e8bd3319 ci: run build-iso on the runner host (DooD path fix)
Some checks failed
CI / test (push) Waiting to run
Build ISO / build-iso (push) Failing after 6s
CI / lint (push) Successful in 25s
CI / validate-json (push) Successful in 23s
CI / markdown-links (push) Has been cancelled
Now that the runner uses docker-outside-of-docker, volume mounts in
`build.sh` (`docker run -v \$REPO_ROOT:/work ...`) are interpreted by
host docker — so `\$REPO_ROOT` must be a real host path. When the job
runs inside a job container, `\$REPO_ROOT` is only valid in the job
container's filesystem namespace and host docker can't find it, hence
`bash: /work/iso/build.sh: No such file or directory`.

Fix: switch `runs-on` to `self-hosted`. Forgejo-runner exposes that
label out of the box and, with no matching container image mapping,
runs steps directly on the runner VM. Checkout writes to a real host
path; `docker run -v …` then mounts a path both the outer CLI and
host docker agree on.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:50:47 +02:00
a6cccc67c1 ci: drop duplicate docker.sock mount in build-iso
Some checks failed
Build ISO / build-iso (push) Failing after 6s
CI / lint (push) Successful in 26s
CI / test (push) Successful in 33s
CI / validate-json (push) Successful in 23s
CI / markdown-links (push) Has been cancelled
Forgejo-runner's valid_volumes already injects /var/run/docker.sock
into every job container, so the explicit `container.volumes` mount
in the workflow triggered 'Duplicate mount point' and the job never
started. Removed — DOCKER_HOST env is enough.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:49:12 +02:00
0f0308bf68 ci: switch build-iso to docker-outside-of-docker
Some checks failed
Build ISO / build-iso (push) Failing after 46s
CI / lint (push) Successful in 25s
CI / test (push) Successful in 32s
CI / validate-json (push) Successful in 24s
CI / markdown-links (push) Successful in 14s
The DinD setup was the wrong tool here: forgejo-runner runs on host
docker, but it spawned jobs via the DinD sidecar — meaning jobs
were isolated inside DinD's own docker namespace and couldn't reach
`docker-in-docker` by hostname, and couldn't see the
`forgejo-runner_default` network (which only exists on host docker).

Switched the runner (compose.yml + data/config.yml) to talk directly
to host docker via `/var/run/docker.sock` and added it to the host
`docker` group (GID 988) so the non-root runner user can use the
socket. `valid_volumes` now whitelists the socket so job containers
can mount it too.

Workflow now mounts /var/run/docker.sock into the job container and
points DOCKER_HOST at that unix socket. `./iso/build.sh` then runs
its inner `docker run --privileged archlinux:latest` against the
host daemon — no nested docker.

Tradeoff: this is less isolated than DinD (jobs have full host docker
access — they could spawn arbitrary containers), but on a dedicated
single-user build VM the DooD simplification is worth it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:45:32 +02:00
4c5a00a0e0 ci: drop ineffective container.options override for build-iso
Some checks failed
Build ISO / build-iso (push) Failing after 1s
CI / lint (push) Failing after 1s
CI / test (push) Failing after 1s
CI / validate-json (push) Failing after 1s
CI / markdown-links (push) Failing after 1s
forgejo-runner 6.4 filters `--network` out of `container.options`, so
the workflow-level override was silently ignored and the job kept
landing on a per-task network where `docker-in-docker` didn't resolve.
Fixed at the right level by editing the runner's `/data/config.yml`
(`container.network: "forgejo-runner_default"`) and restarting the
forgejo-runner container — every job now joins the shared network so
DOCKER_HOST=tcp://docker-in-docker:2375 just works.

Workflow trimmed back to only what's needed: DOCKER_HOST env pin. The
default runner image (catthehacker/ubuntu:act-latest) already has the
docker CLI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:40:16 +02:00
ba36bb4741 ci: attach build-iso job to DinD network, pin lychee-action source
Some checks failed
Build ISO / build-iso (push) Failing after 5s
CI / lint (push) Successful in 27s
CI / test (push) Successful in 44s
CI / markdown-links (push) Failing after 1s
CI / validate-json (push) Failing after 10m34s
- build-iso: the job container was on a per-job docker network, so
  `docker-in-docker` (the DinD sidecar hostname on
  `forgejo-runner_default`) didn't resolve. Pin the container to that
  shared network via `container.options: --network forgejo-runner_default`.
  catthehacker/ubuntu:act-latest already has the docker CLI, so drop
  the apt-get step.

- ci.yml markdown-links: forgejo's action mirror at data.forgejo.org
  doesn't carry `lycheeverse/lychee-action`, so `uses:` was 404ing
  before the step could even run (rendering continue-on-error moot).
  Fully-qualified GitHub URL bypasses the mirror.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:37:54 +02:00
a777efd4c0 ci: green the pipeline — tests match 4.x schema, build-iso hits DinD, lint clean
Some checks failed
Build ISO / build-iso (push) Failing after 20s
CI / lint (push) Successful in 26s
CI / test (push) Successful in 31s
CI / validate-json (push) Successful in 23s
CI / markdown-links (push) Failing after 2s
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>
2026-04-14 18:29:42 +02:00
9d8fd34043 docs: reflect reality on drive filtering in iso/README
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>
2026-04-14 18:18:24 +02:00
6114cb2f27 ci: build the live ISO on push-to-main and publish as artifact
Some checks failed
Build ISO / build-iso (push) Failing after 19s
CI / lint (push) Failing after 27s
CI / test (push) Failing after 41s
CI / validate-json (push) Successful in 24s
CI / markdown-links (push) Failing after 2s
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>
2026-04-14 18:13:15 +02:00
7442dbe47e feat: console welcome with proksi.local + post-install reboot flow
Some checks failed
CI / lint (push) Failing after 28s
CI / test (push) Failing after 32s
CI / validate-json (push) Successful in 24s
CI / markdown-links (push) Failing after 2s
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>
2026-04-14 18:08:59 +02:00
3a259beb98 feat: install progress bar + fix docker group creation order
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>
2026-04-14 17:07:57 +02:00
51cdf460d9 fix: wire webinstaller to archinstall 4.x config schema
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>
2026-04-14 17:00:39 +02:00
15b876c70a feat: webinstaller writes archinstall config + execs install, styled
Some checks failed
CI / lint (push) Failing after 25s
CI / test (push) Successful in 31s
CI / validate-json (push) Successful in 23s
CI / markdown-links (push) Failing after 2s
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
defd2eda06 feat: publish public website at furtka.org
Some checks failed
CI / lint (push) Successful in 24s
CI / test (push) Successful in 32s
CI / validate-json (push) Successful in 23s
CI / markdown-links (push) Failing after 2s
Hugo static site with an intentionally minimal single-page copy — English
default, German under /de/ — while the project stays pre-alpha. No CMS, no
external theme, no webfonts, no external requests. System-UI sans on a
paper-white / near-black palette with a deep crimson accent; a small
wicket-gate SVG as the sole brand mark.

Hosting: nginx on forge-runner-01 serves /var/www/furtka.org; the upstream
openresty proxy terminates TLS so the VM itself only speaks plain HTTP.
Deploy is ./website/deploy.sh (rsync + remote hugo --minify). One-time VM
bootstrap in ops/nginx/setup-vm.sh.
2026-04-14 10:27:51 +02:00
7f15543f1c docs: capture UEFI + Secure Boot gotchas in iso/README.md
Some checks failed
CI / lint (push) Successful in 42s
CI / test (push) Successful in 47s
CI / validate-json (push) Successful in 38s
CI / markdown-links (push) Failing after 2s
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>
2026-04-13 23:57:54 +02:00
a535debf2e 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>
2026-04-13 23:55:58 +02:00
03b2b7d451 chore: rename project Homebase → Furtka, domain furtka.org
Some checks failed
CI / lint (push) Successful in 26s
CI / test (push) Successful in 31s
CI / validate-json (push) Successful in 22s
CI / markdown-links (push) Failing after 2s
furtka.org registered via Strato 2026-04-13, so the working title is
retired. Python package, managed-gateway NS hostnames, and repo URLs all
follow. The CHANGELOG "Unreleased" section documents the switch so the
history is preserved at the 26.0-alpha → next-release boundary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:43:34 +02:00
6cf65f2c36 ci: stand up forge-runner-01 with DinD sidecar, fix doc label bug
Some checks failed
CI / lint (push) Successful in 1m12s
CI / test (push) Successful in 32s
CI / validate-json (push) Successful in 22s
CI / markdown-links (push) Failing after 2s
Bootstrap script + compose + config checked in under ops/forgejo-runner/
so a second runner is a scripted setup. runner-setup.md corrects the
register label format (<name>:docker://<image>, not bare names) and
documents the Ubuntu systemd-resolved DNS gotcha.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 21:31:35 +02:00
5be7206a93 docs: add runner setup + release publish to roadmap as next actions
Some checks failed
CI / lint (push) Failing after 2s
CI / test (push) Failing after 2s
CI / validate-json (push) Failing after 2s
CI / markdown-links (push) Failing after 1s
Mark release-process + CI work complete. Add two next-session TODOs
for Daniel: stand up the forgejo-runner (without which CI queues
forever) and publish the 26.0-alpha Forgejo Release.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:28:08 +02:00
852efdb0ed ci: add Forgejo Actions workflow with ruff, pytest, JSON + link checks
Some checks failed
CI / lint (push) Failing after 36s
CI / test (push) Failing after 1s
CI / validate-json (push) Failing after 2s
CI / markdown-links (push) Failing after 1s
- .forgejo/workflows/ci.yml: four jobs (lint, test, validate-json,
  markdown-links) running on push to main and on pull requests
- pyproject.toml: project metadata, flask dep, dev extras (ruff, pytest),
  ruff config (E/F/I/W/B/UP rulesets, 100-char lines, py311 target),
  pytest config (pythonpath=webinstaller so tests can import drives)
- tests/test_drives.py: 11 unit tests covering parse_size_gb (TB/GB/MB,
  European comma decimal, empty input, unknown units), drive type
  scoring (nvme/ssd/hdd), size scoring bands, and score_device summing
- .gitignore: ignore .pytest_cache, *.egg-info, .ruff_cache
- webinstaller/drives.py: refactor subprocess.run to capture_output
  kwarg (ruff UP022) — drops four lines, same behavior
- webinstaller/app.py: ruff-sorted imports (isort)

All checks pass locally: ruff check + format, pytest 11/11, JSON valid.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:24:05 +02:00
7759574481 docs: add changelog, contributor guide, release process, and runner setup
- CHANGELOG.md: Keep-a-Changelog format, [26.0-alpha] entry covering
  everything shipped so far (installer webapp, drive scoring, base
  archinstall config, wireframes, competitor analysis, wizard flow spec)
- CONTRIBUTING.md: dev setup, conventional commit format, code style
- RELEASING.md: calendar versioning rules (YY.N-stage, no "v" prefix)
  and the release workflow (bump changelog, commit, tag, push, create
  Forgejo Release)
- docs/runner-setup.md: install + register a forgejo-runner so the
  upcoming CI workflow actually executes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:23:48 +02:00
decab35fbf Sharpen README positioning based on competitor analysis
Targeted edits reflecting findings from docs/competitors.md:

- New "Recent signals" subsection under Landscape: Umbrel license
  complaints, Umbrel's 4+ year HTTPS refusal (#546), CasaOS
  maintenance mode
- "Where we differentiate" bullet 4 replaced: "Arch base (rolling
  release)" -> "HTTPS + AGPL from day one" — the actual counter-
  positioning shots vs Umbrel per the analysis
- "Gap we're targeting" tightened to include HTTPS-by-default
- Key Decisions table: added rows for locked tech picks (Caddy,
  Authentik, NS delegation, local CA) with link to wizard-flow.md
- Roadmap: marked competitor analysis + wizard flow spec complete,
  reordered so bootable image is clearly the next blocker, added
  Caddy/Authentik bootstrap and managed gateway infra items

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:06:22 +02:00
f06d32989d Add wizard-flow spec with locked tech picks
8-screen first-boot installer spec extending Robert's 4-screen
wireframe with the YunoHost-style post-install pattern (domain,
SSL, diagnostic, confirm). Covers:

- Entry point via https://proksi.local with local CA cert install
- Screens S1-S8, each mapped to archinstall config fields or side
  effects (SSL cert issuance, DNS delegation, diagnostic gates)
- Data model mapping wizard fields to user_configuration.json +
  user_credentials.json
- Locked tech picks with rejected alternatives: Caddy (reverse
  proxy), Authentik (SSO), NS delegation (managed gateway DNS),
  local CA (HTTPS on proksi.local)
- Open questions for Robert: Backend on/off meaning, local CA vs
  Tailscale ACME, UI framework choice, language list, S2 auto-setup
  branch behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 20:06:11 +02:00
1f0abc464a Add competitor analysis: CasaOS, Umbrel, YunoHost
Online research (no hands-on testing yet) covering install flow,
hardware detection, app store UX, reverse proxy/SSL, and common
user complaints for the top 3 competitors.

Key findings: device-aware install wizard and managed gateway/DNS
are both uncontested. Umbrel's PolyForm license and 4+ year refusal
of HTTPS on local UI are direct counter-positioning opportunities.
CasaOS is in maintenance mode (IceWhale moved to ZimaOS).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 19:55:26 +02:00
8e2fe83802 Fix installer basics: syntax, secrets, consolidation
- 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>
2026-04-13 19:44:29 +02:00
bf26fc881a basics and hardware eval 2026-04-13 19:38:34 +02:00
7f0b099ef3 Add next steps to roadmap + awesome-docker-compose resource
Daniel: test CasaOS/Umbrel/YunoHost on Proxmox.
Robert: get minimal bootable Arch image with Docker + installer webapp.
Robert's resource: awesome-docker-compose.com for later app store defaults.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:09:22 +01:00
b161107b5a Add competitive landscape section with 7 existing projects
CasaOS, Umbrel, Runtipi, HomeDock OS, Cosmos Server, YunoHost,
TurnKey Linux — plus analysis of where Homebase differentiates
(installer wizard, auto setup, gateway-as-a-service, Arch base).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:03:31 +01:00
163d43181a Add naming context: Homebase (working), Furtka/FurtkaOS (Robert's codename)
Furtka = Polish for "gate", plays on the gateway concept.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:56:42 +01:00
7e32d52456 Add installer wireframe docs from Robert's Proksi UX sketches
4-screen wizard flow: welcome/account setup, drive selection with
auto-setup, per-device purpose assignment, network/VPN config.
Accessed via http://proksi.local from a headless live USB boot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:54:39 +01:00
877f05324c Improve README: principles, architecture detail, actual progress, business model
Reflects actual project state from Robert/Daniel discussions — Arch already
running on Proxmox, webapp prototype working, and long-term Proxmox-style
business model.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:53:10 +01:00
c87701f1e5 Initial README with project vision, architecture, and roadmap
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:47:30 +01:00
0375e8f16f Initial commit 2026-03-06 10:45:49 +01:00