The runtime beside the forge served 0 tools: GiteaClient.fromEnv required a token (settings or MESH_GITEA_TOKEN), nobody had one to give — the mesh raised the forge — and putting one in settings would store a secret in plaintext in the inventory. So the fifteen tools registered nothing and the watcher logged "not watching".
What the mesh does deliver is the admin account (login in the manifest, password minted by the vault and unsealed into a file — ADR 0086). That is enough to mint a token, so the module does (hq issue 100, the forge's tools).
How it works (modules/gitea/token.ts)
Mint: on the first call, POST /api/v1/users/mesh-admin/tokens over basic auth, name mesh-tools, scopes write:repository + write:issue — the least the 15 tools and the repo watcher need (repos, pulls, /user/repos poll; issues, comments, labels). Nothing under /admin, /orgs, /users; gitea_api reaches only what these two cover.
Keep: written whole (temp + rename) at 0600 to /var/lib/mesh/gitea/state/token — a new runtime-state directory resource (0700) the runtime container mounts writable at /run/state, named to it as MESH_GITEA_STATE_DIR. Read back on the next start.
Rotate: a 401 from the forge renews once and repeats the call. If the kept file is gone but the forge still holds a token named mesh-tools, it is deleted by name and re-minted. Concurrent first calls share one mint; one source per kept file per process (the watcher and the tools entrypoint would otherwise renew against each other); the kept file is re-read before minting over it.
Configured token wins (settings token, MESH_GITEA_TOKEN, GITEA_TOKEN) and is never minted over — a 401 on it is reported, not repaired.
Admin refused (401/403 on the mint — the forge's data came from the predecessor, mesh-admin never created there): a plain message naming the account and what creates it; nothing kept; tools stay registered, the watcher logs the reason once and keeps trying every minute, and recovers on its own once the account exists.
The token is never logged; lines say minted / reusing / renewed and the path, never the value.
Tests (modules/gitea/test/token.test.ts, npm test builds then runs against a fake forge): mints on first start with exactly those two scopes and 0600; reuses on second start; re-mints on 401; replaces a name the forge still holds; concurrent mint shared; admin refused reported plainly and recovers; second process reuses the first's renewal; configured token wins; missing env named; the 15 tools register before any mint and the first call mints.
Also type-checked with the module's tsconfig, and the changed manifest passes the controller's TestEveryCatalogueManifestDeclaresWhatItMounts (MESH_CATALOG pointed at this branch).
On a node whose forge data came from the predecessor: the admin-bootstrap run-once step creates mesh-admin only when its declaration digest has not been recorded as applied; if the data was restored after the step ran, mesh-admin is gone and the step will not run again. The runtime will say so on every poll until an operator creates mesh-admin (admin) on the forge with the password the vault delivered (/var/lib/gitea/admin.secret). Open question whether that should be an action, or a change to the step that re-applies.
The runtime beside the forge served 0 tools: `GiteaClient.fromEnv` required a token (settings or `MESH_GITEA_TOKEN`), nobody had one to give — the mesh raised the forge — and putting one in settings would store a secret in plaintext in the inventory. So the fifteen tools registered nothing and the watcher logged "not watching".
What the mesh does deliver is the admin account (login in the manifest, password minted by the vault and unsealed into a file — ADR 0086). That is enough to mint a token, so the module does (hq issue 100, the forge's tools).
**How it works** (`modules/gitea/token.ts`)
- **Mint:** on the first call, `POST /api/v1/users/mesh-admin/tokens` over basic auth, name `mesh-tools`, scopes `write:repository` + `write:issue` — the least the 15 tools and the repo watcher need (repos, pulls, `/user/repos` poll; issues, comments, labels). Nothing under `/admin`, `/orgs`, `/users`; `gitea_api` reaches only what these two cover.
- **Keep:** written whole (temp + rename) at 0600 to `/var/lib/mesh/gitea/state/token` — a new `runtime-state` directory resource (0700) the runtime container mounts writable at `/run/state`, named to it as `MESH_GITEA_STATE_DIR`. Read back on the next start.
- **Rotate:** a 401 from the forge renews once and repeats the call. If the kept file is gone but the forge still holds a token named `mesh-tools`, it is deleted by name and re-minted. Concurrent first calls share one mint; one source per kept file per process (the watcher and the tools entrypoint would otherwise renew against each other); the kept file is re-read before minting over it.
- **Configured token wins** (settings `token`, `MESH_GITEA_TOKEN`, `GITEA_TOKEN`) and is never minted over — a 401 on it is reported, not repaired.
- **Admin refused** (401/403 on the mint — the forge's data came from the predecessor, `mesh-admin` never created there): a plain message naming the account and what creates it; nothing kept; tools stay registered, the watcher logs the reason once and keeps trying every minute, and recovers on its own once the account exists.
- The token is never logged; lines say minted / reusing / renewed and the path, never the value.
**Tests** (`modules/gitea/test/token.test.ts`, `npm test` builds then runs against a fake forge): mints on first start with exactly those two scopes and 0600; reuses on second start; re-mints on 401; replaces a name the forge still holds; concurrent mint shared; admin refused reported plainly and recovers; second process reuses the first's renewal; configured token wins; missing env named; the 15 tools register before any mint and the first call mints.
Also type-checked with the module's tsconfig, and the changed manifest passes the controller's `TestEveryCatalogueManifestDeclaresWhatItMounts` (`MESH_CATALOG` pointed at this branch).
**On a node whose forge data came from the predecessor:** the `admin-bootstrap` run-once step creates `mesh-admin` only when its declaration digest has not been recorded as applied; if the data was restored after the step ran, `mesh-admin` is gone and the step will not run again. The runtime will say so on every poll until an operator creates `mesh-admin` (admin) on the forge with the password the vault delivered (`/var/lib/gitea/admin.secret`). Open question whether that should be an action, or a change to the step that re-applies.
The runtime beside the forge served 0 tools: GiteaClient.fromEnv required a token
(settings or MESH_GITEA_TOKEN), nobody had one to give — the mesh raised the forge —
and putting one in settings would store a secret in plaintext in the inventory. So
the fifteen tools registered nothing and the watcher logged "not watching".
What the mesh does deliver is the admin account: a login the manifest names and a
password the vault minted and the host unsealed into a file (ADR 0086). That is
enough to mint a token, so the module does (hq issue 100, the forge's tools):
POST /users/{admin}/tokens over basic auth, scoped to write:repository and
write:issue — the least the tools and the repo watcher need — kept at 0600 in the
module's own state (/var/lib/mesh/gitea/state, a new directory resource the runtime
mounts writable), read back on the next start, and minted afresh when the forge
answers 401 to it or the kept file is gone. A forge whose data came from the
predecessor has no mesh-admin: that is reported in plain words on every poll until
it clears, once per reason, not crash-looped. A configured token still wins and is
never minted over.
The mint happens on the first call, not at registration: a contributor is
synchronous, and a forge not yet answering must not keep the runtime from serving.
One source per kept file in a process, or the watcher and the tools would each
renew on a 401 and drop the other's token by name.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The runtime beside the forge served 0 tools:
GiteaClient.fromEnvrequired a token (settings orMESH_GITEA_TOKEN), nobody had one to give — the mesh raised the forge — and putting one in settings would store a secret in plaintext in the inventory. So the fifteen tools registered nothing and the watcher logged "not watching".What the mesh does deliver is the admin account (login in the manifest, password minted by the vault and unsealed into a file — ADR 0086). That is enough to mint a token, so the module does (hq issue 100, the forge's tools).
How it works (
modules/gitea/token.ts)POST /api/v1/users/mesh-admin/tokensover basic auth, namemesh-tools, scopeswrite:repository+write:issue— the least the 15 tools and the repo watcher need (repos, pulls,/user/repospoll; issues, comments, labels). Nothing under/admin,/orgs,/users;gitea_apireaches only what these two cover./var/lib/mesh/gitea/state/token— a newruntime-statedirectory resource (0700) the runtime container mounts writable at/run/state, named to it asMESH_GITEA_STATE_DIR. Read back on the next start.mesh-tools, it is deleted by name and re-minted. Concurrent first calls share one mint; one source per kept file per process (the watcher and the tools entrypoint would otherwise renew against each other); the kept file is re-read before minting over it.token,MESH_GITEA_TOKEN,GITEA_TOKEN) and is never minted over — a 401 on it is reported, not repaired.mesh-adminnever created there): a plain message naming the account and what creates it; nothing kept; tools stay registered, the watcher logs the reason once and keeps trying every minute, and recovers on its own once the account exists.Tests (
modules/gitea/test/token.test.ts,npm testbuilds then runs against a fake forge): mints on first start with exactly those two scopes and 0600; reuses on second start; re-mints on 401; replaces a name the forge still holds; concurrent mint shared; admin refused reported plainly and recovers; second process reuses the first's renewal; configured token wins; missing env named; the 15 tools register before any mint and the first call mints.Also type-checked with the module's tsconfig, and the changed manifest passes the controller's
TestEveryCatalogueManifestDeclaresWhatItMounts(MESH_CATALOGpointed at this branch).On a node whose forge data came from the predecessor: the
admin-bootstraprun-once step createsmesh-adminonly when its declaration digest has not been recorded as applied; if the data was restored after the step ran,mesh-adminis gone and the step will not run again. The runtime will say so on every poll until an operator createsmesh-admin(admin) on the forge with the password the vault delivered (/var/lib/gitea/admin.secret). Open question whether that should be an action, or a change to the step that re-applies.The runtime beside the forge served 0 tools: GiteaClient.fromEnv required a token (settings or MESH_GITEA_TOKEN), nobody had one to give — the mesh raised the forge — and putting one in settings would store a secret in plaintext in the inventory. So the fifteen tools registered nothing and the watcher logged "not watching". What the mesh does deliver is the admin account: a login the manifest names and a password the vault minted and the host unsealed into a file (ADR 0086). That is enough to mint a token, so the module does (hq issue 100, the forge's tools): POST /users/{admin}/tokens over basic auth, scoped to write:repository and write:issue — the least the tools and the repo watcher need — kept at 0600 in the module's own state (/var/lib/mesh/gitea/state, a new directory resource the runtime mounts writable), read back on the next start, and minted afresh when the forge answers 401 to it or the kept file is gone. A forge whose data came from the predecessor has no mesh-admin: that is reported in plain words on every poll until it clears, once per reason, not crash-looped. A configured token still wins and is never minted over. The mint happens on the first call, not at registration: a contributor is synchronous, and a forge not yet answering must not keep the runtime from serving. One source per kept file in a process, or the watcher and the tools would each renew on a 401 and drop the other's token by name.