Files
mesh-catalog/modules/umami/module.json
T
jschoubben f0665ba956 ADR 0056: selectable ACME issuer, step-ca init from operator root, route labels
Piece A + B of ADR 0056, completing the internal-CA work in ff01ada.

Selectable issuer. acme-ca is now a role two providers can satisfy: step-ca
(internal CA) or the new public-acme (a fact-only module, no container/listen)
that serves Let's Encrypt production. A mesh assigns one or the other to satisfy
route-proxy's `requires: acme-ca`.

One directory shape for both. A provider serves the ACME directory's parts the
way the mesh already models any reachable service -- an address (`at`), a `port`
and a `path` -- and route-proxy composes `https://<at>:<port><path>`. step-ca
lets the mesh fill `at` (its node) and `port` (its single listen) and serves only
`path`; public-acme, not being a mesh service, serves all three (overriding `at`
with the public host). Same composition either way.

Empty root means the system trust store. Both providers serve `root`: step-ca
the operator root PEM (settled per mesh), public-acme an empty string. route-proxy
writes it to the CA bundle file unconditionally; the binary now reads an empty
bundle as "the root is already trusted by the OS" and falls back to system roots
(examples/route-proxy/main.go, committed on the mesh-control ADR-0056 branch).

step-ca inits from the operator's root. The operator's root cert, root key and
root-key password are mounted at the smallstep entrypoint's default init paths
(/run/secrets/root_ca.crt, root_ca_key, root_ca_key_password) with the matching
DOCKER_STEPCA_INIT_*_FILE vars, so `step ca init` adopts the operator's root
instead of self-generating one -- the CA that signs is the CA route-proxy trusts.

Route names are labels, not FQDNs. Every routed module now contributes a `label`
(the leftmost subdomain) instead of a full public hostname; the node's public
domain composes the name. Apex (novox.be) is left as a full name -- composeName
has no empty-label/apex convention yet (mesh-control follow-up).

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
2026-09-10 00:21:42 +02:00

128 lines
3.3 KiB
JSON

{
"module": "umami",
"version": "1",
"capabilities": [
"container-runtime"
],
"requires": [
"postgres-database",
"route"
],
"contributes": {
"postgres-database": {
"name": "umami"
},
"route": {
"label": "umami",
"port": 3000
}
},
"binds": {
"postgres-database": "/var/lib/umami/database.json",
"route": "/var/lib/umami/route.json"
},
"secrets": {
"postgres-database": "/var/lib/umami/database.secret"
},
"provides": [
{
"name": "analytics",
"scope": "mesh"
}
],
"serves": {
"analytics": {}
},
"receives": {
"analytics": "/var/lib/umami/grants/mesh.json"
},
"grants": {
"analytics": "/var/lib/umami/grants"
},
"own-secrets": {
"app-secret": "/var/lib/umami/app.secret",
"admin": "/var/lib/umami/admin.secret",
"broker": "/var/lib/mesh/umami/broker"
},
"listens": [
{
"port": 3000,
"protocol": "tcp",
"from": "anywhere",
"why": "one port serves two surfaces: the dashboard (the proxy gates it to the mesh) and the public collection endpoint that the browsers of every tracked site POST to \u2014 so the port itself must be reachable from anywhere"
}
],
"resources": [
{
"id": "mesh-state",
"type": "directory",
"path": "/var/lib/mesh/umami",
"mode": "0700"
},
{
"id": "state",
"type": "directory",
"path": "/var/lib/umami",
"mode": "0700"
},
{
"id": "grants",
"type": "directory",
"path": "/var/lib/umami/grants",
"mode": "0700"
},
{
"id": "server-env",
"type": "file",
"path": "/var/lib/umami/server.env",
"mode": "0600",
"content": "DATABASE_URL=postgresql://${bound:postgres-database:as}:${secret:postgres-database}@${bound:postgres-database:at}:${bound:postgres-database:port}/${bound:postgres-database:as}\nDATABASE_TYPE=postgresql\nAPP_SECRET=${secret:app-secret}\n"
},
{
"id": "provisioner-env",
"type": "file",
"path": "/var/lib/umami/provisioner.env",
"mode": "0600",
"content": "MESH_PROVISION_UMAMI_URL=http://umami:3000\nGRANTS=/var/lib/umami/grants\n"
},
{
"id": "net",
"type": "network",
"name": "umami"
},
{
"id": "server",
"type": "container",
"name": "umami",
"image": "ghcr.io/umami-software/umami@sha256:fa32d116cf20cad52cbc3fad9a63b46e7fa02299d8f967168eb453d49c476b4a",
"network": "umami",
"env-file": [
"/var/lib/umami/server.env"
],
"ports": [
"3000"
]
},
{
"id": "runtime",
"type": "container",
"name": "mesh-umami",
"image": "mesh-runtime-umami@sha256:0000000000000000000000000000000000000000000000000000000000000000",
"network": "umami",
"volumes": [
"/var/lib/mesh/umami/broker:/run/secrets/broker:ro",
"/var/lib/umami/grants:/var/lib/umami/grants",
"/var/lib/umami/admin.secret:/run/secrets/admin:ro"
],
"env": {
"MESH_BROKER_FILE": "/run/secrets/broker",
"MESH_RECEIVES": "/var/lib/umami/grants/mesh.json",
"MESH_UMAMI_ADMIN_PASSWORD_FILE": "/run/secrets/admin"
},
"env-file": [
"/var/lib/umami/provisioner.env"
]
}
]
}