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.
|
ab326d3a
Keenan Nemetz
14d ago
|
1 | # Accounts and access |
|
520470e1
Keenan Nemetz
16d ago
|
2 | |
|
6c84d387
Keenan Nemetz
16d ago
|
3 | Vole has no password database. An account has two kinds of credential: |
| 4 | ||
| 5 | - an **SSH key** for Git over SSH and remote administration; | |
| 6 | - a **passkey** for signing in to the browser. | |
| 7 | ||
|
474a812d
Keenan Nemetz
16d ago
|
8 | Both resolve to the same account, so access rules, approvals, and audit records |
| 9 | do not care which one you used. | |
|
520470e1
Keenan Nemetz
16d ago
|
10 | |
| 11 | ## Accounts and keys | |
| 12 | ||
|
62576fa8
Keenan Nemetz
14d ago
|
13 | An account is identified by its **name**, chosen when the account is created and |
| 14 | fixed for good. It is what your contributor branches are named after | |
| 15 | (`refs/heads/contrib/<name>/`), and it is the key for access rules, approvals, | |
| 16 | file locks and CI records. There is no second internal identifier and no separate | |
| 17 | display label, so a rename would strand every ref, approval and author field | |
| 18 | already written. Keep the name. | |
|
474a812d
Keenan Nemetz
16d ago
|
19 | |
| 20 | Names are never reused: because the account row outlives retirement, retiring | |
| 21 | someone keeps their name attached to them. An old reference can never silently | |
| 22 | point at a different person. | |
|
520470e1
Keenan Nemetz
16d ago
|
23 | |
|
29a795b9
Keenan Nemetz
7d ago
|
24 | ## Bot accounts |
| 25 | ||
| 26 | An automation can have an account of its own, so the work it does is attributable | |
| 27 | to it and it can be assigned an issue or named in a comment like anyone else. A bot | |
| 28 | is created by an instance admin with the repository role it should have: | |
| 29 | ||
| 30 | ```sh | |
| 31 | vole admin account create coding-agent --bot --key agent.pub --role contribute | |
| 32 | ``` | |
| 33 | ||
|
8c72543b
Keenan Nemetz
5d ago
|
34 | An instance admin can do the same from the **Create a bot account** form on the |
| 35 | Accounts page: name the account, paste its public key, and choose a role. A bot | |
| 36 | cannot be given the instance-admin role in either place. | |
| 37 | ||
|
29a795b9
Keenan Nemetz
7d ago
|
38 | The kind is fixed at creation, like the name. A bot differs from a person in three |
| 39 | ways, and only three: | |
| 40 | ||
| 41 | - **No browser sign-in.** A bot cannot be given a passkey or an enrollment link, | |
| 42 | because it has no browser, and a link it could never use is a secret in the roster | |
| 43 | for no reason. `account invite` and adding a passkey are refused for a bot. | |
| 44 | - **No instance admin.** A bot is a repository automation, and the Admin role reaches | |
| 45 | every repository and every instance surface, so it is refused when the account is | |
| 46 | created and when its role is changed later. | |
| 47 | - **It is shown as a bot** in the user directory and on its profile, so a reader | |
| 48 | knows a machine wrote what they are reading. | |
| 49 | ||
| 50 | Everything else is the same, and deliberately so: a bot is subject to the rules of | |
| 51 | whatever role it holds. A `contribute` bot may push only | |
| 52 | `refs/heads/contrib/<name>/`, and CI on that tip waits for a maintainer to allow it, | |
| 53 | exactly as for a person. A `maintain` bot may push shared branches, its pushes run | |
| 54 | CI immediately, it receives the secrets its workflows declare, and its approval | |
| 55 | counts when it is not the author. Granting a role is the trust decision, and the | |
| 56 | audit log and every approval name the bot. | |
| 57 | ||
|
6e1fca51
Keenan Nemetz
6d ago
|
58 | A workflow can act as a bot without holding a long-lived key. A job that names the |
|
ac541b26
Keenan Nemetz
6d ago
|
59 | account with `acts-as:` is given a credential when it is leased: a key generated for |
| 60 | that run, written into the job, and gone when the run ends. The key may do what the | |
|
6e1fca51
Keenan Nemetz
6d ago
|
61 | account may do: discuss, file an issue, manage an item it wrote, open a change from a |
| 62 | branch it may push, triage and approve when the role allows it, and push the branches | |
| 63 | the role allows. It cannot reach an admin surface, read repository secrets, or lease | |
| 64 | work, because those are not repository writes. | |
| 65 | ||
| 66 | The credential is not the account's key, and it does not widen the role: a | |
| 67 | `contribute` bot's job still opens changes in its own namespace, and a job cannot | |
| 68 | approve unless its account can. See | |
|
ac541b26
Keenan Nemetz
6d ago
|
69 | [Workflow reference § acts-as](reference/workflow-reference.md#acts-as). A bot may also hold an |
|
6e1fca51
Keenan Nemetz
6d ago
|
70 | ordinary key, provisioned the way a person's is, for an automation that is not a |
|
338a19dd
Keenan Nemetz
6d ago
|
71 | workflow run. A CI runner is that case: the runner process uses the bot's ordinary |
| 72 | key to lease work, while the jobs it runs still get a credential for the run. See | |
| 73 | [Runners § The runner's account](runner.md#the-runners-account). | |
|
29a795b9
Keenan Nemetz
7d ago
|
74 | |
| 75 | Retiring a bot revokes it immediately: its key stops resolving, and a queued job it | |
| 76 | was a runner for is no longer owed. Its name stays attached to its history, like | |
| 77 | anyone else's. | |
| 78 | ||
|
c14588ef
Keenan Nemetz
7d ago
|
79 | ## Profiles and the user directory |
|
a6b42433
Keenan Nemetz
8d ago
|
80 | |
|
b7396a11
Keenan Nemetz
8h ago
|
81 | Account names are public on an instance. `/-/u` lists everyone who has an account, |
| 82 | and `/-/u/<name>` is that person's profile: what they have opened, reviewed and | |
|
a6b42433
Keenan Nemetz
8d ago
|
83 | commented on, and the repositories that work is in. Retiring an account keeps its |
| 84 | page, marked as no longer active, because every closed issue and merged change | |
| 85 | still refers to the name. | |
| 86 | ||
|
4739664b
Keenan Nemetz
7d ago
|
87 | The directory is a table of every account and its visible counts. Search it by |
| 88 | name, narrow it to active or retired accounts, and order it from a column header | |
| 89 | — by name, repositories, issues, changes, reviews or joining date. The search, the | |
| 90 | filter, the order and the page are all in the URL, so a view can be linked and | |
| 91 | paging cannot change the question. | |
| 92 | ||
|
a6b42433
Keenan Nemetz
8d ago
|
93 | What a profile counts is not public. Every number and every event is limited to |
| 94 | the repositories the reader may open, so two people can see different totals for | |
| 95 | the same account, and a private repository's work contributes nothing to a | |
| 96 | stranger's view. The page says what its numbers cover instead of pretending they | |
| 97 | are the whole history. The same rule covers the counts beside a name in the | |
| 98 | directory. | |
| 99 | ||
| 100 | A profile is derived, not stored. The numbers and the feed are read from the web | |
| 101 | index (`data/index.db`), which is rebuilt from Git and from the CI records; no | |
| 102 | profile is written to `data/users.db`. Keys, passkeys, sessions and a person's | |
| 103 | instance role are not shown, with one exception: an instance admin sees the role | |
| 104 | on the profile, because they can already see it on the admin pages. Git commit | |
| 105 | authors are shown as text and never counted, because a commit's author is a name | |
| 106 | someone typed, not the credential that proves an account. | |
| 107 | ||
| 108 | Writing `@name` in an issue, a comment, a change request, or a release note links | |
| 109 | to that account's profile. It is the same mention syntax a workflow's `mentioned` | |
| 110 | filter evaluates ([Workflows](workflows.md)), so a name that reads as an address | |
| 111 | on the page is the name a job sees. A name that does not belong to an account | |
| 112 | stays plain text, and a mention inside a code sample is not a mention. | |
| 113 | ||
| 114 | Both pages ask crawlers to leave them out of an index. The names are public; a | |
| 115 | scraped roster is not what public has to mean. | |
| 116 | ||
|
6c84d387
Keenan Nemetz
16d ago
|
117 | Account data lives in `data/users.db`. It also holds passkeys, one-time |
| 118 | enrollment links, browser sessions, repository access rules, and file locks. | |
|
ab326d3a
Keenan Nemetz
14d ago
|
119 | Credentials are public keys, and session or enrollment-link tokens are stored |
| 120 | only as hashes. Vole reloads the roster every few seconds, so key changes and retirements | |
|
6c84d387
Keenan Nemetz
16d ago
|
121 | take effect without restarting the server. |
|
520470e1
Keenan Nemetz
16d ago
|
122 | |
| 123 | ```sh | |
|
2e024cc9
Keenan Nemetz
14d ago
|
124 | vole admin account create alice --key ~/.ssh/id_ed25519.pub |
| 125 | vole admin account create bob --key bob.pub | |
| 126 | vole admin account add-key alice --key ~/.ssh/id_ed25519_work.pub | |
| 127 | vole admin account list | |
| 128 | vole admin account rm-key alice SHA256:... | |
| 129 | vole admin account retire alice | |
| 130 | vole admin account backup backup/users.db | |
|
520470e1
Keenan Nemetz
16d ago
|
131 | ``` |
| 132 | ||
|
2e024cc9
Keenan Nemetz
14d ago
|
133 | `account create --key` writes the account and its first SSH key together. Without |
|
6c84d387
Keenan Nemetz
16d ago
|
134 | `--key` (and with no key on stdin), it creates a browser-only account and prints a |
|
2e024cc9
Keenan Nemetz
14d ago
|
135 | one-time enrollment link. `account invite` issues a fresh link for an existing |
| 136 | account. `account add-key` adds another SSH key; use it to rotate credentials | |
|
62576fa8
Keenan Nemetz
14d ago
|
137 | without changing the account that access rules and approvals refer to. There is no |
|
474a812d
Keenan Nemetz
16d ago
|
138 | command to change the account name. |
|
6c84d387
Keenan Nemetz
16d ago
|
139 | |
| 140 | ## Browser sign-in | |
| 141 | ||
|
ab326d3a
Keenan Nemetz
14d ago
|
142 | Browser sign-in uses a passkey (Touch ID, Windows Hello, a phone, or a security |
|
6c84d387
Keenan Nemetz
16d ago
|
143 | key). It requires `public_url` to be an `https://` URL; Vole refuses to start a |
| 144 | WebAuthn ceremony without a canonical secure origin. | |
| 145 | ||
|
ab326d3a
Keenan Nemetz
14d ago
|
146 | 1. An instance admin creates the account and gives the person its one-time |
|
f920a9f1
Keenan Nemetz
16d ago
|
147 | enrollment link, or the server has `open_registration = true` and the person |
|
b7396a11
Keenan Nemetz
8h ago
|
148 | creates the account at `/-/register`. |
|
f920a9f1
Keenan Nemetz
16d ago
|
149 | 2. The person registers a passkey. |
|
6c84d387
Keenan Nemetz
16d ago
|
150 | 3. The passkey creates a browser session. The session cookie is HttpOnly and |
| 151 | Secure; Vole stores only its hash. | |
|
7d7718fa
Keenan Nemetz
6h ago
|
152 | 4. From **Account**, the person can register another passkey, rename or remove the |
| 153 | ones they have, add or remove SSH keys, or review their sessions. A label is | |
| 154 | display text only: the credential is what signs in, so renaming one is not a | |
| 155 | re-registration. Removing a passkey cannot be undone, and the last one is | |
| 156 | refused while it is the account's only way in — an SSH key counts as a way in | |
| 157 | for Git, so an account with one loses only its browser sign-in. Otherwise the | |
| 158 | person would need an admin to invite them back. | |
|
6c84d387
Keenan Nemetz
16d ago
|
159 | |
|
ad79b7ab
Keenan Nemetz
3d ago
|
160 | The account field opts into WebAuthn conditional UI (`autocomplete="username |
| 161 | webauthn"`), so a supporting browser can offer a saved passkey while the field is | |
| 162 | focused. The username-first form remains for browsers without autofill and for a | |
| 163 | passkey created before discoverable credentials were required. New passkeys are | |
| 164 | registered as discoverable credentials (resident keys) and require user | |
| 165 | verification; that is what lets the browser find one without being told which | |
| 166 | account it belongs to. | |
| 167 | ||
|
6c84d387
Keenan Nemetz
16d ago
|
168 | A link is single-use and expires. Issuing a new link invalidates the previous |
|
ab326d3a
Keenan Nemetz
14d ago
|
169 | one. If the person loses every passkey, an instance admin issues a new link; there |
|
6c84d387
Keenan Nemetz
16d ago
|
170 | is no password reset to phish. Retiring an account deletes its sessions |
|
7d7718fa
Keenan Nemetz
6h ago
|
171 | immediately, and removing a passkey deletes the sessions that passkey created, |
| 172 | including the one the removal was made from. | |
| 173 | ||
| 174 | Both writes are audited: `identity.credential-rename` names the passkey and its new | |
| 175 | label, and `identity.credential-remove` names the passkey, with the account as the | |
| 176 | subject. The account page's own record of the write is `web.passkey.rename` and | |
| 177 | `web.passkey.remove`, the same way `web.key.add` and `web.key.remove` sit beside the | |
| 178 | identity actions for SSH keys. | |
|
6c84d387
Keenan Nemetz
16d ago
|
179 | |
|
b7396a11
Keenan Nemetz
8h ago
|
180 | The link carries its token in the URL fragment — `https://forge.example.com/-/invite#<token>` |
|
edf9db6e
Keenan Nemetz
13d ago
|
181 | — so the browser never sends it to the server, a proxy's access log, or the |
|
b7396a11
Keenan Nemetz
8h ago
|
182 | referrer of anything the page loads. Links issued in the older `/-/invite/<token>` |
|
edf9db6e
Keenan Nemetz
13d ago
|
183 | shape still work: Vole redirects them into the fragment, and its own logs redact |
| 184 | the old path. | |
| 185 | ||
| 186 | Sign-in, registration, enrollment and passkey-adding are rate-limited per client | |
| 187 | address (and per account name for sign-in), and the ceremony store is bounded and | |
| 188 | expires after five minutes. A client that exceeds a budget gets `429` with | |
| 189 | `Retry-After` rather than a slower answer. Behind a proxy, set | |
| 190 | `git_http_client_ip_header` so those budgets follow the real client instead of | |
| 191 | the proxy. | |
| 192 | ||
| 193 | An account name is not proof of anything: asking to sign in as an account that | |
| 194 | does not exist, is retired, or has no passkey gets the same assertion request as | |
| 195 | a real one. The difference only appears when the ceremony is finished, and the | |
| 196 | failure is generic. | |
| 197 | ||
| 198 | A passkey's signature counter must advance. If an assertion arrives with a | |
| 199 | counter that did not move, two copies of the key probably exist, so the sign-in | |
| 200 | is refused and the event is recorded in the audit log as | |
| 201 | `auth.clone-warning`. This can lock out an authenticator with a broken counter | |
| 202 | until an admin removes the passkey; that is the deliberate trade. Synced | |
| 203 | passkeys that always report zero are handled by WebAuthn's own rule and do not | |
| 204 | trigger the refusal. | |
| 205 | ||
|
b7396a11
Keenan Nemetz
8h ago
|
206 | With `open_registration = true`, `/-/register` creates a new account and its first |
|
ab326d3a
Keenan Nemetz
14d ago
|
207 | passkey without an instance admin. Set `open_registration_role` to the |
|
5b814e99
Keenan Nemetz
14d ago
|
208 | instance-wide role those accounts should have; `contribute` is the usual choice |
| 209 | for an open-source instance: | |
|
f920a9f1
Keenan Nemetz
16d ago
|
210 | |
|
5b814e99
Keenan Nemetz
14d ago
|
211 | ```toml |
| 212 | open_registration = true | |
| 213 | open_registration_role = "contribute" | |
|
f920a9f1
Keenan Nemetz
16d ago
|
214 | ``` |
| 215 | ||
|
5b814e99
Keenan Nemetz
14d ago
|
216 | That role applies to every **public** repository, so a new contributor can read, |
| 217 | discuss, and push to `refs/heads/contrib/<name>/` without being added to each | |
|
9e8cc155
Keenan Nemetz
13d ago
|
218 | repository. It does not open private repositories. Only `none`, `read` and |
| 219 | `contribute` are accepted: a stranger who typed a name must not be able to move a | |
| 220 | shared branch, approve a review or administer the instance, so `maintain` and | |
| 221 | `admin` are refused and have to be granted explicitly. A registered account can | |
| 222 | add its own SSH key from **Account**, which is what lets it push. Open | |
| 223 | registration has no email verification and is rate-limited per address, so leave | |
| 224 | it off on a private instance unless you want that policy. | |
|
f920a9f1
Keenan Nemetz
16d ago
|
225 | |
|
6c84d387
Keenan Nemetz
16d ago
|
226 | Admins can inspect and revoke sessions: |
| 227 | ||
| 228 | ```sh | |
|
2e024cc9
Keenan Nemetz
14d ago
|
229 | vole admin account credentials alice |
| 230 | vole admin account rm-credential alice <credential-id> | |
| 231 | vole admin account sessions alice | |
| 232 | vole admin account revoke-sessions alice | |
| 233 | vole admin account revoke-sessions --all | |
|
6c84d387
Keenan Nemetz
16d ago
|
234 | ``` |
| 235 | ||
| 236 | `revoke-sessions --all` is the recovery step after restoring an old `users.db`. | |
|
c36d5073
Keenan Nemetz
16d ago
|
237 | |
|
520470e1
Keenan Nemetz
16d ago
|
238 | To see which account the current remote's key belongs to: |
| 239 | ||
| 240 | ```sh | |
| 241 | vole whoami | |
| 242 | ``` | |
| 243 | ||
|
eec0e603
Keenan Nemetz
6d ago
|
244 | To see the role that account holds on the repository named by the remote: |
| 245 | ||
| 246 | ```sh | |
| 247 | vole whoami --role | |
| 248 | ``` | |
| 249 | ||
|
3abbe749
Keenan Nemetz
16d ago
|
250 | ## Remote administration |
| 251 | ||
| 252 | By default, server management commands run on the host. To use them from another | |
| 253 | machine, mark an account as an **instance admin** and pass `--remote`: | |
| 254 | ||
| 255 | ```sh | |
|
2e024cc9
Keenan Nemetz
14d ago
|
256 | vole admin account grant-admin alice |
| 257 | vole admin --remote ssh://git@forge.example.com:2222 account create bob --key bob.pub | |
|
3abbe749
Keenan Nemetz
16d ago
|
258 | vole admin --remote origin repo create demo --owner bob |
|
2e024cc9
Keenan Nemetz
14d ago
|
259 | vole admin --remote origin acl set-default-role demo read |
|
3abbe749
Keenan Nemetz
16d ago
|
260 | ``` |
| 261 | ||
|
ab326d3a
Keenan Nemetz
14d ago
|
262 | An instance admin is the instance-wide `admin` role. They can create accounts and |
|
5b814e99
Keenan Nemetz
14d ago
|
263 | repositories, change access rules anywhere, and use `--remote`. They are also a |
| 264 | repository admin on every repository, private ones included: they can push, | |
| 265 | change settings, and manage access without a repository-specific entry. SSH Git | |
| 266 | and SSH LFS use the key's account for this check; anonymous HTTP clone has no | |
| 267 | identity, so a private repository still answers `404` there. | |
|
381d6aac
Keenan Nemetz
14d ago
|
268 | |
|
43e681d8
Keenan Nemetz
5d ago
|
269 | Instance admins also get an **Admin** page in the top navigation. Its tabs cover |
|
ea21f923
Keenan Nemetz
5d ago
|
270 | the instance's overview, storage, runners, and effective configuration; an |
| 271 | Accounts tab that creates a person from an enrollment link or an automation | |
| 272 | account; and the audit log. Existing accounts are managed from the users | |
|
b7396a11
Keenan Nemetz
8h ago
|
273 | directory at `/-/u`, which is searchable and paginated: an instance admin gets a |
|
ea21f923
Keenan Nemetz
5d ago
|
274 | **Manage** action on each row that opens the account page, where they set its |
| 275 | instance-wide role (including `admin`), retire it, start a fresh enrollment link, | |
| 276 | and inspect or revoke SSH keys and browser sessions. Repository creation is a | |
| 277 | **New repository** action on the home page, and per-repository access rules live | |
| 278 | on that repository's **Settings** tab, visible there to instance admins. | |
|
395554f4
Keenan Nemetz
15d ago
|
279 | |
|
5b814e99
Keenan Nemetz
14d ago
|
280 | The first instance admin must be granted on the host. Instance admin is the |
|
2e024cc9
Keenan Nemetz
14d ago
|
281 | instance-wide `admin` role, so `vole admin account role alice admin` and |
| 282 | `vole admin account grant-admin alice` mean the same thing. Vole refuses to remove or | |
|
3abbe749
Keenan Nemetz
16d ago
|
283 | retire the last active instance admin, so a remote mistake cannot lock everyone |
| 284 | out. Add `--json` to any remote command for a machine-readable response: | |
| 285 | ||
| 286 | ```sh | |
|
2e024cc9
Keenan Nemetz
14d ago
|
287 | vole admin --remote origin --json account list |
|
3abbe749
Keenan Nemetz
16d ago
|
288 | ``` |
| 289 | ||
| 290 | Backups, garbage collection, and index rebuilds stay on the host; they are | |
| 291 | recovery operations, not remote administration. | |
| 292 | ||
|
520470e1
Keenan Nemetz
16d ago
|
293 | ## Roles |
| 294 | ||
|
5b814e99
Keenan Nemetz
14d ago
|
295 | A role can be granted in two places: |
| 296 | ||
| 297 | - **instance-wide**, on the account: it applies to every public repository; | |
| 298 | - **per repository**: an explicit entry for that repository. | |
| 299 | ||
| 300 | The effective role is the higher of the repository's default role, the | |
| 301 | repository entry for the account, and the account's instance-wide role. Instance | |
| 302 | roles only take effect on public repositories. The exception is `admin`: an | |
| 303 | instance admin is an admin on every repository, including private ones. | |
| 304 | ||
| 305 | Each repository also has a default role for everyone and optional roles for | |
| 306 | specific accounts. | |
|
520470e1
Keenan Nemetz
16d ago
|
307 | |
| 308 | | Role | What it allows | | |
| 309 | |---|---| | |
| 310 | | `none` | No access | | |
| 311 | | `read` | Clone, fetch, and browse the repository | | |
|
5b814e99
Keenan Nemetz
14d ago
|
312 | | `contribute` | Read, file issues and comments, push only `refs/heads/contrib/<name>/*`, and open change requests from those branches | |
|
0c40a1d3
Keenan Nemetz
7d ago
|
313 | | `maintain` | Everything the contribute role can do, plus push shared branches, triage issues, approve change requests, and manage the repository's secrets | |
|
6c84d387
Keenan Nemetz
16d ago
|
314 | | `admin` | Everything a maintainer can do, plus manage access, push protected branches, override locks, and repair metadata | |
|
520470e1
Keenan Nemetz
16d ago
|
315 | |
|
62b59a76
Keenan Nemetz
13d ago
|
316 | Rules are edited with a compare-and-swap. A page or a command that read the rules |
| 317 | before somebody else changed them is refused rather than allowed to write its copy | |
| 318 | back, so an instance admin revoking a role cannot be undone by a repository admin | |
| 319 | who still has the older page open. The operations that own one field — the | |
| 320 | visibility toggle, adding one account, one runners entry — re-read the row and | |
| 321 | apply only that field, so an unrelated concurrent change is kept. | |
| 322 | ||
|
520470e1
Keenan Nemetz
16d ago
|
323 | A new repository is private. Its default role is `none`, and its owner is an |
| 324 | admin. Private repositories return `404` to anonymous visitors, so people cannot | |
|
5b814e99
Keenan Nemetz
14d ago
|
325 | tell whether they exist. An instance-wide `read`, `contribute`, or `maintain` |
| 326 | grant does not open a private repository; an instance admin does. | |
|
520470e1
Keenan Nemetz
16d ago
|
327 | |
| 328 | To publish a repository, give the default role `read`: | |
| 329 | ||
| 330 | ```sh | |
|
2e024cc9
Keenan Nemetz
14d ago
|
331 | vole admin acl set-default-role demo read |
|
520470e1
Keenan Nemetz
16d ago
|
332 | ``` |
| 333 | ||
|
1948358f
Keenan Nemetz
15d ago
|
334 | A repository admin can also change the description, default branch, and |
|
2973f727
Keenan Nemetz
15d ago
|
335 | visibility, and rename or delete the repository, from its **Settings** tab in |
|
5b814e99
Keenan Nemetz
14d ago
|
336 | the browser. An instance admin has those rights on every repository too. |
|
e189a23e
Keenan Nemetz
1d ago
|
337 | Settings is split into tabs: **General** (the repository's own fields), |
| 338 | **Access** (the instance-admin rules), **Secrets**, and **Danger** (rename and | |
| 339 | delete). Each tab is offered only to the accounts that may use it, and a viewer | |
| 340 | who can use one of them sees no row at all. Visibility | |
|
1948358f
Keenan Nemetz
15d ago
|
341 | toggles the default role between `none` and `read`; explicit role entries are |
|
5b814e99
Keenan Nemetz
14d ago
|
342 | left alone. The Accounts table shows only accounts with an explicit role or |
| 343 | runner entry; use **Add account** below it to grant another account a | |
| 344 | repository-specific role. Access rules and review policy remain instance-admin | |
|
e189a23e
Keenan Nemetz
1d ago
|
345 | operations: instance admins can edit them on the repository's **Access** tab, |
|
5b814e99
Keenan Nemetz
14d ago
|
346 | or with `vole admin acl`. |
|
1948358f
Keenan Nemetz
15d ago
|
347 | |
|
e189a23e
Keenan Nemetz
1d ago
|
348 | A maintainer who is not an admin also opens the page, and its one tab is |
| 349 | Secrets. Values a repository keeps out of its code — a deploy key, a | |
|
0c40a1d3
Keenan Nemetz
7d ago
|
350 | registry token, a staging password — belong to the accounts that approve work and allow CI, |
| 351 | so the page shows them the names, versions and read times, and the form that sets | |
| 352 | them. It never shows a value; `vole secret get` prints one when somebody needs it. | |
| 353 | ||
|
520470e1
Keenan Nemetz
16d ago
|
354 | ## Changing access |
| 355 | ||
| 356 | ```sh | |
|
2e024cc9
Keenan Nemetz
14d ago
|
357 | vole admin account create carol --role contribute --key carol.pub |
| 358 | vole admin account role alice maintain | |
| 359 | vole admin account role bob none | |
|
5b814e99
Keenan Nemetz
14d ago
|
360 | |
|
3abbe749
Keenan Nemetz
16d ago
|
361 | vole admin acl show demo |
|
2e024cc9
Keenan Nemetz
14d ago
|
362 | vole admin acl set-role demo alice maintain |
| 363 | vole admin acl set-role demo bob contribute | |
| 364 | vole admin acl set-role demo carol read | |
| 365 | vole admin acl set-default-role demo read | |
| 366 | vole admin acl protect add demo refs/heads/main | |
| 367 | vole admin acl set-approvals demo 2 | |
| 368 | vole admin acl checks enable demo | |
|
0789f22e
Keenan Nemetz
9d ago
|
369 | vole admin acl require-issues enable demo |
|
2e024cc9
Keenan Nemetz
14d ago
|
370 | vole admin acl runners add demo ci-runner-1 |
|
520470e1
Keenan Nemetz
16d ago
|
371 | ``` |
| 372 | ||
|
2e024cc9
Keenan Nemetz
14d ago
|
373 | - `account create --role` and `account role` set the account's instance-wide role. |
|
5b814e99
Keenan Nemetz
14d ago
|
374 | On a public repository that role applies without a per-repository entry; on a |
| 375 | private repository it does not, unless the role is `admin`. | |
|
2e024cc9
Keenan Nemetz
14d ago
|
376 | - `set-role` gives one account a role in one repository. The roles are `none`, |
|
5b814e99
Keenan Nemetz
14d ago
|
377 | `read`, `contribute`, `maintain` and `admin`. `contributor` and `contrib` are |
| 378 | accepted as aliases for `contribute`; `write` is accepted as the historical | |
| 379 | spelling of `maintain`. A repository entry can raise the instance-wide role but | |
|
2e024cc9
Keenan Nemetz
14d ago
|
380 | cannot lower it. Use `none` to remove the entry. |
| 381 | - `set-default-role` changes the role for everyone else. `read` or higher makes | |
| 382 | the repository public. | |
| 383 | - `protect` manages the protected branch patterns. `set` replaces the list, | |
| 384 | `add` and `remove` change it one entry at a time, `clear` empties it, and | |
|
93c7cae8
Keenan Nemetz
23h ago
|
385 | `list` shows it. A pattern is an exact ref (`refs/heads/main`) or a prefix |
| 386 | ending in one `*` (`refs/heads/release/*`); `add` and `set` refuse anything | |
| 387 | else, because a pattern the hook can never match would leave the branch open | |
| 388 | while `acl show` listed it as protected. Admins can still push to protected | |
| 389 | branches directly. | |
|
2e024cc9
Keenan Nemetz
14d ago
|
390 | - `set-approvals` sets how many approvals a protected branch needs. `0` means |
|
520470e1
Keenan Nemetz
16d ago
|
391 | admin-only. |
|
2e024cc9
Keenan Nemetz
14d ago
|
392 | - `checks enable` requires CI checks to pass before a protected branch can be |
| 393 | updated; `checks disable` allows approvals alone to land it. | |
|
0789f22e
Keenan Nemetz
9d ago
|
394 | - `require-issues enable` requires at least one linked issue when a new change |
| 395 | request is opened; `disable` makes it optional again. The rule is checked when | |
| 396 | the request is opened, so requests that already exist keep their history and | |
| 397 | can still be approved and landed. | |
|
2e024cc9
Keenan Nemetz
14d ago
|
398 | - `runners` manages the accounts whose CI results this repository trusts. It uses |
| 399 | the same `list`, `add`, `remove`, `set`, and `clear` verbs as `protect`. | |
|
62b59a76
Keenan Nemetz
13d ago
|
400 | Admins always count. A retired account stops counting even while its name is |
| 401 | still in the list: the ACL row outlives the account, so membership alone is not | |
| 402 | a grant that lasts. See | |
|
2e024cc9
Keenan Nemetz
14d ago
|
403 | [Runners](runner.md#who-can-run-jobs-and-report-results). |
|
520470e1
Keenan Nemetz
16d ago
|
404 | |
|
6c84d387
Keenan Nemetz
16d ago
|
405 | ## Contributors and their branches |
| 406 | ||
|
ab326d3a
Keenan Nemetz
14d ago
|
407 | The `contribute` role is for open-source repositories that accept outside patches. |
|
7e0b5950
Keenan Nemetz
6d ago
|
408 | A contributor can read, file issues and comments, manage the items it wrote, and |
| 409 | open a change request from a branch it may push — through the CLI or the browser, | |
| 410 | which apply the same rules. Their Git writes are confined to a branch folder named | |
| 411 | after their account name: | |
|
6c84d387
Keenan Nemetz
16d ago
|
412 | |
| 413 | ```text | |
|
474a812d
Keenan Nemetz
16d ago
|
414 | refs/heads/contrib/<name>/<branch> |
|
6c84d387
Keenan Nemetz
16d ago
|
415 | ``` |
| 416 | ||
|
474a812d
Keenan Nemetz
16d ago
|
417 | For example, account `alice` may push `refs/heads/contrib/alice/fix-docs`. They |
|
7e0b5950
Keenan Nemetz
6d ago
|
418 | cannot push `refs/heads/main`, another account's folder, or tags. The pre-receive |
| 419 | hook checks the namespace, and rebasing or deleting a branch inside their own | |
| 420 | folder is allowed because it is not shared history. | |
| 421 | ||
| 422 | They may also write `refs/heads/vole`, the issues branch, because that is where a | |
| 423 | comment, an issue, or a change request lives. What the push may contain is the | |
| 424 | same list the browser offers: a comment, an issue, a change request from their own | |
| 425 | branch, or a change to an item they wrote. Pre-receive refuses anything else, so | |
| 426 | the two transports cannot disagree about what a contributor may do. | |
|
474a812d
Keenan Nemetz
16d ago
|
427 | |
|
62576fa8
Keenan Nemetz
14d ago
|
428 | The namespace is the account name, so a ref says whose work it is without a |
| 429 | lookup. It is also why names are fixed at creation: renaming an account would | |
| 430 | otherwise orphan every branch under its old folder. | |
|
6c84d387
Keenan Nemetz
16d ago
|
431 | |
| 432 | The browser's **Open change request** form lists only the branches the account | |
| 433 | may use as a head. The head commit is resolved from that branch; a contributor | |
| 434 | cannot name a commit they did not push. A maintainer can approve the request, and | |
| 435 | landing it still goes through the same protected-branch gate. | |
| 436 | ||
|
04e00cfc
Keenan Nemetz
16d ago
|
437 | The CLI's `vole issue new`, `vole issue comment` and `vole change new` write the |
|
a781cd9b
Keenan Nemetz
6d ago
|
438 | metadata branch directly. Pre-receive checks the same per-path rules as the |
| 439 | browser, so a `contribute` account can use those commands too: it may file an | |
| 440 | issue, comment, and open a change from a branch in its own namespace. | |
|
6c84d387
Keenan Nemetz
16d ago
|
441 | |
| 442 | Contributor branches are ordinary branches except for one rule: CI does not run | |
|
564acd4b
Keenan Nemetz
15d ago
|
443 | for them automatically. A maintainer or admin opens the change request for the |
|
f6f4b1db
Keenan Nemetz
15d ago
|
444 | branch and chooses **Allow CI** there. The approval names the exact |
|
564acd4b
Keenan Nemetz
15d ago
|
445 | commit, so every new push waits again. Treat runner isolation as part of the trust decision before granting |
|
ab326d3a
Keenan Nemetz
14d ago
|
446 | `contribute` on a public repository. |
|
6c84d387
Keenan Nemetz
16d ago
|
447 | |
|
4ac42680
Keenan Nemetz
7d ago
|
448 | Allowing CI also grants that commit the repository's secrets, if its workflow |
| 449 | declares them. It is the same decision as letting the code run on a runner: the | |
| 450 | values arrive on the machine that runs it, and any step in that job can read the | |
| 451 | ones the job declared. A maintainer who would not hand a value to an outside patch | |
| 452 | should not approve a tip whose workflow asks for it. | |
| 453 | ||
|
520470e1
Keenan Nemetz
16d ago
|
454 | ## When rules are checked |
| 455 | ||
| 456 | Every rule is checked before Vole accepts a push. If a push breaks a rule, none of | |
|
6c84d387
Keenan Nemetz
16d ago
|
457 | its branches move. Browser issue, comment, and approval writes check the same ACL |
| 458 | capability and use the same metadata writers and validation as the CLI. | |
|
520470e1
Keenan Nemetz
16d ago
|
459 | |
| 460 | That includes protected branches, approvals, required checks, file size limits, | |
| 461 | locks, issue formatting, and the CI runner allowlist. Privileged actions — such as | |
|
ab326d3a
Keenan Nemetz
14d ago
|
462 | an admin force-push, a lock override, an account change, or a merge through review — |
|
43e681d8
Keenan Nemetz
5d ago
|
463 | are written to `data/audit.log` and can be read by an instance admin at |
| 464 | **Admin → Audit log**. | |
|
520470e1
Keenan Nemetz
16d ago
|
465 | |
| 466 | For the reasoning behind this design, see | |
|
6c84d387
Keenan Nemetz
16d ago
|
467 | [Architecture](architecture.md#trust-a-credential-resolves-to-an-account). For file locks, see |
|
520470e1
Keenan Nemetz
16d ago
|
468 | [LFS § Locking](lfs.md#locking-files). |