furtka/CHANGELOG.md
Daniel Maksymilian Syrnicki e68ed279cc
All checks were successful
Build ISO / build-iso (push) Successful in 17m23s
CI / lint (push) Successful in 27s
CI / test (push) Successful in 1m2s
CI / validate-json (push) Successful in 24s
CI / markdown-links (push) Successful in 15s
Release / release (push) Successful in 11m34s
fix(https): make HTTPS opt-in to stop the BAD_SIGNATURE trap on fresh installs
Every Furtka since 26.5 shipped a Caddyfile with a
`__FURTKA_HOSTNAME__.local { tls internal }` site block, so every
first boot auto-generated a fresh self-signed CA + intermediate +
leaf. That worked for the first-ever Furtka user, but every reinstall
(or second box on the same LAN) produced a new CA whose intermediate
shared the fixed CN `Caddy Local Authority - ECC Intermediate` with
the previous one. Firefox caches intermediates by CN across profiles
— even private windows share cert9.db — so any visitor who had
trusted an older Furtka's CA got a cached intermediate with
mismatched keys when they hit the new box, producing
`SEC_ERROR_BAD_SIGNATURE`. Unlike UNKNOWN_ISSUER, Firefox has NO
"Advanced → Accept Risk" bypass for BAD_SIGNATURE, so fresh-install
boxes were effectively unreachable over HTTPS in any browser that
had ever seen a previous Furtka.

Validated live on the .46 test VM: fresh 26.14 ISO install → Firefox
hits BAD_SIGNATURE on https://furtka.local/ (even in private mode).
Chromium bypasses it via mDNS failure but the issue is the same.
openssl verify on the box confirms the chain is internally valid —
this is purely client-side cache pollution across boxes.

