- error state gets a 'Try again' button (settings are kept) plus links back
to the disk / settings pages; the page reloads into it instead of only
recolouring the bar
- names are lowercased instead of rejected; system account names (root,
caddy, docker, …) are refused with a clear message
- plain wording: 'Server name' with a live http://<name>.local preview,
'Pick a disk' with an explicit erase warning, a readable confirm table
(language label, disk size/type) and a Back button
- the password hint says it is also the SSH / console password; the
language hint says the wizard itself is English
- wizard pages redirect to the progress page while an install is running
- progress page: aria-live phase, aria-valuenow updates, 'lost connection'
note, 'fine to close this page'
- .local fallback on the reboot page: router device list, IP may change
- live console banner says a network cable with internet is required
- target gets a systemd-networkd-wait-online --any drop-in: boards with a
second unplugged NIC (or Ethernet + Wi-Fi) waited up to 2 min at boot
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wq1hzUp4ZmCKHDuLKCnu73
- an unfinished install-state.json whose runner is gone (free lock, >60 s
old — reboot, OOM kill, runner never started) is marked 'error' instead
of blocking every later install with 409 until someone fixes it over SSH
- read_state in install_runner/updater tolerates unreadable and non-object
files
- Forgejo API + release downloads wrap OSError and HTTPException (socket
timeouts, resets, disk full) — the update/catalog check endpoints used to
drop the connection on a flaky network
- catalog sync records any failure as the 'error' stage, not just
CatalogError
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wq1hzUp4ZmCKHDuLKCnu73
updater:
- /opt/furtka/current is swapped via rename() instead of unlink+symlink,
so a power cut can't leave the box without a current version
- OSError after the flip (disk full writing the Caddyfile) now rolls back
too; rollback restores the previous Caddyfile and reloads caddy +
daemon-reload
- the health check keeps polling on read timeouts / RemoteDisconnected
instead of escaping and skipping the rollback
- run_update writes an 'error' stage for any failure (download, sha,
disk full); the settings page shows it instead of spinning on
'Downloading…'
auth:
- users.json that exists but is empty/corrupt/unreadable raises instead of
reading as 'no admin yet' — before, a truncated file made /login an open
create-admin form for the whole LAN. /login answers 503 with SSH recovery
steps instead
- save_users fsyncs before the rename
- first-run admin creation is serialised (threaded server)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wq1hzUp4ZmCKHDuLKCnu73
- app names from URL paths and JSON bodies must match APP_NAME_RE; installer
remove()/update_env() check it too. Before, {"name": ".."} or "/" sent
remove() into shutil.rmtree on /var/lib/furtka or the root filesystem
- manifests must carry a valid app name as well
- settings are validated in memory (placeholders, system paths, line breaks)
before .env is written — a rejected /etc used to be saved anyway
- values containing $ are single-quoted so compose doesn't expand them
("pa$word" became "pa"); line breaks are refused (env injection)
- ThreadingHTTPServer so status polls keep answering during slow requests;
state-changing POSTs still run one at a time
- 1 MiB body cap, 30 s socket timeout, bad Content-Length → 400
- POSTs with Sec-Fetch-Site other than same-origin are refused (apps on
other ports of the same host count as same-site for SameSite=Strict)
- unexpected exceptions return a JSON 500 instead of a dropped connection
- UI pages send the browser to /login on a 401 instead of showing an empty
app list after an API restart
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wq1hzUp4ZmCKHDuLKCnu73
- drives: classify by lsblk TRAN/ROTA (SATA SSDs were labelled HDD and lost
to bigger spinning disks); drop zram, eMMC boot/rpmb areas, read-only and
0-byte disks; grey out disks under 16 GB; rank USB disks last; warn about
>2 TB disks on BIOS (MBR); boot-stick fallback via archiso kernel args
when bootmnt is gone; timeouts on lsblk/smartctl/blkid
- step 2 only accepts a disk that was actually offered
- /install/run refuses a second concurrent install and disables the button
- archinstall's exit code is tracked: OOM kills / signals now show as errors
instead of a progress bar stuck forever
- disk-probe exceptions land in the install log instead of a bare 500
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>