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