Files
werkator/docs/plan/22-multi-repo.md
T
mhoennigandClaude Fable 5 da1054bf90 Step 22: instance config decided as ~/.werkator.yml, open questions sharpened
Host/instance configuration (domain, port, registry, global concurrency)
lives in a .werkator.yml in the home directory of the user running the
instance — one instance per OS user, matching the platform model; repo
configuration stays in each repository's .git/werkator/. Repo-level
instance keys get ignored with a warning once a home config exists.
The open questions now name the decisions still needed: repo defaults
in the home file, cwd-vs-registry precedence, repo naming, and whether
the third .werkator.yml location should carry a distinct name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 12:49:45 +02:00

89 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Step 22: One Werkator Instance, Many Repositories
Prerequisites: none in code; step 21's Werkdock work is independent.
Read `README.md` first.
This step is a roadmap in sessions (AE), like step 21; each session is sized for one focused Claude Code session.
## The Problem
"One instance per repository" is a founding tenet (`docs/Werkator-Konzept.md`, AGENTS.md) — and on a Managed Webspace it does not scale even to two repositories.
Building Werkbaum next to Werkator on mih34 today means: a second pac user or a second service, a second assigned port, a second tunnel or domain, a second UI, a second metrics page.
Every repository added multiplies operations, while the instance-level resources (port, UI, watcher schedule, executor slots, metrics) could be shared.
The goal: one Werkator instance serves a *set* of repositories — one service, one port, one UI — while each repository keeps its own configuration, secrets, history, and artifacts.
## The Guiding Idea: the Repository Stays Self-Contained
Everything repository-specific already lives *inside* the repository: the machine config with secrets in `.git/werkator/`, build results, auto-build slots, worktrees, buildenvs — and the artifact store is already keyed per repo path.
The multi-repo instance therefore does not absorb repository state; it becomes an *aggregator* over self-contained repositories.
Consequences:
- Adding or removing a repository is editing a registry entry, never a data migration.
- A repository can move between instances (or back to its own) without losing anything.
- Single-repo mode stays the degenerate case: a registry of one, implicitly the current working directory — existing installations keep working without any config change.
## Key Ownership Splits
Today all config comes from the repo's own layers; multi-repo splits ownership:
- **Instance-level** (decided 2026-09-01: a `.werkator.yml` in the *home directory* of the user running the instance): `server.*` (port, bind address, public base URL / domain, nginx), the repository registry, the control token (one UI, one token), `executor.maxConcurrent` as the *global* cap, watcher interval, metrics.
- **Repo-level** (unchanged, from the repo's own layers — machine config in its `.git/werkator/`, committed project config, branch layer): `gitea.*` (each repo has its own owner/repo/token/statusContext), `git.*` credentials, `builds`, retention, per-repo watcher options (e.g. `pullRequestGate`), sandbox policy and its pinning.
- **Both**: a per-repo concurrency cap below the global one may come later; not in the first cut.
The pinning model is untouched: pinned keys still come from each repo's machine config, and the branch layer still cannot reach them.
## The Sessions
### A — Decision and schema (ADR 0009)
- Write ADR 0009: revise the one-instance-per-repository tenet to one-instance-per-*set*; record the aggregator idea and the key ownership split above.
Considered alternative to record: a federation dashboard proxying several single-repo instances — less invasive, but it keeps N services/ports and solves only the UI, not the operations burden.
- Define the instance config: `~/.werkator.yml` (decided 2026-09-01) — the repository registry plus the instance-level keys above; one instance per OS user, which matches the platform model (pac users on a webspace, service users elsewhere).
`werkator server` without a home config serves the current directory exactly as today.
- Decide the transition for instance keys that today sit in a repo's machine config (mih34's carries `server.*`): once a home config exists, repo-level instance keys are ignored with a warning naming both files — never merged silently.
- Define repo identity for display and routes: a short unique name per registry entry (default: directory basename), used as the route segment (`/repos/<name>/…`) and UI grouping key.
- Update `docs/Werkator-Konzept.md` and AGENTS.md wording ("one instance per repository set").
### B — RepoContext refactor, behavior unchanged
- Introduce a `RepoContext` (working dir, config loading, git access, result repository, artifact store key, watcher state) and thread it through executor, watcher, and server code paths that today implicitly use the single `workingDir`.
- The executor becomes instance-global with repo-scoped pools: serialization per (repo, branch), the global `maxConcurrent` across repos; `BuildResult` needs no schema change — results stay in each repo's own JSON file, the repo dimension exists only in memory and in routes.
- Single-repo behavior, routes, and UI stay byte-identical; the full test suite is the acceptance gate.
### C — The registry and N repositories
- Load the registry, build one `RepoContext` per entry; fail the start loudly on duplicate names or unreadable repos (config-version violations abort only that repo's registration, like branch-config violations fail only that branch).
- Watcher multiplexing: one poll cycle iterates the contexts (fetch, enqueue, prune per repo) with per-repo error isolation — one unreachable origin must not starve the others; `WatcherState` gains the repo dimension for the health banner.
- Startup recovery per repo; auto-build slots stay in each repo's `.git/werkator/`.
- CLI commands gain an optional repo selector and default to the current working directory, so `werkator status` inside a repo behaves as today.
### D — Server, API, and UI scoping
- Routes gain the repo segment (`/api/repos/<name>/builds/…`, `/repos/<name>/builds/<key>`); with exactly one registered repo the today-routes keep working (redirect or alias) so bookmarks and posted Gitea links survive.
- Latest/branches/history views group by repo or gain a repo column; one instance-wide metrics page; one control token.
- Gitea status links use the repo-scoped URLs.
### E — Rollout on mih34: Werkbaum joins
- Registry with the Werkator and Werkbaum repositories under the existing user, one service, one port, the existing tunnel.
- Write Werkbaum's `.werkator.yml`: Gradle backend build and npm frontend build in the shared trimmed image (Node is already in it).
- Record the deployment; retire the second-instance/second-user idea from the notes.
## Open Questions
- May `~/.werkator.yml` also carry *repo defaults* (e.g. one `git.account`/`git.token` for all repos of the same forge), merged beneath every repo's own layers — or is it strictly instance-only?
Defaults are convenient but widen where secrets live; strictly-instance-only keeps the "repository stays self-contained" property clean.
- What wins when `werkator server` is started inside a repository while a home config exists — the registry, the cwd, or is that an error demanding an explicit flag?
- Repo names for routes and display: registry-entry name with directory-basename default — or always explicit?
- The same file name `.werkator.yml` now exists in a third location (home, repo root committed, repo `.git/werkator/`) — accept the overload, or name the home file differently (e.g. `~/.werkator-instance.yml`)?
- Fairness across repos when the global concurrency cap is contended (round-robin per repo vs. FIFO) — decide in session C with the real queue behavior at hand.
- Whether buildenv rootfs trees should be shared across repos (today each repo unpacks its own under `.git/werkator/buildenv/`) — the natural answer is Werkdock's image store (step 21 session C), not instance-level state; until then duplicate unpacked rootfs trees are the accepted cost.
- Whether `artifactKey` needs a repo prefix or stays globally unique by construction (random suffix) — decide in session B when the routes are designed.
## Acceptance Criteria
- Session A: ADR 0009 merged; registry and key-ownership documented in `docs/configuration.md`.
- Session B: full suite green with `RepoContext` threaded through; no route or behavior change observable.
- Session C: an instance with two registered repos builds pushes in both, with per-repo error isolation proven by a test (one broken origin, the other keeps building).
- Session D: both repos browsable in one UI; single-repo installations keep their existing URLs.
- Session E: mih34 builds Werkator and Werkbaum from one service; `docs/deployment.md` describes the registry setup.