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.
| 1 | # CLI reference |
| 2 | |
| 3 | The command line is Vole's admin interface. That is deliberate: if you can run |
| 4 | Vole on the machine, you are the operator. There is no separate admin panel to |
| 5 | secure or keep in sync. |
| 6 | |
| 7 | Run `vole help` for the built-in summary. If you make a usage mistake, Vole exits |
| 8 | with code `129`, just like Git. |
| 9 | |
| 10 | ## Common options |
| 11 | |
| 12 | | Option | Used by | Meaning | |
| 13 | |---|---|---| |
| 14 | | `--config <path>` | Local server management | Use a different `vole.toml` (default: `./vole.toml`, then `~/.vole/vole.toml`; `VOLE_CONFIG` overrides both) | |
| 15 | | `--remote <name\|url>` | `admin`, issue, change, release, and pages commands | Git remote or instance SSH URL to talk to | |
| 16 | | `--json` | `admin --remote` | Print the server's JSON response | |
| 17 | | `--no-push` | `issue`, `change` | Commit locally without pushing | |
| 18 | | `--dir <path>` | `init`, `workflow`, `runner` | Working or checkout directory | |
| 19 | | `--branch <name>` | `pages` | Site branch (default `pages`) | |
| 20 | | `--format <fmt>` | `issue list`, `change list` | Output fields such as `%(id)`, `%(number)` and `%(title)`, or `json` | |
| 21 | | `-h`, `--help` | All | Print usage | |
| 22 | |
| 23 | Options follow Git's conventions: `--long value`, `--long=value`, `-o value`, |
| 24 | `-ovalue`, bundled short options such as `-am`, repeated options, and `--` to |
| 25 | end option parsing. Options may appear before or after arguments. `vole help |
| 26 | <command>` and `vole <command> -h` print that command's usage. |
| 27 | |
| 28 | ## Secrets |
| 29 | |
| 30 | A repository's secrets are the values its maintainers share, stored on the |
| 31 | instance, encrypted, and never in the repository: |
| 32 | |
| 33 | ```sh |
| 34 | vole secret list <repo> |
| 35 | vole secret set <repo> <NAME> # the value is read from stdin |
| 36 | vole secret set <repo> <NAME> --from-file key.pem |
| 37 | vole secret get <repo> <NAME> # the value, and nothing else |
| 38 | vole secret rm <repo> <NAME> |
| 39 | ``` |
| 40 | |
| 41 | `set` reads the value from standard input or a file and never from the command |
| 42 | line: an argument is visible to every process on the host and lands in shell |
| 43 | history. On a terminal it prompts without echoing; from a pipe it removes one |
| 44 | trailing newline, so `echo "$TOKEN" | vole secret set demo DEPLOY_KEY` stores the |
| 45 | token and not the line ending. `get` writes the value with no newline added, so it |
| 46 | can be redirected to a file. |
| 47 | |
| 48 | `set` needs the maintainer role in that repository, and every read and change is |
| 49 | recorded in `data/audit.log`. `list` shows names, versions, who last changed one, |
| 50 | and when a value was last read — never a value. The same values appear on the |
| 51 | repository's **Settings** tab, which is write-only: a saved value is not shown |
| 52 | again. |
| 53 | |
| 54 | Names are upper-case letters, digits and `_`, so a name can be read as an |
| 55 | environment variable. A value is limited to 128 KiB, the most one environment |
| 56 | variable can carry. |
| 57 | |
| 58 | ## Instance |
| 59 | |
| 60 | ```sh |
| 61 | vole admin init [--dir <path> | --config <path>] |
| 62 | vole serve [--config <path>] [--log debug] |
| 63 | vole version |
| 64 | ``` |
| 65 | |
| 66 | ## Server management |
| 67 | |
| 68 | `vole admin` is the single namespace for account, repository (`repo`), and access |
| 69 | (`acl`) management. |
| 70 | Without `--remote`, it changes the instance named by the local `vole.toml`. With |
| 71 | `--remote`, it sends the same command over SSH and must be run by an instance |
| 72 | admin. |
| 73 | |
| 74 | ```sh |
| 75 | vole admin account create alice --key ~/.ssh/id_ed25519.pub |
| 76 | vole admin --config /srv/vole/vole.toml account list |
| 77 | vole admin --remote origin account create bob --key bob.pub |
| 78 | vole admin --remote ssh://git@forge.example.com:2222 account list |
| 79 | ``` |
| 80 | |
| 81 | `--config` names a `vole.toml` other than the one discovery finds, and may sit on |
| 82 | either side of the command (`vole admin --config P account list` and `vole admin |
| 83 | account list --config P` are the same). It is local only: with `--remote` the |
| 84 | server's own config is the one that applies. |
| 85 | |
| 86 | See [Accounts and access](accounts-and-access.md#remote-administration). |
| 87 | |
| 88 | `vole admin account` is the documented spelling. The old `vole admin user` form |
| 89 | and the pre-admin aliases (`vole account`, `vole user`, `vole repo`, `vole acl`, |
| 90 | `vole index`, and `vole gc`) remain for compatibility; documentation and help use |
| 91 | `vole admin account`. |
| 92 | |
| 93 | ### Accounts |
| 94 | |
| 95 | The `account` commands manage accounts. |
| 96 | |
| 97 | ```sh |
| 98 | vole admin account create [--key <file|->] [--role <role>] [--bot] <name> |
| 99 | vole admin account invite <name> |
| 100 | vole admin account credentials <name> |
| 101 | vole admin account rm-credential <name> <credential-id> |
| 102 | vole admin account sessions <name> |
| 103 | vole admin account revoke-sessions [--all | <name>] |
| 104 | vole admin account add-key --key <file|-> <name> |
| 105 | vole admin account rm-key <name> <fingerprint> |
| 106 | vole admin account retire <name> |
| 107 | vole admin account role <name> <none|read|contribute|maintain|admin> |
| 108 | vole admin account grant-admin <name> |
| 109 | vole admin account revoke-admin <name> |
| 110 | vole admin account show <name> |
| 111 | vole admin account list |
| 112 | vole admin account backup <path> |
| 113 | vole whoami [--remote <name>] [--role] |
| 114 | ``` |
| 115 | |
| 116 | Omitting `--key` creates a browser-only account and prints a one-time enrollment |
| 117 | link. `--bot` creates an automation account instead: it has no browser sign-in and |
| 118 | cannot be an instance admin, so it must be created with a key. `--role` sets the |
| 119 | account's instance-wide role; it applies to public repositories, while `admin` |
| 120 | applies to every repository. `account role` changes it later. `account invite` |
| 121 | issues a fresh link for an existing account. `account backup` writes a file on the |
| 122 | host, so it is local-only. |
| 123 | |
| 124 | The account name is the only identifier an account has. It cannot be changed: it |
| 125 | is the folder a `contribute` account's branches live under, so it is fixed at |
| 126 | creation and never reissued. |
| 127 | |
| 128 | `vole whoami` prints the account name. With `--role` it prints the effective role |
| 129 | on the repository named by the remote: the repository's default and per-account |
| 130 | rules, with the account's instance-wide role applied where the repository is |
| 131 | public. A private repository the account cannot read is reported as not found, |
| 132 | the same as a fetch. |
| 133 | |
| 134 | ### Repositories |
| 135 | |
| 136 | ```sh |
| 137 | vole admin repo create --owner <account> <name> [--description <text>] |
| 138 | vole admin repo list |
| 139 | vole admin repo show <name> |
| 140 | vole admin repo rename <old> <new> |
| 141 | vole admin repo delete <name> |
| 142 | ``` |
| 143 | |
| 144 | New repositories are private. The owner becomes the first admin. |
| 145 | |
| 146 | ### Access |
| 147 | |
| 148 | Instance-wide roles are account standing; repository rules are set with `acl`: |
| 149 | |
| 150 | ```sh |
| 151 | vole admin account role <name> <none|read|contribute|maintain|admin> |
| 152 | |
| 153 | vole admin acl show <repo> |
| 154 | vole admin acl set-role <repo> <account> <none|read|contribute|maintain|admin> |
| 155 | vole admin acl set-default-role <repo> <none|read|contribute|maintain|admin> |
| 156 | vole admin acl set-approvals <repo> <n> |
| 157 | |
| 158 | vole admin acl checks enable <repo> |
| 159 | vole admin acl checks disable <repo> |
| 160 | |
| 161 | vole admin acl require-issues enable <repo> |
| 162 | vole admin acl require-issues disable <repo> |
| 163 | |
| 164 | vole admin acl protect list <repo> |
| 165 | vole admin acl protect add <repo> <ref-pattern>... |
| 166 | vole admin acl protect remove <repo> <ref-pattern>... |
| 167 | vole admin acl protect set <repo> <ref-pattern>... |
| 168 | vole admin acl protect clear <repo> |
| 169 | |
| 170 | vole admin acl runners list <repo> |
| 171 | vole admin acl runners add <repo> <account>... |
| 172 | vole admin acl runners remove <repo> <account>... |
| 173 | vole admin acl runners set <repo> <account>... |
| 174 | vole admin acl runners clear <repo> |
| 175 | ``` |
| 176 | |
| 177 | `set-role` and `set-default-role` accept `none` to remove access. `protect set` |
| 178 | and `runners set` replace the whole list; the `add` and `remove` forms change one |
| 179 | entry at a time. `require-issues enable` makes a linked issue mandatory when a |
| 180 | new change request is opened. |
| 181 | |
| 182 | A protected ref pattern is either an exact ref (`refs/heads/main`) or a ref |
| 183 | prefix ending in one `*` (`refs/heads/release/*`), the two shapes the hook |
| 184 | understands. `protect add` and `protect set` refuse anything else, because a |
| 185 | pattern that can never match would leave the branch open while `acl show` listed |
| 186 | it as protected. A misspelled category is named in the refusal: |
| 187 | |
| 188 | ```console |
| 189 | $ vole admin acl protect add demo refs/head/main |
| 190 | vole: protected pattern "refs/head/main" cannot match any ref: did you mean "refs/heads/main"? |
| 191 | ``` |
| 192 | |
| 193 | See [Accounts and access](accounts-and-access.md) and |
| 194 | [Runners](runner.md#who-can-run-jobs-and-report-results). |
| 195 | |
| 196 | ### Maintenance |
| 197 | |
| 198 | ```sh |
| 199 | vole admin gc [--dry-run] [--grace <duration>] |
| 200 | vole admin index rebuild |
| 201 | ``` |
| 202 | |
| 203 | These are local-only. See [Operations](operations.md). |
| 204 | |
| 205 | ## Issues |
| 206 | |
| 207 | ```sh |
| 208 | vole issue new --title <title> [-m <text> | -F <file>] [-l <label>]... [-a <account>]... |
| 209 | vole issue comment <id> [-m <text> | -F <file>] [--reply-to <comment-id>] |
| 210 | vole issue edit-comment <id> <comment-id> [-m <text> | -F <file>] |
| 211 | vole issue rm-comment <id> <comment-id> |
| 212 | vole issue edit <id> [--title <title>] [-m <text> | -F <file>] [-l <label>]... [-a <account>]... [--unassign <account>]... |
| 213 | vole issue assign <id> <account>... |
| 214 | vole issue unassign <id> <account>... |
| 215 | vole issue close <id> |
| 216 | vole issue reopen <id> |
| 217 | vole issue list [--state open|closed|all] [-l <label>]... [--assignee <account>]... [--format <fmt>] |
| 218 | vole issue show <id> |
| 219 | ``` |
| 220 | |
| 221 | Ids and comment ids accept a unique prefix. See [Issues](issues.md). |
| 222 | |
| 223 | ## Change requests |
| 224 | |
| 225 | ```sh |
| 226 | vole change new --title <title> --base <branch> [--head <rev>] [--closes <issue>]... [-m <text> | -F <file>] |
| 227 | vole change list [--state open|closed|all] [--format <fmt>] |
| 228 | vole change show <id> |
| 229 | vole change comment <id> [-m <text> | -F <file>] [--path <file> --line <n> [--side new|old]] [--reply-to <comment-id>] |
| 230 | vole change edit-comment <id> <comment-id> [-m <text> | -F <file>] |
| 231 | vole change rm-comment <id> <comment-id> |
| 232 | vole change update <id> [--base <branch>] [--head <rev>] [--closes <issue>]... [--no-closes] |
| 233 | vole change approve <id> [--as <account>] |
| 234 | vole change merge <id> |
| 235 | vole change close <id> |
| 236 | vole change reopen <id> |
| 237 | ``` |
| 238 | |
| 239 | `change` also accepts `request`, `requests` and `cr` as compatibility aliases. An |
| 240 | approval is only accepted from the account it names. `--closes` takes a full |
| 241 | issue id (repeatable, comma lists accepted) and names the issues landing the |
| 242 | request closes; it replaces the list on `update`, and `--no-closes` clears it. |
| 243 | A request opened with `--head <branch>` follows that branch: a later push moves its |
| 244 | head and invalidates the old approvals and checks. `--head <commit>` (or a tag) |
| 245 | pins the request to that commit instead. `--base <branch>` on `update` retargets an |
| 246 | open request, which is how a stacked request moves onto `main` after the branch |
| 247 | below it lands; approvals named the old base and stop counting. See |
| 248 | [Change requests](change-requests.md). |
| 249 | |
| 250 | `change merge` lands the request. When the combined result is still building it |
| 251 | prints that the request will merge when the result passes, which is the same |
| 252 | "merge when ready" a click on the page arms. `change show` prints the combined |
| 253 | result and whether the landing is ready. |
| 254 | |
| 255 | ## Releases |
| 256 | |
| 257 | ```sh |
| 258 | vole release create <tag> (-m <text> | -F <file|->) |
| 259 | vole release attach <tag> <file>... [--remote <name>] |
| 260 | ``` |
| 261 | |
| 262 | `create` writes the annotated tag in the current repository; push it with |
| 263 | `git push --follow-tags`. See [Releases](releases.md). |
| 264 | |
| 265 | ## Pages |
| 266 | |
| 267 | ```sh |
| 268 | vole pages deploy [<dir>] [--branch <name>] [-m <text> | -F <file>] [--dry-run] [--remote <name>] |
| 269 | vole pages status [--branch <name>] [--remote <name>] |
| 270 | vole pages unpublish [--branch <name>] [--remote <name>] |
| 271 | ``` |
| 272 | |
| 273 | See [Sites](sites.md). |
| 274 | |
| 275 | ## CI |
| 276 | |
| 277 | ```sh |
| 278 | vole runner --remote <ssh-url> [--once] [--jobs <n>] [--dir <path>] [--repos <name>]... [--label <name>]... |
| 279 | [--container-runtime <name>] [--container-network none|bridge] |
| 280 | [--container-pull missing|always|never] [--container-user <uid:gid>] [--container-userns <mode>] |
| 281 | [--container-memory <size>] [--container-cpus <n>] [--container-pids <n>] |
| 282 | [--cache-size <size>] [--git-cache-size <size>] [--no-cache] |
| 283 | vole run list [--state <state>] [--branch <branch>] [--commit <sha>] [--limit <n>] |
| 284 | [--format <fmt>] [--remote <name|url>] |
| 285 | vole run show <id> [--remote <name|url>] |
| 286 | vole run logs <id> [--attempt <n>] [--step <n>] [--remote <name|url>] |
| 287 | vole run cancel <id> [--remote <name|url>] |
| 288 | vole run rerun <id> [--remote <name|url>] |
| 289 | vole workflow check [--dir <path>] |
| 290 | vole workflow list [--dir <path>] |
| 291 | vole workflow run [--dir <path>] [<workflow>...] [--job <id>]... |
| 292 | [--container-runtime <name>] |
| 293 | [--container-network none|bridge] [--container-pull missing|always|never] |
| 294 | [--container-user <uid:gid>] [--container-userns <mode>] |
| 295 | [--cache-size <size>] [--no-cache] |
| 296 | vole workflow dispatch --remote <name|url> --workflow <path> [--ref <branch>] |
| 297 | [--input name=value]... |
| 298 | ``` |
| 299 | |
| 300 | `--jobs` is how many jobs one runner process runs at once (default 1). Each is a |
| 301 | session of its own, so the container limits are per job and a machine running four |
| 302 | jobs needs room for four containers. Under `--once` the runner still starts `--jobs` |
| 303 | jobs, and exits when they all finish. |
| 304 | |
| 305 | `workflow run` executes steps in the working tree, in the job's image. It needs no |
| 306 | runner, but it does read the secrets its jobs declare from the instance the |
| 307 | checkout's `origin` names, using your key; a value it cannot read is left unset with |
| 308 | a note, and the values it did read are masked in the output. It needs a container |
| 309 | runtime; there is no host mode. Both it and |
| 310 | `vole runner` reuse a job cache (see [Runners § Caching](runner.md#caching)). A |
| 311 | runner also keeps a copy of every repository it builds and clones each job from it, |
| 312 | so the history crosses the network once rather than once per run (see |
| 313 | [Runners § Saved checkouts](runner.md#saved-checkouts)). See |
| 314 | [Workflows § Check and run a workflow locally](workflows.md#check-and-run-a-workflow-locally). |
| 315 | |
| 316 | `workflow dispatch` queues a manual run on the instance instead. The workflow must |
| 317 | declare `on.workflow-dispatch`; `--ref` defaults to the default branch, and each |
| 318 | `--input` is checked against the input's declared type, options, and default. Only |
| 319 | a maintainer or instance admin may dispatch. |
| 320 | |
| 321 | `run list`, `run show` and `run logs` are the CLI half of the runs page and a run's |
| 322 | log. The queue lives on the instance, so they read it over the key you push with; any |
| 323 | account that may read the repository may use them. `run list` defaults to the newest |
| 324 | 20 runs and narrows by status, branch or a commit id or prefix. `--format` understands |
| 325 | `%(id)`, `%(shortid)`, `%(status)`, `%(workflow)`, `%(job)`, `%(branch)`, `%(sha)`, |
| 326 | `%(trigger)`, `%(trigger_title)`, `%(exit)`, `%(attempts)`, `%(log)` and `%(created)`. |
| 327 | |
| 328 | `run show` prints the run's status, commit, trigger, steps with their exit codes and |
| 329 | durations, each attempt's log size, the published artifacts, and whether the result |
| 330 | counts as a check. The id is the 26-character one on the run page; `vole change show` |
| 331 | also lists the head's runs, and the combined result's runs when there is one, so a |
| 332 | reviewer can copy the failing id without opening the browser. |
| 333 | |
| 334 | `run logs` writes the bytes the runner wrote to stdout, with nothing added, so it can |
| 335 | be redirected or piped. It opens the attempt the run is on, or the newest logged |
| 336 | attempt once the run has finished; `--attempt` picks a retry and `--step` narrows the |
| 337 | output to one step's range inside it. A refusal goes to stderr, so a redirect produces |
| 338 | a log or an empty file. |
| 339 | |
| 340 | `run cancel` is the CLI counterpart of the cancel button on a run page. A queued run |
| 341 | is canceled outright, and a running one is asked to stop, with its runner stopping at |
| 342 | the next heartbeat. `run rerun` queues a finished run again with the same pinned job. |
| 343 | Only a maintainer or instance admin may do either, and `data/audit.log` records the |
| 344 | account, the repository, and the run (`run.cancel` or `run.rerun`). |
| 345 | |
| 346 | ## Internal |
| 347 | |
| 348 | `vole hook pre-receive|post-receive` is installed in repositories by Vole. It is |
| 349 | not intended to be run by hand. |
| 350 |