diff --git a/docs/app-developer-guide.md b/docs/app-developer-guide.md index d0dce5b7..c0c21bc6 100644 --- a/docs/app-developer-guide.md +++ b/docs/app-developer-guide.md @@ -109,7 +109,7 @@ app: | `app.container.pull_policy` | Pull behavior, usually `if-not-present` | | `app.container.network` | Podman network setting such as `archy-net` or `pasta`; dangerous namespace-sharing modes are rejected | | `app.container.entrypoint` / `custom_args` | Entrypoint and command override | -| `app.container.derived_env` | Environment values rendered from host facts. The complete placeholder set is `{{HOST_IP}}`, `{{HOST_MDNS}}`, `{{DISK_GB}}`, `{{BITCOIN_HOST}}` — anything else is left in the value verbatim rather than erroring, so a typo ships a literal `{{FOO}}` to your container | +| `app.container.derived_env` | Environment values rendered from host facts. The complete placeholder set is `{{HOST_IP}}`, `{{HOST_MDNS}}`, `{{DISK_GB}}`, `{{BITCOIN_HOST}}`; an unknown name or an unbalanced `{{` is a parse error, so typos fail loudly | | `app.container.secret_env` | Environment values read from `/var/lib/archipelago/secrets/`, injected as podman secrets (never visible in `podman inspect` or unit files) | | `app.container.generated_secrets` | Secrets the orchestrator creates on first use (`hex16`/`hex32`/`base64`/`bcrypt`) — self-healing, 0600, no host provisioning | | `app.container.generated_certs` | Self-signed TLS certs materialised before create; CN/SANs rendered from host facts | diff --git a/docs/app-manifest-spec.md b/docs/app-manifest-spec.md index 22d4dcc5..ecd29f05 100644 --- a/docs/app-manifest-spec.md +++ b/docs/app-manifest-spec.md @@ -6,14 +6,22 @@ document and the code disagree, the code wins. See [`app-developer-guide.md`](app-developer-guide.md) for the authoring workflow._ Every app is a directory `apps//` containing a `manifest.yml` with a single -top-level `app:` block. Apps are purely declarative — the orchestrator owns the -entire lifecycle; there is no per-app installer code. +top-level `app:` block. Apps are declarative — the orchestrator owns the entire +lifecycle; there is no per-app installer code. + +One honest caveat: seven first-party apps still get Rust-side pre-start work +through a hardcoded `match app_id` in `ProdOrchestrator::run_pre_start_hooks` — +`bitcoin-ui`, `filebrowser`, `lnd`, `archy-nbxplorer`, `btcpay-server`, +`fedimint-clientd` and `grafana` render or repair a config before start. That is +orchestrator code rather than a per-app installer, but it is not +manifest-declared, and the direction of travel is to replace each case with a +reusable manifest primitive. ## Top-level fields (`app:`) | Field | Type | Required | Notes | |-------|------|----------|-------| -| `id` | string | ✅ | Lowercase alphanumeric + `-`/`_`. Must match the directory name. | +| `id` | string | ✅ | Lowercase ASCII letters, digits and single hyphens only — **no underscores**, no leading/trailing `-`, no `--` (`is_valid_app_id`). Should match the directory name, though nothing enforces that: the loader keys off `app.id`, so a mismatch silently registers the app under the id in the file rather than the folder. | | `name` | string | ✅ | Display name. | | `version` | string | ✅ | App version shown in the UI. | | `description` | string | — | One-line description. | @@ -45,7 +53,7 @@ Exactly **one** of `image` or `build` must be present (image XOR build). | `network_aliases` | list of string | Extra DNS names on `network` (podman `--network-alias`) — lets stack members answer to short baked-in hostnames (`api`, `minio`, `relay`). | | `entrypoint` | list of string | Entrypoint override. | | `custom_args` | list of string | Extra positional args appended after the image. | -| `derived_env` | list | `- { key, template }` — template rendered against host facts at apply time. Allowed placeholders: `{{HOST_IP}}`, `{{HOST_MDNS}}`, `{{DISK_GB}}` (plus dependency-resolved facts such as the active bitcoin host). Never hard-code host specifics. | +| `derived_env` | list | `- { key, template }` — template rendered against host facts at apply time. The allow-list is exactly `{{HOST_IP}}`, `{{HOST_MDNS}}`, `{{DISK_GB}}`, `{{BITCOIN_HOST}}` (`DERIVED_PLACEHOLDERS`); an unknown name or unbalanced `{{` fails validation. `{{BITCOIN_HOST}}` resolves to whichever Bitcoin app is running (`bitcoin-knots` or `bitcoin-core`, defaulting to knots). Never hard-code host specifics. | | `secret_env` | list | `- { key, secret_file }` — value read from `/var/lib/archipelago/secrets/` and injected as a **podman secret**, so it never appears in `podman inspect` or unit files. `secret_file` must be a bare filename (no `/`, no `..`). | | `generated_secrets` | list | `- { name, kind }` — orchestrator materialises the secret on first use (0600, rootless service user, idempotent + self-healing). `kind ∈ hex16 | hex32 | base64 | bcrypt` (bcrypt writes `` = hash and `.pw` = plaintext). | | `generated_certs` | list | `- { crt, key, common_name?, sans? }` — self-signed TLS materialised before create; CN/SANs rendered against host facts. | @@ -104,11 +112,18 @@ hooks: ## Installation semantics -The orchestrator compiles the manifest into a rootless Podman **Quadlet unit -under `user.slice`** — the container survives backend restarts and reboots, and -a level-triggered reconciler converges drift every 30 seconds. Multi-container -apps are sets of per-member manifests installed together via the stack -orchestrator (`api/rpc/package/stacks.rs`) on an app-local network. +The orchestrator compiles the manifest into a rootless Podman container that +survives backend restarts and reboots, and a level-triggered reconciler +converges drift every 30 seconds (`BootReconciler::DEFAULT_INTERVAL`). +Multi-container apps are sets of per-member manifests installed together via +the stack orchestrator (`api/rpc/package/stacks.rs`) on an app-local network. + +**Quadlet is not the default path.** `config.use_quadlet_backends` defaults to +`false`, so ordinary apps still take the legacy `podman create + start` path; +the Quadlet-unit-under-`user.slice` backend is opt-in per node (config key or +`ARCHIPELAGO_USE_QUADLET_BACKENDS`) and stays behind the flag until the +lifecycle harness has gone green against it. Companion UI containers are the +exception that do use Quadlet today. ## Distribution