Commit graph

7 commits

Author SHA1 Message Date
f0acc4427e feat(furtka): release CI + \furtka update\ / \furtka rollback\ CLI
Slice 2 of the self-update story. Tagging a release on main now
produces a downloadable self-update payload on the Forgejo releases
page, and a running box can pull it down, verify it, atomically swap
to the new version, and health-check the result.

New pieces:

- scripts/build-release-tarball.sh <version> — packages the furtka/
  package + bundled apps/ + a root-level VERSION file as
  dist/furtka-<version>.tar.gz, plus a .sha256 sidecar and a
  release.json metadata blob.
- scripts/publish-release.sh <version> — uses the Forgejo v1 API to
  create a release (body pulled from the CHANGELOG section for this
  tag, pre-release auto-flagged on -alpha/-beta/-rc) and upload the
  three assets sequentially. Needs \$FORGEJO_TOKEN.
- .forgejo/workflows/release.yml — tag-triggered, runs both scripts
  with the new \$FORGEJO_RELEASE_TOKEN repo secret.
- furtka/updater.py — check_update, prepare_update, apply_update,
  run_update, rollback. Atomic symlink swap, sha256 verify (TOCTOU-
  safe: re-hashes on-disk file), health-check post-restart with
  auto-rollback on failure, stage-by-stage progress persisted to
  /var/lib/furtka/update-state.json so the UI can poll independent
  of the (restarting) API process. Path overrides via FURTKA_ROOT /
  FURTKA_STATE_DIR / FURTKA_LOCK_PATH so tests pin a tmpdir.
- furtka/cli.py — \`furtka update [--check] [--json]\` and
  \`furtka rollback\`.
- tests/test_updater.py — 15 tests: version compare, sha256 verify,
  tarball extract (including traversal refusal), lockfile, apply
  happy + rollback paths, rollback CLI, check_update with stubbed
  Forgejo.
- iso/build.sh — writes VERSION at the tarball root so the install
  path matches the self-update path (previously assumed only the
  release script did this).

RELEASING.md now points at the automated flow — no more manually
clicking "Create release" on the Forgejo UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:30:45 +02:00
df08938d7e refactor(webinstaller): extract inline payload constants to furtka/assets/
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>
2026-04-16 13:08:53 +02:00
9f4e514d8a feat(furtka): ship resource manager + fileshare app on the ISO — slice 3
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>
2026-04-15 10:06:01 +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
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
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