mssql disables the login, mongodb takes the user's roles, minio revokes the key and keeps the bucket, mailu disables the mailbox, gitea prohibits the login instead of purging the user and their repositories, umami keeps the website. Each provider's create already enables what this locks.
66 lines
4.0 KiB
TypeScript
66 lines
4.0 KiB
TypeScript
// gitea's provisioner — the adapter that makes gitea a provider of the mesh
|
|
// `npm-package-registry` interface. The reconcile loop, the contributions file, and reading the
|
|
// mesh's minted password are the sdk harness's; this writes only the per-service half: how gitea
|
|
// creates and removes a consumer's npm credential (novox/hq ADR 0048/0076).
|
|
//
|
|
// **A package registry seat is one per ecosystem (novox/hq ADR 0109).** gitea holds the npm seat
|
|
// (ADR 0110). Adding cargo or PyPI is adding a provision — another `provides` entry, another
|
|
// `receives` path and another registration below — not widening this one. `git`, which gitea also
|
|
// provides, mints nothing and so registers nothing here: the mesh's own repositories are public,
|
|
// and a clone credential is not yet decided (ADR 0111).
|
|
//
|
|
// The `npm-package-registry` interface: a consumer authenticates to the npm registry at
|
|
// `/api/packages/novox/npm/` with basic auth, as `as` with the password the mesh minted, and can
|
|
// read and write packages under the `@novox` scope. The registry's npm owner is the gitea org
|
|
// `novox`; a consumer is a gitea *user* placed on that org's package team.
|
|
//
|
|
// **The user name and password are the mesh's, not the provisioner's (ADR 0048).** The mesh derives
|
|
// the login and hands it to both ends, and mints the password. gitea creates a user under exactly
|
|
// that login and sets exactly that password every run — so a rotation takes — and seals nothing: the
|
|
// consumer already has its copy through the mesh's own channel.
|
|
//
|
|
// The admin calls run through GiteaAdmin (basic auth as the mesh's gitea admin), which is the
|
|
// module's one boundary to the forge's admin API (see client.ts).
|
|
|
|
import { runProvisioner, type Provision } from "@novox/mesh-sdk/provisioner";
|
|
import { GiteaAdmin } from "../client.js";
|
|
|
|
// The npm registry owner: a gitea org named `novox`, whose package team every consumer joins so it
|
|
// can read and write packages under the `@novox` scope (ADR 0076).
|
|
const ORG = "novox";
|
|
const PACKAGE_TEAM = "packages";
|
|
|
|
const gitea = GiteaAdmin.fromEnv();
|
|
|
|
// Where this registration's contributions land comes from $MESH_RECEIVES, never a path written
|
|
// here: the mesh writes the file where the manifest's `receives` says, and a second copy of that
|
|
// path in code would drift from it. One variable carries one path, so a second registration in this
|
|
// module needs the mesh to say where each provision's file is — not yet possible, and not faked.
|
|
runProvisioner("npm-package-registry", {
|
|
async create(p: Provision): Promise<void> {
|
|
// The org and its package team are the same for every consumer; ensuring them per-create is
|
|
// idempotent and needs no separate bootstrap step.
|
|
await gitea.ensureOrg(ORG);
|
|
const teamId = await gitea.ensureTeam(ORG, PACKAGE_TEAM, true);
|
|
// The user carries the consumer's login and the mesh's minted password, set every run so a
|
|
// rotation takes. Membership of the package team is what grants read+write on packages.
|
|
//
|
|
// The address is gitea's own convention for one that is not real: its email validation
|
|
// requires a dotted domain, so `@localhost` was refused at create — the fault that had this
|
|
// grant retrying for a day — while `@noreply.localhost` is the shape gitea itself gives
|
|
// hidden addresses.
|
|
await gitea.ensureUser(p.as, p.password, `${p.as}@noreply.localhost`);
|
|
await gitea.addUserToTeam(teamId, p.as);
|
|
},
|
|
|
|
async remove(p: { as: string }): Promise<void> {
|
|
// Login prohibited, never deleted (novox/hq issue 241: a withdrawal never destroys a consumer's data — on 2026-10-04 a misread grants file withdrew every consumer at once): deleting purges every repository the user owns.
|
|
await gitea.prohibitLogin(p.as);
|
|
},
|
|
// Asked every minute by the harness: whether the backend still holds this consumer exactly as
|
|
// the mesh gave it, so a login lost behind the provisioner's back is made again (novox/hq issue 120).
|
|
async holds(p: Provision): Promise<boolean> {
|
|
return gitea.holdsTeamMember(ORG, PACKAGE_TEAM, p.as, p.password);
|
|
},
|
|
});
|