Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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.

The roster is a database, not a repository

427c47611f7db09e52618a0f2d05291a8641b2d0 · Keenan Nemetz · 17d ago · parent 0f442b1c

The roster is a database, not a repository

data/system.git is gone. Accounts, every name they have held and their SSH keys
live in data/users.db, next to the other authoritative store (runs.db) and the
disposable index.

  users       id, name, status, created
  user_names  every name ever held -- primary key, so a name is issued once
  user_keys   fingerprint, type, line -- primary key, so a key has one owner

Two invariants that used to be parse-time checks are now table keys, and a roster
change is one transaction instead of a commit, a CAS retry and a tree walk. The
snapshot, the five-second reload and the audit line per change are unchanged, so
`vole user add-key` still applies without a restart and revocation is still
immediate.

Clients no longer clone the roster, because there is nothing to clone. The one
view a client needs -- active id and name -- is served by a `vole-roster` SSH
verb over the key it already pushes with, so name resolution gained no endpoint
and no second credential. `vole request new --author keenan` now resolves through
that verb (TestRequestCLIResolvesNamesOverSSH: the request landed with
author_id = the account id, and an unknown name fails before pushing).

A WAL database cannot be backed up with cp, so `vole user backup <path>` takes a
consistent copy with VACUUM INTO (TestRosterIsOneDatabaseFile opens the copy and
resolves a renamed account and its key from it). This replaces the
"identity survives without a side database" criterion, which is withdrawn rather
than left looking unmet: the roster is now exactly one file, and the backup is
what proves it is not lost with the index.

Verified through the binary: init -> create -> add-key -> rename -> list ->
backup, the restored copy listing the same account, former name and key; then
scripts/ci.sh green (gofmt, vet, build, markdown links, go test -race ./...).

README.md +15−11

@@ -33,11 +33,13 @@ Working today:
33 33 - Policy enforced in `pre-receive`, so a rejected push moves no refs at all
34 34 - Protected refs, per-blob size limits, and a push cap enforced by git's own
35 35 `receive.maxInputSize`
36 - The user roster is one file of SSH public keys per user in `data/trusted_keys/`
37 (plain `authorized_keys` format), with an append-only `data/audit.log` recording
38 every key added or revoked. A running instance re-reads the directory every few
39 seconds, so `vole user add-key` — or an edit by hand — applies without a restart,
40 and revocation takes effect the same way
36 - The user roster lives in one SQLite database, `data/users.db`: an account is a
37 stable id plus a mutable name, with its SSH public keys, and a retired name is
38 never issued twice. Nothing stored is secret. A running instance re-reads the
39 database every few seconds, so `vole user add-key` or `vole user retire` applies
40 without a restart and revocation takes effect the same way; an append-only
41 `data/audit.log` records every change. A clone has no copy of the roster — the
42 CLI reads the id/name map it needs over the same SSH key it pushes with
41 43 - Issues and change requests stored as files in the repository on the `vole`
42 44 branch, validated at push time and rendered read-only on the web
43 45 - Review as a git artifact: an approval names the exact commit it reviewed, and
@@ -79,7 +81,7 @@ would restate it for a verifier this deployment does not have (see the roadmap).
79 81 ```sh
80 82 go build -o vole ./cmd/vole
81 83
82 ./vole init --dir . # config + data/trusted_keys
84 ./vole init --dir . # config + data/users.db
83 85 ./vole user create you
84 86 ./vole user add-key you --key ~/.ssh/id_ed25519.pub
85 87 ./vole repo create demo --owner you
@@ -465,16 +467,18 @@ server {
465 467
466 468 ## Backup
467 469
468 Two things, and both are small:
470 Three things, and all are small:
469 471
470 472 ```sh
471 rsync -a data/trusted_keys/ backup/trusted_keys/ # users and their keys
473 ./vole user backup backup/users.db # accounts and their keys
472 474 cp data/instance.key data/ssh_host_ed25519_key backup/ # the only two secrets
473 cp data/audit.log backup/ 2>/dev/null # who added what, when
475 cp data/audit.log backup/ 2>/dev/null # who added what, when
474 476 ```
475 477
476 Losing the key files means re-issuing them, not losing data — nothing in the
477 roster is secret, so a copied directory leaks nothing.
478 `vole user backup` is a consistent copy taken through SQLite, so it is safe while
479 the instance is running; copying `users.db` by hand can miss rows the database has
480 not checkpointed yet. Losing the roster means re-adding keys, not losing
481 repositories — nothing in it is secret, so a copied file leaks nothing.
478 482
479 483 Run everything before pushing:
480 484