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>
32 KiB
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
:80only; visitinghttps://furtka.localnow yields a clean connection-refused instead of the crypto fault. - Added top-level
import /etc/caddy/furtka-https.d/*.caddyfile. The/settingsHTTPS toggle (viafurtka.https.set_force_https) now writes TWO snippets atomically — the top-level hostname +tls internalblock (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 existingfurtka.d/. updater._refresh_caddyfileruns 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.
- 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 (
status.force_httpsnow reads the listener snippet, not the redirect snippet. A lone redirect without a:443listener wouldn't actually serve HTTPS, so the listener file is the authoritative "HTTPS is on" signal. The UI on/settingssees 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-allfile_serverand was served straight fromassets/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_wwwhandler checks the session cookie, redirects to/loginif unauthed, and reads the HTML fromassets/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_HTMLinapi.py, so they never got the Logout entry when it was added in 26.11. Both nav bars now include it plus an inlinedoLogout()that POSTs/logoutand 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 ...infurtka/auth.py, but werkzeug isn't installed on the target system — core runs as system Python with stdlib only, andflask>=3.0inpyproject.tomlis 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-onlyfurtka/passwd.pythat useshashlib.pbkdf2_hmacfor new hashes and parses werkzeug'sscrypt:N:r:p$salt$hexformat for backward compatibility — existingusers.jsonfiles 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_checkpinged/api/appsand 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 2xx–4xx 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/installused 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 readsinstall-state.jsonand 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/installreturnt jetzt202 Acceptednach der synchronen Pre-Validation (Source auflösen, Files kopieren,.envschreiben, Placeholder- und Path-Checks). Den eigentlichen Docker-Teil (compose pull→ volumes →compose up) dispatched der Handler alssystemd-run --unit=furtka-install-<app>Hintergrund-Job, der seine Phase in/var/lib/furtka/install-state.jsonschreibt. NeuesGET /api/apps/install/statusfü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/applyund/api/furtka/update/apply. Neue CLI- Subcommandfurtka app install-bg <name>(intern, von der API aufgerufen);furtka app installfü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/loginpage serves a username/password form;POST /loginvalidates against/var/lib/furtka/users.json(werkzeug PBKDF2- hashed), sets afurtka_sessioncookie (HttpOnly,SameSite= Strict, 7-day TTL), and redirects to/apps.POST /logoutrevokes 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/appsheader picks up aLogoutlink instead. - First-run setup fallback for upgrade-path boxes. Boxes
upgrading from 26.10-alpha have no
users.jsonyet — on the first visit/loginrenders a setup form (username + password + password-confirm) that creates the admin record on submit. Fresh installs skip this: the webinstaller writesusers.jsonduring 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
/loginand/logout.assets/Caddyfilegets two newhandleblocks in the shared(furtka_routes)snippet so both the:80block and thehostname.local, hostnameHTTPS block forward the auth endpoints to the stdlib server on127.0.0.1:7000. Without this Caddy would serve a 404 from the static file server.
Fixed
tests/test_installer.pyruff-format nit — the 26.10-alpha release commit had a misformatted list literal that failedruff format --check. Caught when the Release page on Forgejo showed a red CI badge for the tag.pyproject.tomlversion string bumped from the stale 26.8-alpha to 26.11-alpha. Release pipeline usesGITHUB_REF_NAMEas source of truth for the artefact name, but having the two agree matters for local dev runs that readpyproject.toml.
26.10-alpha - 2026-04-21
Added
- Remove-USB-stick hint on the installer's post-install screen.
webinstaller/templates/install/rebooting.htmlnow 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
pathsetting 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.envsubstitution (${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()andupdate_env()refuse values that aren't absolute, don't exist, aren't directories, or resolve (afterPath.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_urlnow 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 (theManage →fallback for apps without anopen_url) still open in the same tab. scripts/publish-release.shno 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 --jsonnow mirrors/api/appsfield-for-field — previously the CLI emitted a slim projection missingdescription_long,open_url, andsettings. 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.ymlmoves to the self-hosted runner, builds both the self-update tarball and the ISO, andscripts/publish-release.shuploads 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 throughbuild-iso.ymlartifact retention windows. ISO build step iscontinue-on-errorso 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 byPOST /api/furtka/power({"action": "reboot" | "poweroff"}). Handler kicks a delayedsystemd-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.jsonuntil the box is back, reloading the page automatically. No auth (same posture as install/remove). - Manifest
open_urlfield + Open button in/appsand on the landing page. Apps declare a URL template (e.g.smb://{host}/filesfor 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 hardcodedif app.name === 'fileshare'special-case is gone — any app with anopen_urlin its manifest now gets a proper "Open" link. The core seedapps/fileshare/manifest.jsonbumps to v0.1.2 to carry it.
Changed
.btnCSS 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>carryhref.
Notes
26.7-alphawas tagged but never published — the tag push didn't triggerrelease.yml(Forgejo race with the concurrent main push).26.8-alphasupersedes 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-appsForgejo repo carries the bundled app catalog; running boxes pull the latest release viafurtka-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, orsudo furtka catalog syncat 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/appsto move an existing install onto a newer catalog version (settings merge-preserved via the existinginstaller.install_frompath). - Catalog CLI:
furtka catalog sync [--check] [--json]+furtka catalog status [--json]. Same shape as the corefurtka updatecommands. - Catalog API endpoints:
POST /api/catalog/sync/check,POST /api/catalog/sync/apply(detached viasystemd-runfor symmetry with/api/furtka/update/apply),GET /api/catalog/status. The existing/api/bundledendpoint 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_commonextracted fromfurtka.updater. Bothupdaterand the newcatalogmodule 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_unitsnow auto-enables newly-linked.timerunits. On self-update, a fresh timer file (e.g.furtka-catalog-sync.timeradded in this release) needssystemctl enableto actually start firing — linking alone isn't enough. Fresh installs get their enable via the webinstaller's_FURTKA_UNITSlist as before.
Fixed
- SHA-256 CA fingerprint no longer overflows the
/settingsLocal HTTPS card on narrow viewports..kv ddgrid items now setmin-width: 0+overflow-wrap: anywhereso 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 withSSL_ERROR_INTERNAL_ERROR_ALERT; and bothfurtka.httpsand the Caddyfile's/rootCA.crthandler referenced/var/lib/caddy/.local/share/caddy/pki/…, a path that doesn't exist because our systemd unit setsXDG_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_caddyfilereads/etc/hostname).auto_https disable_redirectskeeps Caddy's built-in redirect out of the way of the/settingstoggle. PKI paths corrected in bothfurtka/https.pyandassets/Caddyfile. Verified end-to-end on the 192.168.178.110 test VM: TLS 1.3 handshake completes, leaf cert issued,/rootCA.crtreturns 200.
Changed
- Wizard footer version is now dynamic.
webinstaller/app.pyresolves the Furtka version at startup via a Flask context processor — reads/opt/furtka/VERSIONon the live ISO (written byiso/build.shfrompyproject.tomlat build time), falls back topyproject.tomlin 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.mdadded (manifest schema, volume namespacing,.env.exampleguardrails, SVG sanitiser limits, install/test flow). RootREADME.mdroadmap updated with Phase 1 HTTPS + smoke-VM pipeline as shipped items and 26.4-alpha in the release list.iso/README.mdcorrected: mDNS is wired (not "later milestone"), post-install default URL ishttp://furtka.local(notproksi.local), HTTPS is available viatls internalsince 26.4.website/README.mdnow documents the auto-deploy on push-to-main as the default path, manualdeploy.shas the SSH-hop fallback.
26.4-alpha - 2026-04-18
Added
- Local HTTPS via Caddy
tls internalon port 443. Caddy generates a per-box local root CA on first start; the Caddyfile now serves both:80and:443from the same routes. HTTP stays on by default — no regression for users who haven't trusted the CA yet. New "Local HTTPS" section in/settingsshows the CA's SHA-256 fingerprint, offers a one-click download ofrootCA.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/statusandPOST /api/furtka/https/forceinfurtka.https. The force toggle drops a Caddy import snippet into/etc/caddy/furtka.d/redirect.caddyfileand 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.ymlruns on the self-hosted runner (which is forge-runner-01 — the webserver host), so the deploy is just a local rsync +hugo --minifyinto/var/www/furtka.org/. No SSH, no secrets. Manualwebsite/deploy.shremains for out-of-band deploys. - Post-build smoke VM on Proxmox test host 192.168.178.165. Every
build-isorun 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:5000to confirm the webinstaller is alive. VMs in VMID range 9000–9099 tagged with the commit SHA; last 5 kept for post-mortem debugging. Optionalworkflow_dispatch"Smoke latest ISO" re-tests the cached ISO in ~2 min without rebuilding. Step-levelcontinue-on-errormeans 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/checkresponse already carriescurrent— the settings JS now drivesupd-currentfrom it the same way it drivesupd-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.jsonpolling loop. If the mid-apply API restart drops the poll connection beforestage: doneis 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 ondone(5 s reload wins) orrolled_back(user needs the error visible). - Version string in the webinstaller footer was pinned at
26.0-alphaand didn't track releases. Bumped to26.4-alphafor this release; follow-up will make it render frompyproject.tomldynamically.
26.3-alpha - 2026-04-16
Fixed
- Release workflow no longer depends on
jq. The previousapt-get install -y jqstep hung on a slow mirror for 15+ minutes and stalled the 26.2-alpha publish.publish-release.shnow assembles the release-create payload via a tinypython3 -cblock — Python is always available on the Forgejo Actions runner.apt-getpath 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 packagesfurtka/+apps/+ a root-levelVERSIONfile asfurtka-<tag>.tar.gz, uploads it plus a.sha256+release.jsonto 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/settingsshows installed vs latest, Update button runs the apply flow detached viasystemd-run, progress polls/update-state.jsonserved by Caddy so the mid-update API restart doesn't interrupt reporting. Atomic/opt/furtka/currentsymlink flip, auto-rollback on health-check failure post-restart, SHA256-verified downloads. -
Per-app container image updates (Phase 1).
POST /api/apps/<name>/updaterunsdocker 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. Keepsimage: :latestpins 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/currentas an atomic symlink; updates flip the symlink in place andsystemctl linkevery unit from the shippedassets/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.cssserved by Caddy). Redesigned landing page with a "Your apps" tile grid driven by/api/apps, afileshareapp tile that deep-links tosmb://<host>.local/files, status tiles, and subtle "Coming next" links tofurtka.org./appspage renders real app icons inlined from each manifest'sicon.svg(defensive SVG sanitiser — strips script/on*/javascript: content, 16 KB cap). New/settingspage with About-this-box, Appearance, Furtka-updates, and Coming-next sections. Persistent top nav (Jakob's Law) on every page. Light-mode support viaprefers-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 idempotentops/forgejo/apply-branch-protection.shscript. -
In-browser app settings, so users no longer need SSH +
vimto configure an app before first install. Manifest gains optionalsettings(name/label/description/type/required/default) anddescription_longfields. 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/installnow accepts asettingsobject in the JSON body; newGET/POST /api/apps/<name>/settingsfor inspecting and updating an installed app. Password values never leave the server. -
nanoadded to the installer package list so users have a beginner-friendly editor at the console/SSH (wasvim-only, whichcommand not found'd under Arch 4.x because it was actually missing from the package set too). -
opensshadded explicitly to the installer package list andsshdadded to enabled services.archinstall: truein 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 inops/forgejo-runner/. -
Walking-skeleton live ISO (
iso/build.sh). Overlays an Archrelengprofile with Flask + the webinstaller, bakes a systemd unit that auto-starts the wizard on boot, produces a hybrid BIOS/UEFI ISO viamkarchisoin a privilegedarchlinux:latestcontainer. Tested booting under OVMF in Proxmox — wizard screens 1–3 respond athttp://<vm-ip>:5000. -
Public website at furtka.org (
website/). Hugo static site, English + German, served from/var/www/furtka.orgonforge-runner-01via 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 inops/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.pyinto real files underfurtka/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 (
de→de,pl→pl,en→us) instead of hardcodingus. Previously German users couldn't type/,-, or=at the recovery console. fileshareapp:description_long+settings(SMB_USER, SMB_PASSWORD) for the new settings form. Docker-level healthcheck fromdperson/sambais disabled in the compose override — it timed out under normal operation and marked a working share "unhealthy" indocker ps.- Project name finalized: Furtka. Working title "Homebase" retired. Domain
furtka.orgregistered via Strato 2026-04-13. - Managed gateway NS hostnames updated from
ns1.homebase.cloud/ns2.homebase.cloudtons1.furtka.org/ns2.furtka.org. - Python package renamed from
homebase→furtkainpyproject.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 viadrives.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 atarchinstall/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