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.
Let an account be a bot
29a795b9d5b79cf5c8f2b7f3b4f6572f7cdff444 · Keenan Nemetz · 7d ago · parent e7aa0785
Let an account be a bot An automation needs an identity: something to assign an issue to, mention in a comment, and name in an approval and the audit log. An account already gives all of that, so this adds only the invariants a bot needs and the word for it. A bot is created with `vole admin account create --bot`, with the repository role the admin decides it should have. It has no browser sign-in — `account invite` and adding a passkey are refused — and it cannot hold the instance-admin role, because Admin reaches every repository and every instance surface while a bot is a repository automation. It is marked as a bot in the user directory and on its profile. Everything else is deliberately unchanged. A bot obeys the rules of its role: a contribute bot pushes only its own contributor branch and its CI waits for a maintainer to allow the tip; a maintain bot pushes shared branches, runs CI immediately, and its approval counts. The kind is fixed at creation, like the name, and a roster that predates it reads every account as a person. This is the first of three slices of #165. The per-run credential that lets a workflow act as a bot without a long-lived key in a repository secret is next; this slice changes no push rule and no permission.
server/web/people.go +2−1
| @@ -35,6 +35,7 @@ const peoplePage = 50 | |||
| 35 | 35 | // profile reads, so a row and the page it opens tell the same story. | |
| 36 | 36 | type peopleRow struct { | |
| 37 | 37 | Name string | |
| 38 | Bot bool | ||
| 38 | 39 | Active bool | |
| 39 | 40 | Joined time.Time | |
| 40 | 41 | Role string // rendered only for an instance admin | |
| @@ -256,7 +257,7 @@ func (w *Web) PeoplePage(rw http.ResponseWriter, r *http.Request) { | |||
| 256 | 257 | rows := make([]peopleRow, 0, len(matched)) | |
| 257 | 258 | for _, acct := range matched { | |
| 258 | 259 | row := peopleRow{ | |
| 259 | Name: acct.Name, Active: acct.Active(), Joined: acct.Created, | ||
| 260 | Name: acct.Name, Bot: acct.Bot(), Active: acct.Active(), Joined: acct.Created, | ||
| 260 | 261 | Summary: summaries[acct.Name], | |
| 261 | 262 | } | |
| 262 | 263 | if page.InstanceAdmin { | |