Commit Graph
351 Commits
Author SHA1 Message Date
jschoubben 511200ed9c Merge pull request 'invoicing: the photos lessons, applied before its window' (#86) from fix/invoicing-learns-the-photos-lessons into main 2026-09-26 01:15:38 +00:00
jschoubben b704bf5ad8 invoicing: the photos lessons, applied before its window
The mongo credential authenticates against its own database and the
database is the granted one (mesh_novox_invoice), not the contributed
name the provisioner ignores. Same for the store: the key is sealed to
the derived bucket (mesh-novox-invoice) — the data mirrors in during the
window, the ncloud/photos pattern. And the api gets the route
contribution it always needed: invoicing-api.novox.be is today a traefik
container label, invisible to every file survey, and it must be a grant
before the edge can ever flip.
2026-09-26 03:15:26 +02:00
jschoubben 142d65c52a Merge pull request 'mongodb: the server container is mongodb-server, not the predecessor's name' (#85) from fix/mongodb-coexists-with-the-predecessor into main 2026-09-26 00:37:51 +00:00
jschoubben ccb6e7500e mongodb: the server container is mongodb-server, not the predecessor's name
The adopted node still runs the predecessor's mongo container, and it must
keep running: invoicing points at novox.be:27017 and is not migrating in
this window. A module container named 'mongo' would be held at assign and
would replace the predecessor at take, cutting invoicing off its database.
The mesh's server coexists instead — fresh data directory, its own name,
auto-allocated machine port — and the predecessor retires with its last
consumer.
2026-09-26 01:37:41 +02:00
jschoubben bbda88c13b Merge pull request 'Every credential provider says whether it still holds a consumer (hq issue 120)' (#84) from fix/120-redis-says-what-it-holds into main 2026-09-25 23:31:28 +00:00
jochen 620b47d309 mssql remaps a user only when orphaned; mailu's operator tool no longer re-enables
ALTER USER ... WITH LOGIN runs only when the user's SID is not the
login's, so an already-mapped user is left alone. The provisioner
enables a mailbox through its own method; the password tool an
operator uses keeps changing the password only.
2026-09-26 01:30:15 +02:00
jochen 6fd93afc6c Review fixes: holds and create agree, and no password leaves a check
create re-enables what holds refuses (mssql login, mosquitto client,
mailu mailbox, gitea user) and clears an expired postgres password, so
no disabled account loops. mssql and mongodb checks take the password
from the environment, never argv; mosquitto_ctrl failures no longer
repeat -P. mosquitto reads 'could not ask' as an error, not absence.
mailu checks existence and enabled only: its imap passdb cannot verify
a password. mssql checks the user's SID; gitea pages teams at 50.
2026-09-26 01:24:32 +02:00
jochen 0cb0f814b4 Every credential provider says whether it still holds a consumer
holds() for postgres, mssql, mongodb, minio, lavinmq, mosquitto, mailu
and gitea, so the harness makes again a login the backend lost (hq issue
120). Each checks the mesh's password as the consumer presents it, or
compares it read-only, and returns false only when the backend says the
credential is absent or wrong; an unreachable backend throws.
2026-09-26 01:09:52 +02:00
jochen 3d271f72ea redis: say whether it still holds a consumer's ACL user
The server keeps ACL users in memory only, so a restart forgets every
consumer while the provisioner keeps running (hq issue 120). holds()
checks ACL GETUSER for the user, enabled, with the mesh's password, so
the harness makes a forgotten user again. Needs mesh-sdk 0.1.1.
2026-09-26 00:53:13 +02:00
jschoubben 76ca479f37 Merge pull request 'mailu: the queue is root's and traversable, which is postfix's own convention' (#83) from fix/the-queue-is-traversable into main 2026-09-25 22:17:54 +00:00
jschoubben a055334c9b mailu: the queue is root's and traversable, which is postfix's own convention
The declared 0700 was applied at take and broke mail quietly: postfix's
master runs as root but pickup and smtpd drop to uid postfix, and a
spool root they cannot traverse is a maildrop they cannot scan and a
rewrite socket they cannot open — auth succeeded and MAIL FROM hung.
0755 root is exactly what postfix's own set-permissions makes of
/var/spool/postfix. Fixed live by chmod first; declared here so the
next push stops undoing it.
2026-09-26 00:17:41 +02:00
jschoubben d5b169b6b6 Merge pull request 'mailu: cert flavor while the predecessor's proxy owns the challenge path' (#82) from fix/mailu-tls-cert-behind-the-predecessors-proxy into main 2026-09-25 22:11:26 +00:00
jschoubben 8ecc5a7249 mailu: cert flavor while the predecessor's proxy owns the challenge path
letsencrypt was the aspiration and cannot work yet, proven live: the
predecessor's own ACME machinery owns /.well-known/acme-challenge on
port 80 outright (unknown tokens get its 404) and its entrypoint
redirect owns every other path — the hand-authored passthrough never
matched anything, which is why mailu's certbot state had quietly
expired in April while the copied files carried the name. cert flavor
serves those files (valid to Nov 27). Mailu certifying itself becomes
possible the day route-proxy takes port 80, whose handler falls through
unknown tokens by design — that flip is one line here, made then.
2026-09-26 00:11:11 +02:00
jschoubben 29f00f33a5 Merge pull request 'automx: the seed writes the schema 2021.6 reads' (#81) from fix/automx-seed-knows-prio into main 2026-09-25 22:06:36 +00:00
jschoubben 480627fdd9 automx: the seed writes the schema 2021.6 reads
The hand-written seed predates automx2's prio column, so every
config-v1.1.xml request 500'd against a table the seed had just made —
and the predecessor's own database had the same gap: client
autoconfiguration has been silently broken on the old stack for a long
time, behind a root page that answered 200. The live database gained
the column by ALTER; a fresh mesh now seeds it right.
2026-09-26 00:06:24 +02:00
jschoubben c8c939a595 Merge pull request 'automx: the schema its seed writes belongs to one automx2, so that one is named' (#80) from fix/automx-pins-the-schema-its-seed-writes into main 2026-09-25 21:59:54 +00:00
jschoubben 30b7ce429c automx: the schema its seed writes belongs to one automx2, so that one is named
An unpinned pip install took the latest automx2, whose schema grew a
column (server.prio) the module's own seeding SQL predates — a 500 on
every autoconfig request against a table the seed had just written.
2021.6 is what the proven image runs; the seed and the software agree
again. Regenerating the seed for a newer automx2 is its own change,
made deliberately, not by whatever pip resolved this week.
2026-09-25 23:59:42 +02:00
jschoubben b8a50ee7a0 Merge pull request 'mailu: the containers are named by the vocabulary 2024.06 reads' (#79) from fix/mailu-speaks-2024-06-addresses into main 2026-09-25 21:56:40 +00:00
jschoubben 4e37b3e84d mailu: the containers are named by the vocabulary 2024.06 reads
The front resolves its upstreams from *_ADDRESS, defaulting to the bare
compose service names — admin, antispam — and reads the 1.9-era HOST_*
not at all. Phase 1 never noticed because the predecessor's service
names WERE the defaults; the mesh's containers are mailu-*, and the
front answered 502 asking docker for a name nothing carries. The dead
vocabulary goes; every upstream is named as the container actually is.
2026-09-25 23:56:28 +02:00
jschoubben 7cb7659fbe Merge pull request 'mailu: every container asks the module's own resolver, pinned where they can find it' (#78) from feat/mailu-points-at-its-own-resolver into main 2026-09-25 21:49:30 +00:00
jschoubben 047f228fe3 mailu: every container asks the module's own resolver, pinned where they can find it
2024.06's admin refuses to serve behind a resolver that does not
validate DNSSEC — found live as an unhealthy admin, 454s on submission
and a 500 webmail, with the runtime's forwarder validating nothing. The
unbound this module always shipped becomes reachable: pinned at the
predecessor's own address on the module network (the subnet the mesh
adopted), and named as dns by the nine containers that resolve anything.
Stands on mesh-host #26, which gave the vocabulary these two fields.
2026-09-25 23:49:17 +02:00
jschoubben 40aa93dc2b Merge pull request 'mailu: the admin API answers on 8080 since 2024.06' (#77) from fix/mailu-admin-answers-on-8080 into main 2026-09-25 21:45:11 +00:00
jschoubben 5462317183 mailu: the admin API answers on 8080 since 2024.06
1.9's admin served on 80; 2024.06's gunicorn listens on 8080, and the
runtime's fetch failed against the old port the moment the broker
credential let it try.
2026-09-25 23:45:00 +02:00
jschoubben cb9d43b2a1 Merge pull request 'automx: the launcher's wrapper is written by the build that ships it' (#76) from fix/automx-carries-its-own-wrapper into main 2026-09-25 21:44:46 +00:00
jschoubben aa8c4253f1 automx: the launcher's wrapper is written by the build that ships it
The predecessor's image carried .venv/scripts/flask.sh, created by a
build step that never made it into the files this module holds — the
image worked and its recipe could not reproduce it, caught the moment
the mesh built it from source (exit 127 crash loop at cutover). The
wrapper is now written explicitly, verbatim from the proven image, so
the recipe is the whole truth about the image.
2026-09-25 23:44:34 +02:00
jschoubben 4863eef586 Merge pull request 'mailu: pin exactly what phase 1 verified' (#75) from fix/mailu-pins-what-phase-one-verified into main 2026-09-25 21:34:26 +00:00
jschoubben 75f993e92b mailu: pin exactly what phase 1 verified
Phase 1 (MAILU-CUTOVER.md) upgraded the live stack 1.9→2024.06 and its
lessons land here as pins: every image is the digest running and
verified tonight — webmail under the name 2024.06 actually uses (the
draft's roundcube pin misled a whole hop), antivirus on the upstream
clamav image with the signature DB in its own directory (the mailu-built
image ended at 2.0), and the front trusting the proxy address the live
config actually names. The welcome-mail texts ride along for parity.

TLS_FLAVOR stays letsencrypt deliberately where the live .env says cert:
the cert files are copies whose HAL-era renewal hook died with HAL
(expiry Nov 27); mailu managing its own issuance through the existing
ACME passthrough is the fix, and the files remain on disk as the
fallback flavor if first issuance misbehaves during the window.
2026-09-25 23:34:13 +02:00
jschoubben 621246996d Merge pull request 'mailu: 2024.06 closes 110/143/587 by default; parity says open them' (#74) from fix/mailu-ports-parity into main 2026-09-25 20:56:39 +00:00
jschoubben e3d8bd0726 mailu: 2024.06 closes 110/143/587 by default; parity says open them
PORTS defaults to 25,80,443,465,993,995,4190 in 2024.06 — submission on
587 among the closed, which is what every client of this server uses.
The same parity decision the listens already state, now stated where the
software reads it.
2026-09-25 22:56:27 +02:00
jschoubben a708666bad Merge pull request 'mailu: the manifest matches the machine, provides smtp, and carries automx' (#73) from feat/mailu-becomes-real into main 2026-09-25 20:40:08 +00:00
jschoubben ed5d1386ce mailu: the manifest matches the machine, provides smtp, and carries automx
Five gaps between the draft and what actually runs, each verified live
before being written down:

- front published bare 80 — the machine port Traefik holds; now the
  predecessor's own mappings (7080:80, 7443:443) plus the 110/143/995
  parity ports the draft dropped. Pruning legacy protocols is its own
  deliberate change, not a cutover side effect.
- TLS_FLAVOR said cert, which nothing supplies; live is letsencrypt —
  mailu runs its own certbot, state already on disk, HTTP-01 answered
  through a path-scoped route contribution (priority above the web one).
- the web route said http:7080, the redirect-loop shape; it now says
  what the hand-authored file always knew: https 7443, insecure.
- automx was absent entirely: the autoconfig responder is now a second
  artifact (its Containerfile moved in from the predecessor's images
  dir, base declared per ADR 0097), a container on a real data dir —
  the anonymous-volume loss of 2026-08-10 stays fixed — and the three
  public names are route contributions.
- and the reason this moved ahead of de-spiegel: mailu now provides
  smtp. A consumer contributes the account it sends as; the provisioner
  creates <account>@<domain> via the admin API and applies the minted
  password every reconcile (ADR 0048). The domain is served on the
  binding so a consumer composes its own login from mesh facts.

route-adapter learns to say no: a contribution over https, scoped to a
path, or carrying a policy is skipped aloud rather than written into a
file shape that cannot say it — plain http into a TLS listener was the
concrete wrong file this prevents. The hand-authored files keep covering
those routes until the mesh's own proxy takes over, exactly as today.
2026-09-25 22:39:29 +02:00
jschoubben 3875987656 Merge pull request 'gitea: the package team may read code, and its units are reconciled' (#72) from fix/gitea-package-team-reads-code into main 2026-09-25 19:59:25 +00:00
jschoubben 311f7f1fdb gitea: the package team may read code, and its units are reconciled
The builder's first credentialed clone of a private repository answered
'not found': the packages team named only repo.packages in its
units_map, which is exhaustive — so members had no code unit at all, and
gitea hides what a user cannot read. One credential answering npm and
git alike was the whole design of the builder's grant; the team now says
so.

And found teams are patched, not just returned: a team is configuration
the reconcile loop owns, the same as a user's password, so a unit this
code gains reaches the team that already exists rather than only the
next mesh raised from scratch.
2026-09-25 21:59:09 +02:00
jschoubben bbd1723612 Merge pull request 'route-proxy binds the mesh's own authority for internal names; gitea's internal-API refusal joins its route' (#70) from feat/route-proxy-internal-acme into main 2026-09-25 19:55:32 +00:00
jschoubben c7964b7285 Merge pull request 'gitea: a consumer's user is actually created, and a failed create says why' (#71) from fix/gitea-provisioner-user-create into main 2026-09-25 19:54:08 +00:00
jschoubben 02ccf31a71 gitea: a consumer's user is actually created, and a failed create says why
The builder's package-registry grant — the first this provider ever
received — retried for a day saying only that an edit 404'd. Two faults
under it: the API refuses an email without a dotted domain, so
`@localhost` failed validation at create (the CLI that made mesh-admin
accepts it, which is why the admin exists and no consumer did); and
ensureUser read that 422 as 'already exists' and went on to edit a user
that was never made, burying the create's own message. The address is now
gitea's own hidden-address shape, and the edit path is taken only for a
user that is actually there.
2026-09-25 21:42:38 +02:00
jschoubben c2353fc0a6 gitea: the internal-API refusal is part of the route, not a file beside the proxy
The 2026-09-12 incident response blocked /api/internal by hand in the
predecessor's dynamic directory, with a note that its durable home is the
mesh's routing. A route carries the policy applied to a request (ADR
0108), so the refusal now travels with the grant: route-proxy enforces
it on both the public name and the internal alias the moment it serves
this route, and the adapter skips it aloud (no port, nothing to write)
while the predecessor's own file still stands. The hand-authored file
retires with the proxy it configures.
2026-09-25 20:51:44 +02:00
jochen 45dd036623 The npm registry is a seat gitea holds, and gitea holds the git seat a build's source can live on
Implements novox/hq ADR 0109, 0110 and 0111 in the catalogue.

package-registry becomes npm-package-registry throughout (ADR 0109): gitea provides and serves it,
verdaccio provides it, the builder requires, binds and receives its secret under it. gitea's
contributions file is grants/npm.json, so a second ecosystem's file has an obvious name beside it.

gitea claims two mesh seats (ADR 0110): npm-package-registry, which it delivers, and git, which it
now provides with what a clone URL is composed from — http on the forge's web port (ADR 0111).
verdaccio provides npm-package-registry and claims nothing: it is the second provider the seat
exists to make harmless, since a consumer now resolves to the seat's holder without a pin.

No cargo or PyPI provision is added; ADR 0109 defers that. git mints no credential, so gitea's
provisioner registers nothing for it — the mesh's own repositories are public, and a clone
credential is undecided (ADR 0111).

The provisioner still reads where its contributions land from $MESH_RECEIVES, and names no path
itself. One variable carries one path, so a second registration in this module would need the mesh
to say where each provision's file is; that is not possible yet and is not faked here.

Verified: the controller's tests read this catalogue — every claim is a seat in the set, the forge
holds both seats and serves what a clone URL needs, the builder requires what the npm seat delivers
— and pass. Not verified here: a TypeScript build of gitea, whose dependencies resolve from the
private registry.
2026-09-25 20:48:10 +02:00
jschoubben 962cba7c04 route-proxy: internal names are certified by the mesh's own authority
Two name spaces, two authorities (08-connectivity §2): a public name is
certified by a public CA, an internal one by the mesh's own. step-ca now
offers that second seat as internal-acme-ca beside its existing acme-ca,
and route-proxy requires both — the server dispatches by which authority
may certify the name at all, so an .internal alias stops being plain-HTTP
only without ever asking a public CA for a name it cannot validate.
2026-09-25 20:36:41 +02:00
jschoubben bfe99f8c78 Merge pull request 'minio: declare the route contribution it has always needed, scoped from PR #58' (#68) from fix/minio-declares-its-real-route-contribution into main 2026-09-25 16:19:39 +00:00
jschoubben f0aa9e5fed minio: declare the route contribution it has always needed, scoped from PR #58
files-api.novox.be and files.novox.be worked earlier tonight from route-
adapter-generated files, but minio's module.json on main never actually
carried a route requirement — that capability has been sitting in PR #58
the whole time, bundled with an unrelated network rename and console
redirect URL that need their own calmer review. This is just the two
routes: requires: route, contributes.route.api/.console (ContributesMany,
proven working via mesh-controller #55/#57), and the console port (9001)
actually published and declared in listens.

Found assigning route-proxy for the first time tonight: its own routes
file, generated the identical way route-adapter's always was, had four
hostnames in it instead of six — nothing served files-api/files at all,
which would have been a real, silent outage the moment Traefik stopped.
2026-09-25 18:19:27 +02:00
jschoubben cb38bf08a6 Merge pull request 'route-proxy: the trust step skips the fetch when the CA names no roots to get' (#67) from fix/route-proxy-trust-skips-when-there-is-nothing-to-fetch into main 2026-09-25 16:15:09 +00:00
jschoubben 95a0a5672c route-proxy: the trust step skips the fetch when the CA names no roots to get
Composed ACME_ROOTS unconditionally from ${bound:acme-ca:roots} even when
that field is empty — public-acme's own case, where an empty roots means
'the system trust store', not 'fetch from the bare authority host'. The
run-once step wget'd https://acme-v02.api.letsencrypt.org:443 (host, no
path) for two minutes every apply and failed, blocking every resource
after it — found live tonight, assigning route-proxy for the first time.

Carries the raw, uncomposed roots value alongside the composed URL
(ACME_ROOTS_PATH) so the step can tell 'nothing to fetch' apart from 'the
authority didn't answer' — a distinction the composed URL alone cannot
make. Empty copies the image's own system CA bundle to /ca/root.crt
instead of fetching one, so ACME_CA_BUNDLE stays the one path it has
always been rather than needing to become conditional itself.
2026-09-25 18:14:58 +02:00
jschoubben d243b56942 Merge pull request 'route-proxy: the server container resolves its own built artifact' (#66) from fix/route-proxy-server-uses-its-real-artifact into main 2026-09-25 16:09:19 +00:00
jschoubben 4c5e69903f route-proxy: the server container resolves its own built artifact
Was still pinned to the scaffold's placeholder digest (mesh-route-
proxy@sha256:0000...0000) even after the build+context work landed —
never caught because nothing had assigned route-proxy before tonight.
artifact: server, matching trust's own reference a few lines up and
every other built module in the catalogue.
2026-09-25 18:09:09 +02:00
jschoubben 547937034f Merge pull request 'public-acme: the roots field is named roots, not root' (#65) from fix/public-acme-field-name-matches-what-route-proxy-reads into main 2026-09-25 16:08:39 +00:00
jschoubben 3147fac08b public-acme: the roots field is named roots, not root
step-ca (the other acme-ca provider) already spells it correctly; route-
proxy's own template reads ${bound:acme-ca:roots}. Found live, assigning
public-acme for the first time tonight: the mesh refused the push outright
rather than composing a broken binding — 'route-proxy asks its acme-ca
binding for roots, and what answers it says ... root'. Empty stays empty:
a public CA's root is the system trust store already, per route-proxy's
own design (an empty ACME_CA_BUNDLE means exactly that).
2026-09-25 18:08:28 +02:00
jschoubben 7a96287d99 Merge pull request 'route-proxy: declare the build context its own Dockerfile has always needed' (#64) from feat/route-proxy-declares-its-real-context into main 2026-09-25 15:58:20 +00:00
jschoubben dd5b973e66 route-proxy: declare the build context its own Dockerfile has always needed
The Dockerfile's own comment already said it — 'the build context is the
mesh-controller repository root' — but nothing in the manifest actually
said so to the mesh, so every build attempt used mesh-catalog's own
directory instead and failed with 'stat go.mod: file does not exist'.
Never caught before because route-proxy has never been assigned anywhere.
Uses the context mechanism just added (mesh-controller#62), proven
working tonight on builder's own self-build.
2026-09-25 17:58:10 +02:00
jschoubben 72b1413497 Merge pull request 'builder is a real built module now, not handed over' (#63) from feat/builder-is-a-real-built-module into main 2026-09-25 15:54:58 +00:00