Commit graph

4 commits

Author SHA1 Message Date
e6f52ada5c feat(furtka): per-app image updates via POST /api/apps/<name>/update
Phase 1 of updates. User clicks Update on an installed app row →
the resource manager runs `docker compose pull`, compares the
running container's image ID to the just-pulled local image ID
per service, and only runs `docker compose up -d` if something
actually changed. Response is {updated: bool, services: [{service,
from, to, tag}]} so the UI can tell the user what happened.

Deliberately small: no pinning, no background checks, no "update
all" button, no version/changelog display. The update flow doesn't
mutate the compose file — it just acts on what's already there.
Reinstall still serves as rollback.

New dockerops helpers: compose_pull, compose_image_tags (parses
`docker compose config --format json`), local_image_id (via
`docker image inspect`), running_container_image_id (via compose
ps --quiet + docker inspect). Six new tests cover the endpoint:
not installed, no changes, changes applied, service not running,
docker pull error, and the HTTP route end-to-end.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:45:47 +02:00
e8ed224eea feat(ui): inline app icons into /api/apps JSON, render on /apps
Slice 2 of the on-box UI uplevel. The resource-manager API already
returned the icon filename in each manifest summary, but the /apps
page never rendered it — and there was no endpoint to fetch the
file either. This inlines the SVG content directly into the JSON
response (one round-trip, Doherty Threshold) and injects it into
each app card's new icon slot on the left.

_read_icon_svg defends against the obvious SVG-XSS vectors (script
tags, on* handlers, javascript: URLs) and rejects anything over
16 KB. The trust model stays what it was — bundled apps are built
into the ISO, the install API has no auth — but the filter keeps
accidents from becoming exploits if an icon gets swapped upstream.

/apps now shows a generic folder fallback for any app without a
parseable icon.svg; slice 3 ships the real fileshare artwork.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:23:41 +02:00
61c7ee232c feat(furtka): in-browser app settings + ISO recovery-path fixes
Some checks are pending
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
CI / validate-json (push) Waiting to run
CI / markdown-links (push) Waiting to run
Build ISO / build-iso (push) Successful in 16m54s
End-to-end VM test today (2026-04-15) validated the resource manager
golden path but exposed four things blocking "dein-Vater-tauglich":
no way to configure an app without SSH+editor, no openssh, no nano,
keyboard stuck on US, and a samba healthcheck that cried wolf.

Resource-manager side:
- Manifest schema gains optional `settings` list (name/label/
  description/type/required/default) and `description_long`.
- Bundled-app install opens a form rendered from the manifest;
  submit carries values to `POST /api/apps/install` which writes
  them into the new app's `.env` before the placeholder check runs.
- Installed apps grow an "Einstellungen" button that merges a
  partial settings dict into the existing `.env` (unsubmitted
  password fields = keep current), then reconciles to restart.
- New endpoints: `GET/POST /api/apps/<name>/settings`. Passwords
  are never returned to the client.
- Fileshare manifest declares its SMB_USER/SMB_PASSWORD settings
  in German with help text.

ISO side (so the next build is actually usable on the TTY):
- Add `openssh` to the package list + `sshd` to enabled services.
  `archinstall: true` in 4.x did not install openssh-server.
- Add `nano` — `vim` was the only editor pitched at users, which
  is brutal for first-timers (and was missing anyway).
- Keyboard layout follows the installer language (`de→de`, `pl→pl`,
  `en→us`) instead of hardcoded `us`. A German user couldn't type
  `/` or `-` at the console, making even `sudo nano` painful.
- Disable the dperson/samba healthcheck in the compose override —
  it timed out on every probe while the share itself worked fine.

19 new tests (manifest parsing + settings-merge + two new API
endpoints over live HTTP); 94 total, format + lint clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 13:00:02 +02:00
c6ed7a8159 feat(furtka): web UI + HTTP API for app install/remove
Some checks are pending
CI / lint (push) Waiting to run
CI / test (push) Waiting to run
CI / validate-json (push) Waiting to run
CI / markdown-links (push) Waiting to run
Build ISO / build-iso (push) Successful in 16m52s
Adds the management UI Daniel asked for end-of-session. Goes beyond
the original MVP scope (plan punted UI to v2) but the architecture
already supports it cleanly: stdlib http.server only, no new deps.

- furtka.api: minimal HTTP server. GET / serves a self-contained
  HTML page (dark-mode card list, vanilla JS, no build step). GET
  /api/apps + /api/bundled return JSON. POST /api/apps/{install,
  remove} accept {"name": "..."} and call the same installer +
  reconciler the CLI uses, so the placeholder-secret refusal and
  per-app reconcile isolation flow through unchanged.
- furtka.cli: new `furtka serve` subcommand. Imports api lazily so
  `furtka app list` / `reconcile` startup stays zero-cost.
- webinstaller: new furtka-api.service (Type=simple, restart on
  failure, after reconcile). Caddyfile gets two new handle blocks
  to reverse-proxy /api and /apps to localhost:7000. Landing page's
  "App store coming soon" tile becomes a real "Manage installed apps
  →" link to /apps.
- Bound to 127.0.0.1 by default; Caddy makes it LAN-reachable. The
  UI shouts a "no auth, anyone on your LAN can install/remove" warning
  at the top — Authentik integration is the proper fix later.

UX wrinkle worth noting: a placeholder-rejected install leaves the
app in /var/lib/furtka/apps/<name>/ (so the user can edit .env in
place). To re-trigger after editing, the Installed list now shows
both Reinstall and Remove buttons.

10 new tests: helper functions (list_installed, list_bundled with
hide-already-installed), install/remove endpoints with the no_docker
fixture, and two real-socket urllib smoke tests that boot the actual
HTTPServer on an ephemeral port and round-trip GET / + POST.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:23:46 +02:00