vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
Add browser auth, contributor access, and gated CI
6c84d387e5f608360e30484a1a5b1cff41076850 · Keenan Nemetz · 16d ago · parent c36d5073
Add browser auth, contributor access, and gated CI Browser sign-in now uses passkeys. Enrollment links are one-time, sessions are hashed in users.db, and issue/comment/approval writes use the session account. HTTPS public_url is required for WebAuthn. Add the contributor role between read and maintain. Contributors can discuss and open change requests, and may push only refs/heads/contrib/<account-id>/. Contributor CI is held per commit until a maintainer allows the exact tip from the repository page. Rename the write role to maintain. write remains accepted as the historical spelling, and access.Write remains as a Go alias so existing callers and ACL rows keep working. Update the web, CLI, queue schema, and guides for the new flows, and cover the role, namespace, auth, and CI-gating paths in tests.
docs/architecture.md +29−15
| @@ -9,21 +9,35 @@ hardware you control, and keeps the important parts in Git. | |||
| 9 | 9 | Vole is built for one team on one server. It does not try to be a hosted service, | |
| 10 | 10 | and it does not connect to other Vole instances. That keeps the design small. | |
| 11 | 11 | ||
| 12 | ## Trust: your key is your identity | ||
| 12 | ## Trust: a credential resolves to an account | ||
| 13 | 13 | ||
| 14 | 14 | Vole uses one rule everywhere: | |
| 15 | 15 | ||
| 16 | > Your SSH key proves who you are. Vole checks every change before accepting it, | ||
| 17 | > and records the decision in `data/audit.log`. | ||
| 16 | > A credential proves which account you are. Vole checks every change before | ||
| 17 | > accepting it, and records the decision in `data/audit.log`. | ||
| 18 | 18 | ||
| 19 | There is no second login system for writes. The web interface is read-only. | ||
| 20 | Approvals, comments, and CI results all belong to the account whose key pushed | ||
| 21 | them. | ||
| 19 | There are two credentials, and they resolve to the same stable account id: | ||
| 20 | |||
| 21 | - an **SSH key** for Git over SSH, administration, and CI; | ||
| 22 | - a **passkey** for browser sessions. | ||
| 23 | |||
| 24 | There is no password database. The browser performs a WebAuthn ceremony, Vole | ||
| 25 | stores only the credential's public half, and the resulting session is an opaque | ||
| 26 | token whose hash alone is in `users.db`. | ||
| 27 | |||
| 28 | Browser writes are deliberately narrow: file or comment on an issue, open a | ||
| 29 | change request from a branch the account may push, and approve a change request. | ||
| 30 | An untrusted contributor's Git writes are confined to | ||
| 31 | `refs/heads/contrib/<account-id>/`; the pre-receive gate enforces the namespace, | ||
| 32 | and CI for that branch does not run until a maintainer allows the exact tip. They are built by the same metadata writers the CLI uses, so | ||
| 33 | the file format and its validation do not fork. Everything else still changes by | ||
| 34 | pushing over SSH. Approvals, comments, and CI results belong to the account the | ||
| 35 | credential resolved to, never to a name typed into a form. | ||
| 22 | 36 | ||
| 23 | 37 | Vole does not require or check Git commit signatures. A signature would only | |
| 24 | repeat what the SSH connection already established, and it cannot reliably prove | ||
| 25 | who authored a commit. Vole would rather keep one clear path than add a second | ||
| 26 | one that can disagree with the first. | ||
| 38 | repeat what the credential already established, and it cannot reliably prove who | ||
| 39 | authored a commit. Vole would rather keep one clear path than add a second one | ||
| 40 | that can disagree with the first. | ||
| 27 | 41 | ||
| 28 | 42 | ## What that means | |
| 29 | 43 | ||
| @@ -41,9 +55,9 @@ auditable. | |||
| 41 | 55 | |---|---|---| | |
| 42 | 56 | | Repositories | `data/repos/<name>.git` | It is the source | | |
| 43 | 57 | | Issues, requests, approvals, release pointers, CI records | The `vole` branch in each repository | It is the source | | |
| 44 | | Accounts, SSH keys, instance admins, access rules, locks | `data/users.db` | No; back it up | | ||
| 58 | | Accounts, SSH keys, passkeys, invites, sessions, instance admins, access rules, locks | `data/users.db` | No; back it up | | ||
| 45 | 59 | | Web/search index | `data/index.db` | Yes, with `vole admin index rebuild` | | |
| 46 | | CI queue and run index | `data/runs.db` | Partly; in-progress work is lost | | ||
| 60 | | CI queue, run index, contributor CI approvals | `data/runs.db` | Partly; in-progress work and approvals are lost | | ||
| 47 | 61 | | LFS files, CI logs, build artifacts | `data/lfs/` | No; stored by content | | |
| 48 | 62 | | Privileged-action history | `data/audit.log` | It is the record | | |
| 49 | 63 | | SSH host key | `data/ssh_host_ed25519_key` | No; it is the secret | | |
| @@ -72,10 +86,10 @@ is checked when it is pushed. | |||
| 72 | 86 | | Choice | Reason | | |
| 73 | 87 | |---|---| | |
| 74 | 88 | | Use the Git command-line tool | Full push behavior and hooks without reimplementing Git | | |
| 75 | | SSH for writes, HTTP for anonymous reads | One authenticated way to change data; reads stay simple | | ||
| 76 | | Check everything before accepting a push | One place for policy; the web has no writes to protect | | ||
| 89 | | SSH for Git writes, passkeys for the browser | Each transport gets the credential it can actually use, and both resolve to one account id | | ||
| 90 | | Check everything before accepting a change | The push boundary and the narrow web writers share the same metadata validation | | ||
| 77 | 91 | | Store metadata in Git, rules in SQLite | Project content travels with clones; local settings stay local | | |
| 78 | | No web login | The web is read-only, so there is no password store or session to build | | ||
| 92 | | Passkeys, not passwords | No shared secret to phish, reset, or store; recovery is an administrator-issued enrollment link | | ||
| 79 | 93 | | One program with separate serve and runner modes | Easy to deploy anywhere | | |
| 80 | 94 | | Keep the host key as the only secret | No signing key to manage or leak | | |
| 81 | 95 | ||
| @@ -89,7 +103,7 @@ parts of Vole without running the `vole` command. | |||
| 89 | 103 | | `client` | Talks to an instance: metadata writes, identity lookups, file transfer, reviews, and site deploys | | |
| 90 | 104 | | `core/id`, `core/git`, `core/lfs` | Shared primitives and storage | | |
| 91 | 105 | | `forge` | Instance setup and administration | | |
| 92 | | `forge/config`, `forge/identity`, `forge/access`, `forge/repo`, `forge/index` | Settings, accounts, permissions, repositories, and search | | ||
| 106 | | `forge/config`, `forge/identity`, `forge/access`, `forge/repo`, `forge/index`, `forge/session` | Settings, accounts, permissions, repositories, search, and browser sessions | | ||
| 93 | 107 | | `metadata` | Issues, requests, releases, comments, approvals, and validation | | |
| 94 | 108 | | `ci` | Workflow validation and the job queue; `ci/agent` is the embeddable runner | | |
| 95 | 109 | | `server` | Network listeners and web pages, with adapters for SSH, Git HTTP, sites, and hooks | | |