vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Phase 6 is done: sites ride the instance's domain, TLS stays in front
9bea7f110ae780105f9c7c58024a3b67a86d7d7a · Keenan Nemetz · 17d ago · parent d3296cda
Phase 6 is done: sites ride the instance's domain, TLS stays in front Custom domains and ACME are dropped as non-goals rather than deferred work. An instance already owns one domain and terminates TLS in front of it, so every repository's site is a subdomain of that domain and inherits the certificate that already exists. `pages_domain` remains the only setting. The operational requirement that falls out of it, documented in the README and the sample config: the proxy must preserve the Host header, because Host is the only thing that says which site a request is for.
docs/roadmap.md +25−11
| @@ -41,6 +41,9 @@ Explicitly **not** built. Each is additive later; none is load-bearing for the c | |||
| 41 | 41 | - Federation, peer replication, cross-instance search, global entity IDs | |
| 42 | 42 | - SSH transport (HTTP + PATs first; SSH keys are Phase 7 work if demanded) | |
| 43 | 43 | - Container-isolated CI steps (shell steps only in v1) | |
| 44 | - Per-repository custom domains and certificate issuance: an instance owns one domain | ||
| 45 | and terminates TLS in front of it, so sites are subdomains of that domain and | ||
| 46 | inheriting the certificate is the whole story | ||
| 44 | 47 | - Horizontal scaling, HA, multi-writer Postgres | |
| 45 | 48 | - Kubernetes deployment story | |
| 46 | 49 | ||
| @@ -338,7 +341,7 @@ the client's own pre-push check cannot stand in for the server's rule); GC never | |||
| 338 | 341 | collects an object reachable from a live ref (tested against an object reachable only | |
| 339 | 342 | through history, and against an archived CI log). | |
| 340 | 343 | ||
| 341 | ### Phase 6 — Site hosting | ||
| 344 | ### Phase 6 — Site hosting — ✅ complete | ||
| 342 | 345 | ||
| 343 | 346 | ✅ **Slice 1 — the tree is the site.** A repository's `pages` branch is served | |
| 344 | 347 | directly out of git at `<repo>.<pages_domain>`: no export step, no build to drift. | |
| @@ -359,18 +362,29 @@ file server and one that feels like a database. Resolving the repository and its | |||
| 359 | 362 | per request cost 3.8 ms, because it spawned three `git` subprocesses; that is why both | |
| 360 | 363 | are resolved once per memo window.) | |
| 361 | 364 | ||
| 362 | Remaining: | ||
| 363 | |||
| 364 | - Custom domains per repository, and automatic TLS (ACME) for them | ||
| 365 | - A per-repository pages ref; today `pages_ref` is one setting for the instance | ||
| 366 | - The `pages` branch has no review story of its own: it is a normal branch, so | ||
| 367 | protecting it is `vole acl protect` | ||
| 365 | Decisions: | ||
| 366 | |||
| 367 | - **Per-repository custom domains and ACME are dropped, not deferred.** An instance | ||
| 368 | already owns one domain and terminates TLS in front of it; sites inherit both from | ||
| 369 | the forge. Asking Vole to request certificates would add an ACME client, a | ||
| 370 | challenge listener and a DNS story to solve a problem the operator solved once | ||
| 371 | already. `pages_domain` is the single setting: point it at the forge's own domain | ||
| 372 | and every repository's site is a subdomain of it. | ||
| 373 | - The proxy must preserve the `Host` header (`proxy_set_header Host $host` in nginx, | ||
| 374 | the default in Caddy). `Host` is how a request names its site; nothing else routes. | ||
| 375 | - A per-repository pages ref is not planned. One ref name for the instance keeps the | ||
| 376 | mental model to one line, and a repository that wants a different branch can say so | ||
| 377 | by pushing to `pages`. | ||
| 378 | - The site branch is a normal branch with no review story of its own: protecting it | ||
| 379 | is `vole acl protect`, and a site change is reviewed exactly like code. | ||
| 368 | 380 | ||
| 369 | 381 | **Exit criteria:** a pushed doc tree is live in under 5 seconds ✅ (measured: pushed | |
| 370 | over SSH, served on the site host, polled until the new bytes appeared); conditional | ||
| 371 | requests return `304` ✅; a custom domain gets a certificate with no manual steps ❌ | ||
| 372 | (needs the ACME slice); serving a repo with a path-traversal-shaped filename is safe ✅ | ||
| 373 | (and so is a traversal-shaped *request*). | ||
| 382 | over SSH, served on the site host, polled until the new bytes appeared, 1.4 s); | ||
| 383 | conditional requests return `304` ✅; serving a repo with a path-traversal-shaped | ||
| 384 | filename is safe ✅ (and so is a traversal-shaped *request*). The former criterion | ||
| 385 | "a custom domain gets a certificate with no manual steps" is withdrawn: certificates | ||
| 386 | are terminated in front of the instance, and a site is a subdomain of the domain that | ||
| 387 | certificate already covers. | ||
| 374 | 388 | ||
| 375 | 389 | ### Phase 7 — Signing and audit | |
| 376 | 390 | ||