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/config/config.go +4−4

@@ -82,11 +82,11 @@ func Load(path string) (*Config, error) {
82 82
83 83 func (c *Config) ReposDir() string { return filepath.Join(c.DataDir, "repos") }
84 84
85 // SystemRepo is the instance repository: the user roster lives on its main
86 // branch, and later the instance's landing page will live there too.
87 func (c *Config) SystemRepo() string { return filepath.Join(c.DataDir, "system.git") }
85 // UsersDB is the authoritative user roster: accounts, the names they have ever
86 // held, and their SSH keys. It is not derived and not rebuildable.
87 func (c *Config) UsersDB() string { return filepath.Join(c.DataDir, "users.db") }
88 88
89 // AuditLog records privileged operations. Roster history also lives in git.
89 // AuditLog records privileged operations, including every roster change.
90 90 func (c *Config) AuditLog() string { return filepath.Join(c.DataDir, "audit.log") }
91 91 func (c *Config) IndexDB() string { return filepath.Join(c.DataDir, "index.db") }
92 92