app store: compose templates and their setup scripts, with two proven end to end
Each provisionable service gets a directory holding a compose template and a setup.sh. Deliberately the shape a sidecar needs once it lives in its own repository: metadata, compose, setup script, schema. The contract (templates/README.md): answers come from the ENVIRONMENT, so the web form fills them in and a person on a VPS is prompted only for what is missing, and only on a TTY — one script for both, not two code paths. Idempotent, writes only inside its own directory, streams progress on stdout (the installer pipes it to a terminal panel), and returns results as OFFICER_RESULT_<KEY>= lines so nothing has to scrape a log. House conventions throughout: relative bind mounts so data sits beside the compose file rather than hiding behind `docker volume inspect`, containers running as the installing user so downloads are not root-owned, loopback-only ports unless the service's whole job is inbound connections, and no external networks — the owner's own composes attach to an `nginx` network that a fresh VPS does not have. Transmission verified end to end on this machine, on non-conflicting ports, then torn down: renders, starts, waits, reports. Its health check accepts 409 because Transmission rejects the first request by design — only-200 would have waited out the full timeout against a working daemon. Re-run produced exactly one container, and files landed owned by the user rather than root. Vaultwarden covers the case where we GENERATE the credential rather than asking for one. An existing token is reused, never rotated, because rotating during a resumed install would lock the owner out of the admin page. The Argon2 hash has its `$` doubled or compose interpolation mangles it. The token is not returned to the platform at all — the vault sidecar proxies the Bitwarden protocol and never needs it, and a secret we do not hold is one we cannot leak. Corrects the design doc, which assumed provisioning always knows the connection. Three shapes: we set the credential, we generate it, or a human must mint it in the service's UI afterwards (Immich, Jellyfin, Memos). The third makes "provisioned and running but not yet connected" a real state rather than a failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -227,6 +227,25 @@ What a plugin author is promised, and bound by. To be written properly; the shap
|
||||
|
||||
---
|
||||
|
||||
## Provisioning has three shapes, not one
|
||||
|
||||
This document originally said provisioning "writes the connection we already know". That is only true
|
||||
some of the time, and the difference decides whether an install can finish unattended:
|
||||
|
||||
1. **We set the credentials.** Passed as container environment, so the connection is known the moment it
|
||||
is up. Transmission (`USER`/`PASS`), Vaultwarden (`ADMIN_TOKEN`).
|
||||
2. **We generate a secret into a file.** The bind mount lets us write it before first boot, so it is
|
||||
still known without asking. slskd's API key lives in its `slskd.yml`.
|
||||
3. **A human must mint a token in the service's own UI after it boots.** Immich, Jellyfin and Memos all
|
||||
work this way — no environment variable pre-seeds an API key.
|
||||
|
||||
Shape 3 means an install can be **provisioned and running but not yet connected**. That is a real state,
|
||||
not a failure: the container is up, the compose file is written, and we are waiting for a token. The
|
||||
step machine stops there, and the UI asks for the key with a link to the page that mints it. Resuming
|
||||
finishes the job — which is what `completedSteps` was for.
|
||||
|
||||
---
|
||||
|
||||
## What Phase 0 must not foreclose
|
||||
|
||||
Three things are coming, and each one constrains a decision that looks free today.
|
||||
|
||||
Reference in New Issue
Block a user