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/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.

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 ./...).

internal/hook/names_test.go +5−4

@@ -8,19 +8,20 @@ import (
8 8 )
9 9
10 10 func TestOpenNamesResolves(t *testing.T) {
11 dir := filepath.Join(t.TempDir(), "system.git")
12 if err := identity.Init(dir); err != nil {
11 path := filepath.Join(t.TempDir(), "users.db")
12 if err := identity.Init(path); err != nil {
13 13 t.Fatal(err)
14 14 }
15 s, err := identity.Open(dir)
15 s, err := identity.Open(path)
16 16 if err != nil {
17 17 t.Fatal(err)
18 18 }
19 defer s.Close()
19 20 a, err := s.Create("keenan", "test")
20 21 if err != nil {
21 22 t.Fatal(err)
22 23 }
23 got := openNames(dir)(a.ID)
24 got := openNames(path)(a.ID)
24 25 if got != "keenan" {
25 26 t.Fatalf("openNames = %q, want keenan (id %s)", got, a.ID)
26 27 }