Commit graph

5 commits

Author SHA1 Message Date
577c2469f7 style(tests): reflow OPTIONAL_PATH_MANIFEST to match ruff format
All checks were successful
Build ISO / build-iso (push) Successful in 20m27s
CI / lint (push) Successful in 29s
CI / test (push) Successful in 1m3s
CI / validate-json (push) Successful in 46s
CI / markdown-links (push) Successful in 23s
Fixes the lint failure on the 26.10-alpha commit — ruff format wanted
the single-item settings list on one line rather than spread over
three. Pure formatting, no behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:56:52 +02:00
04762f5dd1 feat(manifest): add 'path' setting type with server-side validation
Some checks failed
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) Failing after 4m34s
Apps can now declare a setting with "type": "path" whose value is an
absolute host filesystem path. Compose bind-mounts it via standard .env
substitution (${MEDIA_PATH}:/media) — no reconciler changes needed.
Unlocks media/data-heavy apps (Jellyfin, later Paperless, Nextcloud,
Immich) that point at existing user data instead of copying it into a
Docker volume.

Install/update refuses values that aren't absolute, don't exist, aren't
directories, or resolve into a system-path deny-list (/, /etc, /root,
/boot, /proc, /sys, /dev, /bin, /sbin, /usr/bin, /usr/sbin,
/var/lib/furtka). Path.resolve() is applied before the deny-list check
so /mnt/../etc traversal is caught too. Error messages surface in the
existing install/edit modal.

UI: path settings render as a text input with a /mnt/… placeholder.
The manifest's `description` field carries the actual hint ("Absoluter
Pfad zu deinem Filme-Ordner, z.B. /mnt/media"). No new form
components, no new API routes.

Tests: 9 new cases for install + update path validation; 1 new case
for manifest schema accepting the path type. 211 total passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:39:15 +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
ff68dd5ae6 fix(furtka): audit follow-ups — placeholder secrets, isolate reconcile, .env perms
Addresses the four issues raised in the slice-3 audit before pushing.

#1 (critical) — refuse to finish install when .env still contains
placeholder secrets like "changeme". Without this, `furtka app install
fileshare` would happily start an SMB server with a publicly-known
password — the kind of default that ends up screenshotted on Hacker
News. PLACEHOLDER_SECRETS lives in installer.py; new tests cover
placeholder rejection, post-edit retry, and quoted values.

#3 — reconciler now catches DockerError / FileNotFoundError / OSError
per-app instead of letting a single broken app abort the whole
boot-scan. Errors get surfaced as Action(kind="error", …) and
has_errors() drives the CLI exit code so systemd still shows red,
but the other apps actually got reconciled.

#4 — chmod 0600 on .env after install so app secrets aren't world-
readable on multi-user boxes. Done before the placeholder check so
even the half-installed state is safe.

#5 — load_manifest() got an optional expected_name. The scanner
passes the folder name (filesystem source-of-truth contract);
installer leaves it None so `furtka app install /tmp/some-fork/`
works regardless of what the source folder is named.

#2 — TODO comment on dperson/samba:latest. Switching to a digest
needs a verified upstream release; left for the test-day pin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:17:00 +02:00
7b96a25f5b feat(furtka): reconciler + install/remove — slice 2
Fills in the act-on-it half of the resource manager. Reconciler walks
the scanner output and brings docker into the desired state: ensures
each manifest-declared volume exists (idempotent), then runs
docker compose up -d for the project. install/remove on the CLI work
end-to-end against a real /var/lib/furtka/apps/ tree.

- furtka.dockerops: thin subprocess wrapper. Volume + compose
  primitives that other modules call. `_run` raises DockerError with
  the actual stderr so failures are diagnosable.
- furtka.reconciler: builds an ordered Action list (volumes then
  compose_up per app), executes unless dry-run. Broken manifests
  produce a "skip" action, the rest of the apps still get reconciled.
- furtka.installer: copy-from-source with two non-obvious rules —
  user .env is preserved across upgrade installs, and a missing .env
  is bootstrapped from .env.example so compose has values to
  substitute on first install. Bundled-app lookup falls back to
  /opt/furtka/apps/<name>/ when the source arg isn't a path.
- furtka.cli: app install/remove wired up. remove() ignores compose
  down failures so a botched compose doesn't trap users with an
  un-removable folder.
- 15 new tests using monkeypatch'd dockerops so the suite still runs
  without docker installed. Covers reconcile dry-run, multi-volume
  apps, broken-manifest skip behavior, .env preservation, bundled-name
  resolution, and remove edge cases.

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