Fix:
- assets/Caddyfile: removed the hostname site block. Default install
  serves :80 only — https://furtka.local connection-refuses, which is
  a normal error every browser handles instead of the unbypassable
  crypto fault. Added top-level import of
  /etc/caddy/furtka-https.d/*.caddyfile so the /settings HTTPS toggle
  can drop a listener snippet there when a user explicitly opts in.
- furtka/https.py: set_force_https now writes TWO snippets atomically
  — the top-level hostname + tls internal block (enables :443) and
  the :80-scoped redirect (forces HTTP→HTTPS). Disable removes both.
  Reload failure rolls both back. Added _read_hostname + _https_snippet_content
  helpers with `/etc/hostname` → 'furtka' fallback so a missing
  hostname file doesn't produce an empty site block Caddy rejects.
- furtka/https.py::status: force_https now reads the listener
  snippet (was reading the redirect snippet). A redirect without a
  listener isn't actually HTTPS being served, so the listener is the
  authoritative "HTTPS is on" signal.
- furtka/updater.py: new _maybe_migrate_preserve_https hook runs
  inside _refresh_caddyfile on the 26.14 → 26.15 transition. If the
  box had the redirect snippet on disk (user had opted into HTTPS
  under the old regime), it writes the new listener snippet too so
  HTTPS keeps working after the Caddyfile swap removes the hostname
  block.
- webinstaller/app.py: post-install creates /etc/caddy/furtka-https.d/
  alongside /etc/caddy/furtka.d/ so the glob import can't trip an
  older Caddy on a missing path during the first reload.

Live-tested on .46: set_force_https(True) writes both snippets, Caddy
reloads, :443 listener comes up with fresh CA, curl -k returns 302,
HTTP 301-redirects. set_force_https(False) removes both snippets
atomically, :443 goes back to connection-refused.

Tests: test_https.py expanded from 13 to 15 cases. Toggle-on asserts
both snippets written + hostname substituted. Toggle-off asserts
both removed. Rollback cases verify BOTH snippets restore on reload
failure. New test_https_snippet_content_has_tls_internal_and_routes
locks the exact shape of the listener block.
test_webinstaller_assets.py: updated two old asserts that assumed
hostname block was in Caddyfile; new test_post_install_creates_https_snippet_dir
guards the new directory.

276 tests pass, ruff check + format clean.

Known remaining wart (documented in CHANGELOG): a browser that
trusted a prior Furtka CA still hits BAD_SIGNATURE on this box's
HTTPS after enabling it, because the fixed intermediate CN is a
Caddy-side limitation. Workaround: clear cert9.db or visit in a
fresh profile. Won't affect end users with one Furtka box ever.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 19:30:04 +02:00

32 KiB
Raw Permalink Blame History

Changelog

All notable changes to Furtka will be documented in this file.

The format is based on Keep a Changelog. This project uses calendar versioning: YY.N-stage (e.g. 26.0-alpha = 2026, release 0, alpha stage).

Unreleased

26.15-alpha - 2026-04-21

Fixed

  • HTTPS is now opt-in; fresh installs no longer hit unbypassable SEC_ERROR_BAD_SIGNATURE. Every version since 26.5 shipped a Caddyfile with a __FURTKA_HOSTNAME__.local { tls internal } site block, so Caddy auto-generated a self-signed root CA + intermediate
    • leaf on first boot. That worked for first-time-ever users, but every reinstall (or second Furtka box on the same LAN) produced a new CA with the same intermediate CN (Caddy Local Authority - ECC Intermediate — Caddy hardcodes it). Any browser that had ever trusted an earlier Furtka CA got a cached intermediate with mismatched keys, then Firefox's cert lookup substituted the cached intermediate when validating the new box's leaf → the signature check failed → SEC_ERROR_BAD_SIGNATURE, which Firefox has no "Advanced → Accept Risk" bypass for.
    • Removed the hostname site block from the default Caddyfile. Fresh installs serve :80 only; visiting https://furtka.local now yields a clean connection-refused instead of the crypto fault.
    • Added top-level import /etc/caddy/furtka-https.d/*.caddyfile. The /settings HTTPS toggle (via furtka.https.set_force_https) now writes TWO snippets atomically — the top-level hostname + tls internal block (enables :443) and the :80-scoped redirect (forces HTTP → HTTPS) — and removes both on disable. Caddy reloads after the pair-swap; failure rolls both back.
    • Webinstaller creates /etc/caddy/furtka-https.d/ during post-install alongside the existing furtka.d/.
    • updater._refresh_caddyfile runs a 26.14 → 26.15 migration: if the box already had the redirect snippet on disk (user had explicitly enabled "Force HTTPS" under the old regime), the migration also writes the new listener snippet so HTTPS keeps working across the upgrade.
  • status.force_https now reads the listener snippet, not the redirect snippet. A lone redirect without a :443 listener wouldn't actually serve HTTPS, so the listener file is the authoritative "HTTPS is on" signal. The UI on /settings sees the correct state as a result.

Known remaining UX wart: a browser that trusted a previous Furtka box still sees BAD_SIGNATURE when visiting this box's https:// after enabling HTTPS here — the fixed intermediate CN is a Caddy-side limitation we can't fix from Furtka. Fresh installs on a browser that never visited another Furtka box work correctly. Workaround: about:networking#sts → Forget → clear cert9.db.

26.14-alpha - 2026-04-21

Fixed

  • Landing page and /settings/ were silently bypassing the auth guard. Since 26.11 shipped login, the Caddyfile only reverse-proxied /api/*, /apps*, /login*, and /logout* to Python. Everything else — including / and /settings/ — fell through to Caddy's catch-all file_server and was served straight from assets/www/ without ever hitting the session check. The effect: a LAN visitor saw the box's hostname, IP, Furtka version, and the buttons for Update-now / Reboot / HTTPS-toggle. The API calls those buttons fired were all 401-auth-gated so actions didn't land, but the information leak and the "looks open" UX was a real bug. Caught in the 26.13 SSH test session when the user noticed Logout only showed up on /apps. Now Caddy routes / and /settings* through Python; a new _serve_static_www handler checks the session cookie, redirects to /login if unauthed, and reads the HTML from assets/www/ otherwise. Catch-all still serves /style.css, /rootCA.crt, and the runtime JSON files publicly — those don't need auth.
  • Logout link now shows on every authed page, not just /apps. The static HTML for / and /settings/ maintained their own nav separate from _HTML in api.py, so they never got the Logout entry when it was added in 26.11. Both nav bars now include it plus an inline doLogout() that POSTs /logout and bounces to /login, matching the pattern in _HTML.

26.13-alpha - 2026-04-21

Fixed

  • Upgrade path from pre-auth releases actually works. 26.11-alpha introduced from werkzeug.security import ... in furtka/auth.py, but werkzeug isn't installed on the target system — core runs as system Python with stdlib only, and flask>=3.0 in pyproject.toml is never pip-installed on the box. Fresh boxes from the 26.11/26.12 ISO without a manually-installed werkzeug crashed on import; boxes upgrading from pre-26.11 got double-broken by that plus the health check below. Replaced the werkzeug dependency with a stdlib-only furtka/passwd.py that uses hashlib.pbkdf2_hmac for new hashes and parses werkzeug's scrypt:N:r:p$salt$hex format for backward compatibility — existing users.json files created on the rare boxes that did have werkzeug keep working after this upgrade, no re-setup needed. from werkzeug.security import ... is gone from the import chain entirely; pyproject.toml's flask dep stays only for the live-ISO webinstaller.
  • Self-update no longer auto-rolls-back when crossing the auth boundary. updater._health_check pinged /api/apps and demanded a 200, which meant every 26.10 → 26.11+ upgrade hit the post-restart check, got a 401 (auth guard), and treated that as "server dead" → rollback. Now any 2xx4xx response counts as "server alive"; only connection-level failures or 5xx fail the check. 5xx still fails rollback because that means the new process is up but broken.
  • Install lock closes its race window. POST /api/apps/install used to release the fcntl lock immediately after the sync pre-validation so the systemd-run child could re-acquire it — leaving a tiny gap where a second POST could slip in, pass the lock check, and return 202. Both child processes would start, one would win the in-child lock, the other would die silently. Now the API also reads install-state.json and refuses with 409 if the stage is non-terminal (pulling_image, creating_volumes, starting_container). The fcntl lock stays as belt-and-suspenders.

26.12-alpha - 2026-04-21

Changed

  • App-Install geht async mit Live-Progress. POST /api/apps/install returnt jetzt 202 Accepted nach der synchronen Pre-Validation (Source auflösen, Files kopieren, .env schreiben, Placeholder- und Path-Checks). Den eigentlichen Docker-Teil (compose pull → volumes → compose up) dispatched der Handler als systemd-run --unit=furtka-install-<app> Hintergrund-Job, der seine Phase in /var/lib/furtka/install-state.json schreibt. Neues GET /api/apps/install/status für UI-Polling. Das Install-Modal zeigt jetzt live "Image wird heruntergeladen…" → "Speicherbereiche werden erstellt…" → "Container wird gestartet…" statt ~30 Sekunden totem "Installing…". Muster 1:1 parallel zu /api/catalog/sync/apply und /api/furtka/update/apply. Neue CLI- Subcommand furtka app install-bg <name> (intern, von der API aufgerufen); furtka app install für Terminal-User bleibt synchron. Die Reinstall-Taste in der App-Liste pollt ebenfalls den Install-Status und spiegelt die Phase im Button-Text.

26.11-alpha - 2026-04-21

Added

  • Login-auth for the Furtka web UI. Every /apps, /api/*, /, and /settings/ route now requires a signed-in session. New /login page serves a username/password form; POST /login validates against /var/lib/furtka/users.json (werkzeug PBKDF2- hashed), sets a furtka_session cookie (HttpOnly, SameSite= Strict, 7-day TTL), and redirects to /apps. POST /logout revokes the server-side session and clears the cookie. Unauthenticated HTML requests get a 302 to /login; unauthenticated API requests get 401 JSON. The old "No authentication on this UI yet" banner is gone; the /apps header picks up a Logout link instead.
  • First-run setup fallback for upgrade-path boxes. Boxes upgrading from 26.10-alpha have no users.json yet — on the first visit /login renders a setup form (username + password + password-confirm) that creates the admin record on submit. Fresh installs skip this: the webinstaller writes users.json during the chroot post-install step using the step-1 password, so the first browser visit after boot goes straight to the login form.
  • Caddy proxy routes /login and /logout. assets/Caddyfile gets two new handle blocks in the shared (furtka_routes) snippet so both the :80 block and the hostname.local, hostname HTTPS block forward the auth endpoints to the stdlib server on 127.0.0.1:7000. Without this Caddy would serve a 404 from the static file server.

Fixed

  • tests/test_installer.py ruff-format nit — the 26.10-alpha release commit had a misformatted list literal that failed ruff format --check. Caught when the Release page on Forgejo showed a red CI badge for the tag.
  • pyproject.toml version string bumped from the stale 26.8-alpha to 26.11-alpha. Release pipeline uses GITHUB_REF_NAME as source of truth for the artefact name, but having the two agree matters for local dev runs that read pyproject.toml.

26.10-alpha - 2026-04-21

Added

  • Remove-USB-stick hint on the installer's post-install screen. webinstaller/templates/install/rebooting.html now shows a bold "Remove the USB stick now" line before the reboot, plus a muted fallback explaining the BIOS boot-menu keys (F11/F12/Esc) if the machine boots back into the installer anyway. Caught on the first bare-metal test (Medion i5-4gen, 2026-04-21) where the box didn't boot the installed system without manual BIOS-order changes.
  • New path setting type for app manifests. Apps can now declare a setting with "type": "path" whose value is an absolute filesystem path on the host; docker-compose bind-mounts it via the usual .env substitution (${MEDIA_PATH}:/media). Unlocks media/data-heavy apps (Jellyfin, later Paperless/Nextcloud/Immich) where the user points at an existing folder instead of copying everything into a Docker volume. The install form renders path settings as a plain text input with a /mnt/… placeholder hint.
  • Server-side path validation. Both install_from() and update_env() refuse values that aren't absolute, don't exist, aren't directories, or resolve (after Path.resolve()) into a system-path deny-list (/, /etc, /root, /boot, /proc, /sys, /dev, /bin, /sbin, /usr/bin, /usr/sbin, /var/lib/furtka). Catches /mnt/../etc-style traversal too. Error messages surface in the existing install/edit modal error line.

26.9-alpha - 2026-04-21

Fixed

  • Landing-page app tiles with an open_url now open in a new tab (target="_blank" rel="noopener"), matching the Open button behaviour on /apps. Without this, clicking "Uptime Kuma" on the home screen replaced Furtka itself with the Kuma admin page. Internal links (the Manage → fallback for apps without an open_url) still open in the same tab.
  • scripts/publish-release.sh no longer fails the whole release when the ISO upload hits a Forgejo proxy 504. The core tarball + sha256 + release.json (which running boxes need for self-update) are uploaded first and the ISO is attempted last as a best-effort; a 504 now logs a warning and exits 0 so the release page still publishes. Surfaced by the 26.8-alpha cut: the tarball landed but the ~1 GB ISO upload timed out at the Forgejo reverse proxy.

Changed

  • furtka app list --json now mirrors /api/apps field-for-field — previously the CLI emitted a slim projection missing description_long, open_url, and settings. Anyone piping the CLI output into jq for automation was seeing an incomplete view.

26.8-alpha - 2026-04-20

Added

  • Live-installer ISO attached to the Forgejo release page. .forgejo/workflows/release.yml moves to the self-hosted runner, builds both the self-update tarball and the ISO, and scripts/publish-release.sh uploads the ISO as a fourth release asset (furtka-<version>.iso) alongside the existing tarball + sha256 + release.json. Fresh-install users can now grab the ISO from the release page instead of hunting through build-iso.yml artifact retention windows. ISO build step is continue-on-error so an ISO flake doesn't hold back the core tarball that running boxes need for self-update.
  • Reboot + Shut down buttons on /settings. Replaces the two "Coming next" placeholders with real actions backed by POST /api/furtka/power ({"action": "reboot" | "poweroff"}). Handler kicks a delayed systemd-run --on-active=3s systemctl {reboot|poweroff} so the HTTP response reaches the browser before the kernel loses network. Each button opens a native confirm dialog first (reboot: "back in ~30 s", shut down: "need to press the physical power button"), then the UI swaps to a status line and — after a reboot — polls /furtka.json until the box is back, reloading the page automatically. No auth (same posture as install/remove).
  • Manifest open_url field + Open button in /apps and on the landing page. Apps declare a URL template (e.g. smb://{host}/files for fileshare, http://{host}:3001/ for Uptime Kuma); the UI substitutes {host} with the current browser's hostname at render time so the link follows however the user reached Furtka (furtka.local, raw IP, a future reverse-proxy hostname). The landing page's hardcoded if app.name === 'fileshare' special-case is gone — any app with an open_url in its manifest now gets a proper "Open" link. The core seed apps/fileshare/manifest.json bumps to v0.1.2 to carry it.

Changed

  • .btn CSS class introduced so an <a> rendered-as-button lines up with its <button> siblings in .buttons. Needed because "Open" is a real link (middle-click, copy URL, screen readers) and HTML doesn't let <button> carry href.

Notes

  • 26.7-alpha was tagged but never published — the tag push didn't trigger release.yml (Forgejo race with the concurrent main push). 26.8-alpha supersedes it and carries the same content plus power actions.

26.6-alpha - 2026-04-20

Added

  • Apps catalog synced independently of core. A new daniel/furtka-apps Forgejo repo carries the bundled app catalog; running boxes pull the latest release via furtka-catalog-sync.timer (10 min post-boot + daily, ±6 h jitter) and extract atomically into /var/lib/furtka/catalog/. The resolver now prefers catalog apps over the seed /opt/furtka/current/apps/ tree that ships inside the core release tarball, so apps can update without cutting a Furtka core release. Manual trigger: "Sync apps catalog" button on /apps, or sudo furtka catalog sync at the console. Fresh boxes with no network fall back to the seed, so offline first-boot still shows installable apps. Installed apps are never auto-swapped — users click Reinstall in /apps to move an existing install onto a newer catalog version (settings merge-preserved via the existing installer.install_from path).
  • Catalog CLI: furtka catalog sync [--check] [--json] + furtka catalog status [--json]. Same shape as the core furtka update commands.
  • Catalog API endpoints: POST /api/catalog/sync/check, POST /api/catalog/sync/apply (detached via systemd-run for symmetry with /api/furtka/update/apply), GET /api/catalog/status. The existing /api/bundled endpoint keeps working as a backwards-compat alias for /api/apps/available, which now returns the union of catalog + seed apps with a new "source" field on each entry ("catalog" | "bundled").

Changed

  • furtka._release_common extracted from furtka.updater. Both updater and the new catalog module now share one implementation of the Forgejo-releases-API call, SHA256 verification, path-traversal-guarded tarball extraction, and CalVer comparison. Public updater surface unchanged.
  • _link_new_units now auto-enables newly-linked .timer units. On self-update, a fresh timer file (e.g. furtka-catalog-sync.timer added in this release) needs systemctl enable to actually start firing — linking alone isn't enough. Fresh installs get their enable via the webinstaller's _FURTKA_UNITS list as before.

Fixed

  • SHA-256 CA fingerprint no longer overflows the /settings Local HTTPS card on narrow viewports. .kv dd grid items now set min-width: 0 + overflow-wrap: anywhere so the colon-separated hex string breaks within the card's right edge instead of pushing past it.

26.5-alpha - 2026-04-20

Fixed

  • HTTPS handshake regression on the installed box (#10). Phase 1 shipped two linked bugs: the :443 { tls internal } site block had no hostname, so Caddy never issued a leaf cert and every SNI handshake died with SSL_ERROR_INTERNAL_ERROR_ALERT; and both furtka.https and the Caddyfile's /rootCA.crt handler referenced /var/lib/caddy/.local/share/caddy/pki/…, a path that doesn't exist because our systemd unit sets XDG_DATA_HOME=/var/lib. Force-HTTPS toggle made the brokenness user-visible by redirecting working HTTP to dead HTTPS. Fixed: the Caddyfile now ships a __FURTKA_HOSTNAME__.local, __FURTKA_HOSTNAME__ { tls internal } block with the placeholder substituted at install time (webinstaller/app.py) and on every self-update (furtka.updater._refresh_caddyfile reads /etc/hostname). auto_https disable_redirects keeps Caddy's built-in redirect out of the way of the /settings toggle. PKI paths corrected in both furtka/https.py and assets/Caddyfile. Verified end-to-end on the 192.168.178.110 test VM: TLS 1.3 handshake completes, leaf cert issued, /rootCA.crt returns 200.

Changed

  • Wizard footer version is now dynamic. webinstaller/app.py resolves the Furtka version at startup via a Flask context processor — reads /opt/furtka/VERSION on the live ISO (written by iso/build.sh from pyproject.toml at build time), falls back to pyproject.toml in dev runs, then to literal "dev". The 26.4 footer was hand-pinned and drifted within hours of release; that follow-up item is now closed.
  • Docs realigned with 26.4-alpha reality. apps/README.md added (manifest schema, volume namespacing, .env.example guardrails, SVG sanitiser limits, install/test flow). Root README.md roadmap updated with Phase 1 HTTPS + smoke-VM pipeline as shipped items and 26.4-alpha in the release list. iso/README.md corrected: mDNS is wired (not "later milestone"), post-install default URL is http://furtka.local (not proksi.local), HTTPS is available via tls internal since 26.4. website/README.md now documents the auto-deploy on push-to-main as the default path, manual deploy.sh as the SSH-hop fallback.

26.4-alpha - 2026-04-18

Added

  • Local HTTPS via Caddy tls internal on port 443. Caddy generates a per-box local root CA on first start; the Caddyfile now serves both :80 and :443 from the same routes. HTTP stays on by default — no regression for users who haven't trusted the CA yet. New "Local HTTPS" section in /settings shows the CA's SHA-256 fingerprint, offers a one-click download of rootCA.crt, links to the per-OS install guide at /https-install/, and exposes an opt-in "force HTTPS" toggle that only unhides itself once the current browser has already trusted the cert (so enabling it can't lock the user out of the settings page). Backend: GET /api/furtka/https/status and POST /api/furtka/https/force in furtka.https. The force toggle drops a Caddy import snippet into /etc/caddy/furtka.d/redirect.caddyfile and reloads Caddy; reload failure automatically rolls the snippet state back so a bad config can't wedge the next service start.
  • Impressum + Datenschutzerklärung on furtka.org (both DE and EN) covering §5 DDG and Art. 13 GDPR. Linked from the site footer on every page; bilingual with DE as the legally binding version.
  • Auto-deploy of furtka.org on push-to-main. New .forgejo/workflows/deploy-site.yml runs on the self-hosted runner (which is forge-runner-01 — the webserver host), so the deploy is just a local rsync + hugo --minify into /var/www/furtka.org/. No SSH, no secrets. Manual website/deploy.sh remains for out-of-band deploys.
  • Post-build smoke VM on Proxmox test host 192.168.178.165. Every build-iso run boots the freshly built ISO in a throwaway VM on pollux (8 GiB RAM / 2 vCPU — the 4 GB default OOM-ed the host during mkinitcpio), then curls :5000 to confirm the webinstaller is alive. VMs in VMID range 90009099 tagged with the commit SHA; last 5 kept for post-mortem debugging. Optional workflow_dispatch "Smoke latest ISO" re-tests the cached ISO in ~2 min without rebuilding. Step-level continue-on-error means a VM-side flake doesn't mark the ISO build red.

Fixed

  • Settings page "Installed" field now refreshes after a self-update. The /api/furtka/update/check response already carries current — the settings JS now drives upd-current from it the same way it drives upd-latest, so clicking "Check for updates" after a successful update reflects the new installed version without a force-reload.
  • Auto-reload on update completion is now reliable. Clicking "Update now" arms a 45 s fallback setTimeout(location.reload) in addition to the existing /update-state.json polling loop. If the mid-apply API restart drops the poll connection before stage: done is ever observed (as seen on the 2026-04-16 VM test), the fallback still brings the page up on the new version. The fallback is cleared on done (5 s reload wins) or rolled_back (user needs the error visible).
  • Version string in the webinstaller footer was pinned at 26.0-alpha and didn't track releases. Bumped to 26.4-alpha for this release; follow-up will make it render from pyproject.toml dynamically.

26.3-alpha - 2026-04-16

Fixed

  • Release workflow no longer depends on jq. The previous apt-get install -y jq step hung on a slow mirror for 15+ minutes and stalled the 26.2-alpha publish. publish-release.sh now assembles the release-create payload via a tiny python3 -c block — Python is always available on the Forgejo Actions runner. apt-get path removed entirely.

26.2-alpha - 2026-04-16

Fixed

  • Updater "Check for updates" no longer 404s when every release is a pre-release. check_update() queried Forgejo's /releases/latest, which silently excludes pre-releases (anything tagged -alpha/-beta/-rc) and returns 404 when there is no stable release. Switched to /releases?limit=1, which Forgejo sorts newest-first across all release kinds. During the alpha stage where every tag is a pre-release this is the only thing that works; once we tag a stable release, the same query still picks it up.

26.1-alpha - 2026-04-16

Added

  • Furtka self-update (Phase 2). Tagging a release on main fires .forgejo/workflows/release.yml, which packages furtka/ + apps/ + a root-level VERSION file as furtka-<tag>.tar.gz, uploads it plus a .sha256 + release.json to the Forgejo releases page, and makes the release available to running boxes. New CLI: furtka update [--check] + furtka rollback. New endpoints: POST /api/furtka/update/check + /apply + GET /api/furtka/update/status. UI: "Furtka updates" card on /settings shows installed vs latest, Update button runs the apply flow detached via systemd-run, progress polls /update-state.json served by Caddy so the mid-update API restart doesn't interrupt reporting. Atomic /opt/furtka/current symlink flip, auto-rollback on health-check failure post-restart, SHA256-verified downloads.

  • Per-app container image updates (Phase 1). POST /api/apps/<name>/update runs docker compose pull, compares the running container's image digest to the just-pulled local image digest per service, and only restarts containers whose image actually changed. Update button on each installed-app row in /apps. Keeps image: :latest pins simple — no compose-file mutations.

  • Per-version install layout on /opt/furtka/. Install now extracts the resource-manager payload to /opt/furtka/versions/<VERSION>/ and creates /opt/furtka/current as an atomic symlink; updates flip the symlink in place and systemctl link every unit from the shipped assets/systemd/ tree. Runtime JSON (status.json, furtka.json, update-state.json) moved to /var/lib/furtka/ so self-updates never clobber it.

  • On-box UI uplevel across three pages sharing one design system (/style.css served by Caddy). Redesigned landing page with a "Your apps" tile grid driven by /api/apps, a fileshare app tile that deep-links to smb://<host>.local/files, status tiles, and subtle "Coming next" links to furtka.org. /apps page renders real app icons inlined from each manifest's icon.svg (defensive SVG sanitiser — strips script/on*/javascript: content, 16 KB cap). New /settings page with About-this-box, Appearance, Furtka-updates, and Coming-next sections. Persistent top nav (Jakob's Law) on every page. Light-mode support via prefers-color-scheme.

  • Webinstaller step 2 (boot drive) now shows size / type / health chips plus a "Recommended" badge on the auto-selected drive instead of a raw numeric score.

  • Forgejo branch protection on main — no direct pushes except owner-whitelisted, required status checks (CI / lint*, CI / test*, CI / validate-json*), applied via the idempotent ops/forgejo/apply-branch-protection.sh script.

  • In-browser app settings, so users no longer need SSH + vim to configure an app before first install. Manifest gains optional settings (name/label/description/type/required/default) and description_long fields. Installing a bundled app opens a form rendered from the manifest; installed apps grow a "Settings" button that edits merged values (password fields blank = keep current). API: POST /api/apps/install now accepts a settings object in the JSON body; new GET/POST /api/apps/<name>/settings for inspecting and updating an installed app. Password values never leave the server.

  • nano added to the installer package list so users have a beginner-friendly editor at the console/SSH (was vim-only, which command not found'd under Arch 4.x because it was actually missing from the package set too).

  • openssh added explicitly to the installer package list and sshd added to enabled services. archinstall: true in archinstall 4.x did not actually install openssh-server, so the documented recovery path (SSH → edit .env) silently failed.

  • Forgejo Actions runner live on Proxmox VM (forge-runner-01, Ubuntu 24.04) with DinD sidecar — CI green end-to-end. Setup scripts in ops/forgejo-runner/.

  • Walking-skeleton live ISO (iso/build.sh). Overlays an Arch releng profile with Flask + the webinstaller, bakes a systemd unit that auto-starts the wizard on boot, produces a hybrid BIOS/UEFI ISO via mkarchiso in a privileged archlinux:latest container. Tested booting under OVMF in Proxmox — wizard screens 13 respond at http://<vm-ip>:5000.

  • Public website at furtka.org (website/). Hugo static site, English + German, served from /var/www/furtka.org on forge-runner-01 via nginx. Upstream openresty proxy handles TLS. Intentionally minimal single-page copy while the project is pre-alpha. Deploy is ./website/deploy.sh (rsync + remote Hugo build); one-time VM setup in ops/nginx/setup-vm.sh.

Changed

  • Every on-box asset (landing page, settings page, style.css, status/welcome scripts, systemd units, Caddyfile) moved from inline Python string constants in webinstaller/app.py into real files under furtka/assets/. The installer reads them from disk at install time; the self-updater ships them in the release tarball.
  • Settings-button label went from "Einstellungen" (prototyping leftover) to "Settings" — rest of the UI chrome is English.
  • Keyboard layout at the TTY now follows the chosen installer language (dede, plpl, enus) instead of hardcoding us. Previously German users couldn't type /, -, or = at the recovery console.
  • fileshare app: description_long + settings (SMB_USER, SMB_PASSWORD) for the new settings form. Docker-level healthcheck from dperson/samba is disabled in the compose override — it timed out under normal operation and marked a working share "unhealthy" in docker ps.
  • Project name finalized: Furtka. Working title "Homebase" retired. Domain furtka.org registered via Strato 2026-04-13.
  • Managed gateway NS hostnames updated from ns1.homebase.cloud / ns2.homebase.cloud to ns1.furtka.org / ns2.furtka.org.
  • Python package renamed from homebasefurtka in pyproject.toml.

26.0-alpha - 2026-04-13

First tagged snapshot. Pre-alpha — the installer does not yet boot, but the design is locked and the prototype components are shaped.

Added

  • Installer webapp prototype (webinstaller/) — Flask app with 3-step wizard (hostname → drive → overview), serves drive list via drives.py::list_scored_devices().
  • Drive scoring module (webinstaller/drives.py) — scores attached disks by type (NVMe/SSD/HDD), SMART health (smartctl -H), and size. Consumed by the installer and usable as a CLI.
  • Base archinstall configuration (archinstall/user_configuration.json) — systemd-boot, ext4, Docker + Compose preinstalled, server profile, SSH. Credentials template at archinstall/user_credentials.example.json (real credentials gitignored).
  • Installer wireframes (docs/installer-wireframes.md) — Robert's hand-drawn 4-screen UX sketches.
  • Competitor analysis (docs/competitors.md) — CasaOS, Umbrel, YunoHost reviewed across install flow, hardware detection, app store UX, reverse proxy/SSL, user complaints. Key finding: device-aware wizard + managed gateway are uncontested differentiators.
  • Wizard flow spec (docs/wizard-flow.md) — 8-screen first-boot flow extending Robert's wireframe with YunoHost-style domain/SSL/diagnostic/confirm screens. Locked tech picks with rejected alternatives.
  • Project README — vision, principles, architecture, key decisions, landscape, roadmap.

Decisions locked

  • Reverse proxy: Caddy (auto Let's Encrypt, simplest config)
  • Identity provider: Authentik (bundled SSO, every app auto-wired at install)
  • Managed gateway DNS: NS delegation to ns1.furtka.org / ns2.furtka.org (wildcard cert via Let's Encrypt DNS-01)
  • Local HTTPS: Local CA installed by user once (no browser warnings on *.proksi.local)
  • Base OS: Arch (rolling, Debian remains fallback)
  • Containers: Docker + Compose
  • License: AGPL-3.0