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>
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>