Slice 1 of the Resource Manager (see docs/resource-manager.md + plan in ~/.claude/plans/stateful-juggling-pike.md). Lays down the read-only half: a JSON manifest schema with namespacing, a scanner that walks /var/lib/furtka/apps/, and a `furtka` CLI with `app list` and `reconcile --dry-run`. Reconciler / volume creation / docker compose calls land in the next slice. - furtka.manifest: dataclass + load_manifest with required-field + type validation. volume_name() injects the furtka_<app>_<vol> namespace so apps can each declare a "data" volume without colliding. - furtka.scanner: tolerant — broken manifest = ScanResult with error, not an exception. Lets reconcile log + skip rather than abort. - furtka.cli: text + --json output. argparse with `app list` and `reconcile --dry-run`. main() returns int for clean exit codes. - furtka.paths: FURTKA_APPS_DIR env override so tests don't need root. - 19 new tests covering valid manifests, every validation branch, scanner edge cases (missing root, broken manifest, sort order), and the CLI subcommands. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
126 lines
5.4 KiB
Markdown
126 lines
5.4 KiB
Markdown
# Resource Manager — Design Notes
|
||
|
||
Scaffold for the conversation between Daniel and Robert. Captures what Robert already sketched in chat on 2026-04-15 and lists the open questions that need an answer before we write code.
|
||
|
||
**Status:** Draft 2026-04-15. Nothing implemented. First app to consume this will be a LAN fileshare (SMB/NFS) — that's the forcing function.
|
||
|
||
---
|
||
|
||
## What's the Resource Manager?
|
||
|
||
The layer between Furtka apps and the underlying system (disk, Docker, network, users). Apps don't touch Docker or the filesystem directly — they declare what they need, the Resource Manager provisions and tracks it.
|
||
|
||
Robert's framing (2026-04-15): *"żeby później można było manipulować wszystkimi peryferiami"* — so we can manipulate all peripherals from one place later.
|
||
|
||
---
|
||
|
||
## Decided (Robert, 2026-04-15)
|
||
|
||
1. **An app is a folder.** A Furtka app is defined as a directory containing:
|
||
- a manifest
|
||
- a `docker-compose.yaml`
|
||
- a `.env` file
|
||
2. **A registration script** reads that folder and wires the app into the backend.
|
||
3. **Each app gets its own named Docker volume.** Sharing between apps is possible but opt-in, not default.
|
||
4. **Filesystem is the source of truth (for now).** No SQL database yet — the Resource Manager reads current state from Docker and the OS ad-hoc. DB gets added when we actually need to store intent the OS doesn't know (e.g. "this share belongs to App X, readable by User Y").
|
||
|
||
These are locked; don't re-litigate without Robert.
|
||
|
||
---
|
||
|
||
## Open questions
|
||
|
||
These need an answer before the first line of code.
|
||
|
||
### Q1 — What's in the manifest?
|
||
|
||
This is the contract between app authors and Furtka. Changing it later is expensive. Known candidates:
|
||
|
||
- `name` (machine id, must match folder name?)
|
||
- `display_name` (shown in UI)
|
||
- `version` (semver? calver?)
|
||
- `description`
|
||
- `volumes` (list of named volumes the app needs)
|
||
- `ports` (which ports the app wants exposed; needed for reverse proxy later)
|
||
- `icon` (path or URL)
|
||
- ... anything else?
|
||
|
||
**Format:** YAML / TOML / JSON — not decided.
|
||
|
||
### Q2 — Where do apps live on disk?
|
||
|
||
Suggested-but-not-decided: `/var/lib/furtka/apps/<app-name>/`. Robert: confirm path?
|
||
|
||
### Q3 — What does the registration script actually do?
|
||
|
||
Robert said "Skript do rejestrowania w backendzie". Concretely that means some subset of:
|
||
|
||
- Validate manifest (schema check, required fields, volume names unique)
|
||
- Create any declared named volumes that don't exist yet
|
||
- Run `docker compose up -d` inside the app folder
|
||
- Record the app in whatever passes for the backend index (right now: nothing — we'd just re-scan the folder each time)
|
||
|
||
**Sub-question:** is registration a one-shot on install, or does something (systemd unit?) re-scan `/var/lib/furtka/apps/` on every boot so the filesystem really is authoritative?
|
||
|
||
### Q4 — How does the user actually install an app?
|
||
|
||
Out of scope for the first cut, but needed to know the shape of the CLI:
|
||
|
||
- Admin UI with a button "Install Fileshare"?
|
||
- CLI: `furtka app install <tarball>` / `<git repo>` / `<name-from-catalog>`?
|
||
- Catalog = a separate repo with app folders?
|
||
|
||
### Q5 — Upgrades and removal
|
||
|
||
- Upgrade strategy: replace folder + re-run compose? Preserve volumes across upgrades (yes obviously, but needs stating)?
|
||
- Removal: does it delete the volume or keep it? Robert's "apki się dzielą" implies a volume can outlive its original app.
|
||
|
||
### Q6 — Volume sharing semantics
|
||
|
||
Robert: "jak będzie trzeba to będą mogły się dzielić". Open:
|
||
- Who requests the share — the sharing app's manifest, or an admin action?
|
||
- What's the permission model (read-only, read-write, per-user)?
|
||
- This is probably the *first* piece of "intent not reflected in OS state" — might be what finally motivates the DB.
|
||
|
||
### Q7 — Backend API surface
|
||
|
||
Once the filesystem model is clear, the Resource Manager's backend still needs *some* Python/whatever surface that the web UI and the register script both call. Rough shape:
|
||
|
||
- `list_apps()` — scan `/var/lib/furtka/apps/`, return manifests + running status
|
||
- `install_app(folder)` — validate + compose up
|
||
- `remove_app(name)` — compose down, optionally rm volume
|
||
- `list_volumes()` — from Docker
|
||
- `list_shares()` — ???
|
||
|
||
Not decided whether this lives in the existing `webinstaller` Flask app, a new backend service, or a thin CLI that the Flask app shells out to.
|
||
|
||
---
|
||
|
||
## First consumer — Fileshare
|
||
|
||
The point of doing this now is to unblock the fileshare app (see `../memory/project_first_app_fileshare.md` for the conversation context). Walking it through the model above:
|
||
|
||
```
|
||
/var/lib/furtka/apps/fileshare/
|
||
manifest.??? # see Q1 — what exactly goes here?
|
||
docker-compose.yaml
|
||
.env
|
||
```
|
||
|
||
- App declares a volume (e.g. `files`).
|
||
- Compose runs a Samba/NFS container mounting that volume.
|
||
- On Mac/Windows/Android you mount `smb://furtka.local/files`.
|
||
|
||
If we can get *this* end-to-end through the Resource Manager, we've validated the whole model with the simplest possible app. Nextcloud, Jellyfin, etc. are the same shape with more knobs.
|
||
|
||
---
|
||
|
||
## What we do NOT do yet
|
||
|
||
- No database. (Decided.)
|
||
- No user/permissions model beyond what Samba/OS already give us.
|
||
- No reverse proxy / TLS integration in the manifest. (Tracked separately — see `../memory/project_ssl_local_deferred.md`.)
|
||
- No app catalog / store UI. Manual install first.
|
||
- No auto-updates.
|
||
|
||
These all become easier once Q1–Q7 are answered — adding them to an existing model is straightforward; guessing them now is expensive.
|