Add a `route` contribution (requires/contributes/binds) to every web app so
each gets a Host-routed public name via route-proxy, mirroring the
de-spiegel/only-office pattern:
- novox: gitea, keycloak, nextcloud, umami, invoicing, verdaccio, registry,
and mailu (single mail.novox.be -> 7080; admin/webmail/api ride that port).
- ace: grafana, sonarr, radarr, lidarr, bazarr, ombi, tautulli, jackett,
nodered, searxng, home-assistant, bookshelf, baserow.
Split photos so its three sites each get a name: photos keeps server +
admin-client (photos.novox.be), and new photos-eef (eef.novox.be) and
photos-filip (filip.novox.be) modules carry the client sites.
The production FQDN stays the literal default; a per-node .incus name is a
settings override applied where the mesh runs, not a manifest hardcoding.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
The postgres provisioner creates each consumer a database named after the login the mesh
minted (mesh_<node>_<module>), per ADR 0048 -- 'a same-named database under exactly that
login'. But baserow, letta, umami, gitea, keycloak and nextcloud each hardcoded their app db
name (DATABASE_NAME=baserow, /letta, /umami, NAME=gitea, /keycloak, POSTGRES_DB=nextcloud),
so the service connected to a database that does not exist ('database letta does not exist').
Each now uses ${bound:postgres-database:as} as the db name -- the login, which is also the db
name -- matching the working meshboard pattern and the provisioner's actual behaviour.
Found by the two-node DB-consumer lab install (mesh-lab assigned-two-node-db); baserow is
proven connecting and running there. The S3 bucket name is the same class of assumption and is
a separate follow-up (s3 identity has its own length bound).
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
The breadth install (catalogue-broad) surfaced two latent resolution bugs that
single-module compile checks never caught (compile != resolve):
1. postgres/redis/mongodb/minio served no "port", yet seven consumers
(baserow, letta, invoicing, gitea, umami, keycloak, nextcloud) reference
${bound:<provision>:port}. A binding auto-carries at/from/as; the port is the
provider's half of the answer and must be declared in `serves` (manifest.go:
"Serves is what a consumer needs to know ... a port, a path, a realm"). Added
port to each: postgres 5432, redis 6379, mongodb 27017, minio 9000. Without it
no DB consumer could resolve, let alone deploy.
2. baserow declared an empty contribution `contributes: {"redis-cache": {}}`.
redis's serves spec for the cache is empty (a cache takes no per-consumer
payload), so a consumer only `requires` it; an empty contribution is refused.
Dropped it (requires/binds/secrets unchanged).
Found by mesh-lab catalogue-broad; the same fixes are mirrored in that bed.
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
Mirror the postgres-consumer shape (keycloak/nextcloud): requires the
backing service(s), binds/secrets for the delivered credential, and a
server container that reads it from an interpolated env file.
- baserow: consumes postgres-database + redis-cache; tooled (dormant
until an account is configured, like gitea's token).
- letta: consumes postgres-database; tooled, live via a mesh-minted
server-password injected into both server and runtime.
- invoicing: consumes mongodb-database + s3-bucket; plain two-container
service (app + api), no tools.
Digests pinned for baserow and letta; invoicing keeps private-registry
tags (DIGEST-UNRESOLVED). redis-cache and mongodb-database consumer
shapes are inferred (no prior consumer in the catalog).
Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF