-
26.18-alpha
Pre-releaseAll checks were successfulBuild ISO / build-iso (push) Successful in 18m44sDeploy site / deploy (push) Successful in 4sCI / lint (push) Successful in 28sCI / test (push) Successful in 1m28sCI / validate-json (push) Successful in 24sCI / markdown-links (push) Successful in 14sRelease / release (push) Successful in 12m5sreleased this
2026-06-04 16:39:04 +02:00 | 0 commits to main since this releaseFixed
on_startdependency hooks now receive the consumer's stored
credentials. Previously the reconciler handed anon_starthook only
FURTKA_CONSUMER_APP/FURTKA_CONSUMER_VERSION, so it had no way to learn
the consumer's existing secrets — which made the feature's own headline use
case (re-create a provider account, e.g. an MQTT user, after a wipe, with
the same password the consumer already holds) impossible without the
provider stashing a copy itself. The hook now also gets the consumer's.env
values, namespaced underFURTKA_CONSUMER_ENV_<KEY>(only UPPER_SNAKE_CASE
keys, so a hand-edited.envcan't produce a malformed--envargument).
on_startstays read-only with respect to the consumer: unlikeon_install,
its stdout is intentionally not merged back into the consumer's.env— it
reads consumer state to reconcile provider state, it doesn't mutate it.
Surfaced by building the first real provider/consumer catalog pair
(mosquitto + zigbee2mqtt) in daniel/furtka-apps.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
furtka-26.18-alpha.iso
6 downloads · 1.5 GiB
-
furtka-26.18-alpha.tar.gz
1 download · 72 KiB
-
furtka-26.18-alpha.tar.gz.sha256
5 downloads · 92 B
-
release.json
2 downloads · 173 B
-
26.17-alpha
Pre-releaseAll checks were successfulBuild ISO / build-iso (push) Successful in 18m3sCI / lint (push) Successful in 27sCI / test (push) Successful in 1m22sCI / validate-json (push) Successful in 25sCI / markdown-links (push) Successful in 13sRelease / release (push) Successful in 12m13sreleased this
2026-05-11 20:10:04 +02:00 | 2 commits to main since this releaseAdded
- App-to-app dependencies. Manifests gain an optional
requires
array; each entry names a provider app plus two optional hook scripts
that live in the provider's folder.on_installruns once via
docker compose execagainst the provider's running container while
the consumer is being installed (use case:mosquitto_passwda new
MQTT user for the consumer).on_startruns every boot during
reconcile, before the consumer's container starts (use case: make
sure the user still exists after a Mosquitto wipe). Hook stdout
parses asKEY=VALUElines and optionalFURTKA_JSON: {…}sentinel
lines, both validated against the existingSETTING_NAMEregex; the
values get merged into the consumer's.env(hook wins on conflict)
and the placeholder-secret check runs again over the merged file so
a hook returningMQTT_PASS=changemeis refused the same way an
unedited.env.exampleis. POST /api/apps/install/plan. New read-only endpoint that
returns the topo-sorted install order for a target app plus per-app
summaries (display_name, version, has_settings, installed flag). The
catalog UI calls this before opening the settings dialog so it can
show a confirm modal — "Installing zigbee2mqtt also installs
Mosquitto" — before anything mutates. Circular dependencies surface
as400 {error: "circular dependency: A -> B -> A"}; missing
providers as400 {error: "required app 'X' not found …"}./var/lib/furtka/install-plan.json(overridable via
FURTKA_INSTALL_PLAN). The HTTP install endpoint writes this before
it spawns the systemd-run background job so the runner knows the
full chain to pull → create volumes → fire hooks →compose upfor
in plan order. The runner consumes the file after reading so a stale
plan from a previous install can't accidentally steer the next one.
Changed
furtka reconcilenow visits apps in dependency order, not
alphabetical. Topo-sort overrequiresputs providers before
consumers so a consumer'son_starthook can talk to an already-up
provider. Within a tier, ties stay alphabetical so boot logs are
still deterministic across reboots. Apps with unresolvablerequires
(missing provider) are visited last; the per-app error-isolation in
reconcile then catches them without aborting the whole sweep.POST /api/apps/installrequiresconfirm_dependencies: true
when installing a named app would pull in transitive providers.
Without the flag, the endpoint returns409plus the full plan body
so the UI can render the confirm dialog without a second round-trip.
Lone-target installs (no transitive deps) keep the existing
one-click flow — no UX change forfileshare-style standalone apps.furtka app install <name>and the web UI now install transitive
dependencies automatically.furtka app install /path/to/dir
stays as today (single-app, dev/test workflow).compose_execandcompose_exec_scripthelpers in
furtka/dockerops.py. Both pass-T(no TTY) so they work from the
install runner and from reconcile; both raiseDockerErroron
non-zero exit or timeout.compose_exec_scriptstreams the script
body via stdin tosh -sso hooks don't need to be baked into the
provider's container image.
Notes
- Hook target service: v1 auto-picks the first service in the
provider's compose config. Works for Mosquitto, Postgres, Redis.
Multi-service providers (Authentik server+worker) will need an
optionalservicefield on the requirement entry; deferred until a
real case lands. - Hook timeouts:
on_install60 s,on_start30 s. Hardcoded for
v1 — revisit if a DB seed legitimately needs longer. - Removing an app is now blocked (
409 {dependents: […]}from the
API, exit 2 from the CLI) when other installed apps require it.
Downloads
-
Source code (ZIP)
2 downloads
-
Source code (TAR.GZ)
0 downloads
-
furtka-26.17-alpha.iso
4 downloads · 1.4 GiB
-
furtka-26.17-alpha.tar.gz
0 downloads · 72 KiB
-
furtka-26.17-alpha.tar.gz.sha256
5 downloads · 92 B
-
release.json
2 downloads · 173 B
- App-to-app dependencies. Manifests gain an optional
-
26.16-alpha
Pre-releaseAll checks were successfulBuild ISO / build-iso (push) Successful in 18m12sDeploy site / deploy (push) Successful in 3sCI / lint (push) Successful in 28sCI / test (push) Successful in 1m21sCI / validate-json (push) Successful in 24sCI / markdown-links (push) Successful in 13sRelease / release (push) Successful in 12m13sreleased this
2026-05-10 12:59:30 +02:00 | 4 commits to main since this releaseAdded
- Failed-login rate limit on
/login. A new in-memory
LoginAttemptsstore infurtka/auth.pyblocks brute-force attempts
after 10 failures in 15 minutes from the same (username, IP) pair,
with a 15-minute lockout. Successful logins clear the counter; a
systemctl restart furtkaclears any stuck lockout — fine for an
alpha single-user box. Tuple-keying means a flood from one source IP
can't lock the admin out from elsewhere; an attacker can rotate IPs
to keep probing forever, but each attempt still eats the PBKDF2 cost.
Locked attempts get aRetry-Afterheader so the UI can render the
cooldown. - Live-ISO boot USB is filtered out of the install drive picker. On
bare-metal installs,lsblkreports the USB stick the live ISO
booted from asTYPE=disk, so it showed up in the picker alongside
the real install target — a user could in theory pick the USB they
had just booted from.webinstaller/drives.pynow resolves
/run/archiso/bootmntviafindmnt, walks it up to its parent disk
vialsblk -no PKNAME, and drops that disk before scoring. On a
normal (non-live) box/run/archiso/bootmntdoes not exist and the
picker is unchanged.
Changed
- furtka.org homepage rebuild. Adopted the visual feel of Pascal's
prototype while keeping Furtka's voice, brand palette, and bilingual
structure: Three.js wireframe torus-knot behind the hero (color +
opacity tied to the existing--accentCSS var so light and dark
modes share one scene), scroll-driven camera zoom + tilt, GSAP +
ScrollTrigger card reveals, Lenis smooth scroll, gradient wordmark,
drop-shadow glow in dark mode, and a pulsing CTA pointing at
/releases. "What works today" / "What's coming next" lists moved
from markdown bullets into front-matter arrays and now render as
scroll-reveal cards. All vendor JS (Three.js r128, GSAP 3.12.2 +
ScrollTrigger, Lenis 1.0.33) is vendored locally under
website/assets/js/vendor/, fingerprinted with SRI, gated to the
homepage only, deferred so first paint isn't blocked, and
early-returned onprefers-reduced-motion. - Static-asset gzip on the furtka.org nginx (config only — needs a
deploy on forge-runner-01). Default nginx only gzipstext/html,
so the homepage HTML was the only asset coming back compressed. The
~600 KBthree.min.jsbundle (and the hashed CSS) were being shipped
uncompressed across the public openresty proxy.gzip_typesin
ops/nginx/furtka.org.confnow covers css/js/json/xml/svg/woff2.
Needssudo ops/nginx/setup-vm.shon forge-runner-01 to take effect
— the site-deploy workflow only rebuilds Hugo, it doesn't touch the
nginx config.
Downloads
-
Source code (ZIP)
2 downloads
-
Source code (TAR.GZ)
1 download
-
furtka-26.16-alpha.iso
9 downloads · 1.4 GiB
-
furtka-26.16-alpha.tar.gz
2 downloads · 61 KiB
-
furtka-26.16-alpha.tar.gz.sha256
9 downloads · 92 B
-
release.json
5 downloads · 173 B
- Failed-login rate limit on
-
26.15-alpha
Pre-releaseAll checks were successfulBuild ISO / build-iso (push) Successful in 17m23sCI / lint (push) Successful in 27sCI / test (push) Successful in 1m2sCI / validate-json (push) Successful in 24sCI / markdown-links (push) Successful in 15sRelease / release (push) Successful in 11m34sreleased this
2026-04-21 19:30:04 +02:00 | 10 commits to main since this releaseFixed
- 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.local
now 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
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 seesBAD_SIGNATUREwhen visiting this box'shttps://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 → clearcert9.db.Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
2 downloads
-
furtka-26.15-alpha.iso
8 downloads · 1.4 GiB
-
furtka-26.15-alpha.tar.gz
6 downloads · 60 KiB
-
furtka-26.15-alpha.tar.gz.sha256
12 downloads · 92 B
-
release.json
5 downloads · 173 B
- HTTPS is now opt-in; fresh installs no longer hit unbypassable
-
26.14-alpha
Pre-releaseAll checks were successfulBuild ISO / build-iso (push) Successful in 17m14sCI / lint (push) Successful in 26sCI / test (push) Successful in 1m2sCI / validate-json (push) Successful in 24sCI / markdown-links (push) Successful in 15sRelease / release (push) Successful in 11m26sreleased this
2026-04-21 18:16:42 +02:00 | 11 commits to main since this releaseFixed
- 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.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
2 downloads
- Landing page and
-
26.13-alpha
Pre-releaseAll checks were successfulBuild ISO / build-iso (push) Successful in 17m28sCI / lint (push) Successful in 27sCI / test (push) Successful in 59sCI / validate-json (push) Successful in 23sCI / markdown-links (push) Successful in 15sRelease / release (push) Successful in 11m38sreleased this
2026-04-21 17:03:28 +02:00 | 12 commits to main since this releaseFixed
- Upgrade path from pre-auth releases actually works. 26.11-alpha
introducedfrom 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.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.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/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 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.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
2 downloads
- Upgrade path from pre-auth releases actually works. 26.11-alpha
-
26.12-alpha
Pre-releaseAll checks were successfulBuild ISO / build-iso (push) Successful in 17m24sCI / lint (push) Successful in 26sCI / test (push) Successful in 43sCI / validate-json (push) Successful in 24sCI / markdown-links (push) Successful in 16sRelease / release (push) Successful in 11m34sreleased this
2026-04-21 15:50:49 +02:00 | 13 commits to main since this releaseChanged
- App-Install geht async mit Live-Progress.
POST /api/apps/install
returnt 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. Neues
GET /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.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
- App-Install geht async mit Live-Progress.
-
26.11-alpha
Pre-releaseAll checks were successfulBuild ISO / build-iso (push) Successful in 17m30sCI / lint (push) Successful in 27sCI / test (push) Successful in 43sCI / validate-json (push) Successful in 31sCI / markdown-links (push) Successful in 15sRelease / release (push) Successful in 11m38sreleased this
2026-04-21 13:01:17 +02:00 | 14 commits to main since this releaseAdded
- 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 /login
validates against/var/lib/furtka/users.json(werkzeug PBKDF2-
hashed), sets afurtka_sessioncookie (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/appsheader picks up aLogoutlink
instead. - First-run setup fallback for upgrade-path boxes. Boxes
upgrading from 26.10-alpha have nousers.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/Caddyfile
gets two newhandleblocks in the shared(furtka_routes)
snippet so both the:80block and thehostname.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.pyruff-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.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.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
- Login-auth for the Furtka web UI. Every
-
26.10-alpha
Pre-releasereleased this
2026-04-21 11:48:07 +02:00 | 16 commits to main since this releaseAdded
- 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.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 (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.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
- Remove-USB-stick hint on the installer's post-install screen.
-
26.9-alpha
Pre-releaseAll checks were successfulBuild ISO / build-iso (push) Successful in 20m49sCI / lint (push) Successful in 1m13sCI / test (push) Successful in 48sCI / validate-json (push) Successful in 44sCI / markdown-links (push) Successful in 16sRelease / release (push) Successful in 13m31sreleased this
2026-04-20 18:51:30 +02:00 | 20 commits to main since this releaseFixed
- 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 an
open_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 missing
description_long,open_url, andsettings. Anyone piping the
CLI output into jq for automation was seeing an incomplete view.
Downloads
-
Source code (ZIP)
1 download
-
Source code (TAR.GZ)
1 download
- Landing-page app tiles with an