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.
Create accounts with a key and find the instance config
c36d507302c0f7fb24af6eba390c4121d67318c3 · Keenan Nemetz · 16d ago · parent 3abbe749
Create accounts with a key and find the instance config An account is now written with its first key in one transaction, so a name cannot be claimed without a credential. add-key remains for additional keys, and remote user create forwards the key on stdin. Local commands now resolve the config from --config, VOLE_CONFIG, ./vole.toml, or ~/.vole/vole.toml before falling back to defaults. init prints --config in its next steps when the instance will not be discovered automatically.
forge/config/config_test.go +52
| @@ -6,6 +6,58 @@ import ( | |||
| 6 | 6 | "testing" | |
| 7 | 7 | ) | |
| 8 | 8 | ||
| 9 | // TestDefaultPathFindsThePerUserInstance is the flow `vole admin init --dir | ||
| 10 | // ~/.vole` promises: later commands find that config without --config. | ||
| 11 | func TestDefaultPathFindsThePerUserInstance(t *testing.T) { | ||
| 12 | home := t.TempDir() | ||
| 13 | t.Setenv("HOME", home) | ||
| 14 | t.Setenv("USERPROFILE", home) | ||
| 15 | t.Setenv("VOLE_CONFIG", "") | ||
| 16 | t.Chdir(t.TempDir()) | ||
| 17 | |||
| 18 | if got := DefaultPath(); got != "vole.toml" { | ||
| 19 | t.Fatalf("default path with no instance = %q, want vole.toml", got) | ||
| 20 | } | ||
| 21 | |||
| 22 | homeConfig := filepath.Join(home, ".vole", "vole.toml") | ||
| 23 | if err := os.MkdirAll(filepath.Dir(homeConfig), 0o755); err != nil { | ||
| 24 | t.Fatal(err) | ||
| 25 | } | ||
| 26 | if err := os.WriteFile(homeConfig, []byte("data_dir = \"/tmp/vole-data\"\n"), 0o644); err != nil { | ||
| 27 | t.Fatal(err) | ||
| 28 | } | ||
| 29 | if got := DefaultPath(); got != homeConfig { | ||
| 30 | t.Fatalf("default path = %q, want %q", got, homeConfig) | ||
| 31 | } | ||
| 32 | |||
| 33 | // A config in the working directory belongs to the checkout or instance the | ||
| 34 | // operator is standing in, so it wins over the home instance. | ||
| 35 | if err := os.WriteFile("vole.toml", []byte("data_dir = \"./data\"\n"), 0o644); err != nil { | ||
| 36 | t.Fatal(err) | ||
| 37 | } | ||
| 38 | if got := DefaultPath(); got != "vole.toml" { | ||
| 39 | t.Fatalf("local config did not win: %q", got) | ||
| 40 | } | ||
| 41 | |||
| 42 | // An explicit environment choice wins over both. | ||
| 43 | t.Setenv("VOLE_CONFIG", "/srv/vole/vole.toml") | ||
| 44 | if got := DefaultPath(); got != "/srv/vole/vole.toml" { | ||
| 45 | t.Fatalf("VOLE_CONFIG did not win: %q", got) | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | // TestResolvePathKeepsExplicitChoice: an empty path means discover, but a named | ||
| 50 | // path must never be silently replaced. | ||
| 51 | func TestResolvePathKeepsExplicitChoice(t *testing.T) { | ||
| 52 | t.Setenv("VOLE_CONFIG", "/from/env.toml") | ||
| 53 | if got := ResolvePath("/explicit/vole.toml"); got != "/explicit/vole.toml" { | ||
| 54 | t.Fatalf("ResolvePath explicit = %q", got) | ||
| 55 | } | ||
| 56 | if got := ResolvePath(""); got != "/from/env.toml" { | ||
| 57 | t.Fatalf("ResolvePath empty = %q", got) | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 9 | 61 | // A mistyped TOML tag does not fail anything: the key is simply ignored and the | |
| 10 | 62 | // default applies, which is invisible until somebody's cap does nothing. So the one | |
| 11 | 63 | // setting whose absence is a *silent* policy change is pinned here. | |