Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 55m ago 4b8c9e1d
source preview history blame raw 13.8 KiB

CLI reference

The command line is Vole’s admin interface. That is deliberate: if you can run Vole on the machine, you are the operator. There is no separate admin panel to secure or keep in sync.

Run vole help for the built-in summary. If you make a usage mistake, Vole exits with code 129, just like Git.

Common options

Option Used by Meaning
--config <path> Local server management Use a different vole.toml (default: ./vole.toml, then ~/.vole/vole.toml; VOLE_CONFIG overrides both)
--remote <name|url> admin, issue, change, release, and pages commands Git remote or instance SSH URL to talk to
--json admin --remote Print the server’s JSON response
--no-push issue, change Commit locally without pushing
--dir <path> init, workflow, runner Working or checkout directory
--branch <name> pages Site branch (default pages)
--format <fmt> issue list, change list Output fields such as %(id), %(number) and %(title), or json
-h, --help All Print usage

Options follow Git’s conventions: --long value, --long=value, -o value, -ovalue, bundled short options such as -am, repeated options, and -- to end option parsing. Options may appear before or after arguments. vole help <command> and vole <command> -h print that command’s usage.

Secrets

A repository’s secrets are the values its maintainers share, stored on the instance, encrypted, and never in the repository:

vole secret list <repo>
vole secret set <repo> <NAME>                 # the value is read from stdin
vole secret set <repo> <NAME> --from-file key.pem
vole secret get <repo> <NAME>                 # the value, and nothing else
vole secret rm <repo> <NAME>

set reads the value from standard input or a file and never from the command line: an argument is visible to every process on the host and lands in shell history. On a terminal it prompts without echoing; from a pipe it removes one trailing newline, so echo "$TOKEN" | vole secret set demo DEPLOY_KEY stores the token and not the line ending. get writes the value with no newline added, so it can be redirected to a file.

set needs the maintainer role in that repository, and every read and change is recorded in data/audit.log. list shows names, versions, who last changed one, and when a value was last read — never a value. The same values appear on the repository’s Settings tab, which is write-only: a saved value is not shown again.

Names are upper-case letters, digits and _, so a name can be read as an environment variable. A value is limited to 128 KiB, the most one environment variable can carry.

Instance

vole admin init [--dir <path> | --config <path>]
vole serve [--config <path>] [--log debug]
vole version

Server management

vole admin is the single namespace for account, repository (repo), and access (acl) management. Without --remote, it changes the instance named by the local vole.toml. With --remote, it sends the same command over SSH and must be run by an instance admin.

vole admin account create alice --key ~/.ssh/id_ed25519.pub
vole admin --config /srv/vole/vole.toml account list
vole admin --remote origin account create bob --key bob.pub
vole admin --remote ssh://git@forge.example.com:2222 account list

--config names a vole.toml other than the one discovery finds, and may sit on either side of the command (vole admin --config P account list and vole admin account list --config P are the same). It is local only: with --remote the server’s own config is the one that applies.

See Accounts and access.

vole admin account is the documented spelling. The old vole admin user form and the pre-admin aliases (vole account, vole user, vole repo, vole acl, vole index, and vole gc) remain for compatibility; documentation and help use vole admin account.

Accounts

The account commands manage accounts.

vole admin account create [--key <file|->] [--role <role>] [--bot] <name>
vole admin account invite <name>
vole admin account credentials <name>
vole admin account rm-credential <name> <credential-id>
vole admin account sessions <name>
vole admin account revoke-sessions [--all | <name>]
vole admin account add-key --key <file|-> <name>
vole admin account rm-key <name> <fingerprint>
vole admin account retire <name>
vole admin account role <name> <none|read|contribute|maintain|admin>
vole admin account grant-admin <name>
vole admin account revoke-admin <name>
vole admin account show <name>
vole admin account list
vole admin account backup <path>
vole whoami [--remote <name>] [--role]

Omitting --key creates a browser-only account and prints a one-time enrollment link. --bot creates an automation account instead: it has no browser sign-in and cannot be an instance admin, so it must be created with a key. --role sets the account’s instance-wide role; it applies to public repositories, while admin applies to every repository. account role changes it later. account invite issues a fresh link for an existing account. account backup writes a file on the host, so it is local-only.

The account name is the only identifier an account has. It cannot be changed: it is the folder a contribute account’s branches live under, so it is fixed at creation and never reissued.

vole whoami prints the account name. With --role it prints the effective role on the repository named by the remote: the repository’s default and per-account rules, with the account’s instance-wide role applied where the repository is public. A private repository the account cannot read is reported as not found, the same as a fetch.

Repositories

vole admin repo create --owner <account> <name> [--description <text>]
vole admin repo list
vole admin repo show <name>
vole admin repo rename <old> <new>
vole admin repo delete <name>

New repositories are private. The owner becomes the first admin.

Access

Instance-wide roles are account standing; repository rules are set with acl:

vole admin account role <name> <none|read|contribute|maintain|admin>

vole admin acl show <repo>
vole admin acl set-role <repo> <account> <none|read|contribute|maintain|admin>
vole admin acl set-default-role <repo> <none|read|contribute|maintain|admin>
vole admin acl set-approvals <repo> <n>

vole admin acl checks enable <repo>
vole admin acl checks disable <repo>

vole admin acl require-issues enable <repo>
vole admin acl require-issues disable <repo>

vole admin acl protect list <repo>
vole admin acl protect add <repo> <ref-pattern>...
vole admin acl protect remove <repo> <ref-pattern>...
vole admin acl protect set <repo> <ref-pattern>...
vole admin acl protect clear <repo>

vole admin acl runners list <repo>
vole admin acl runners add <repo> <account>...
vole admin acl runners remove <repo> <account>...
vole admin acl runners set <repo> <account>...
vole admin acl runners clear <repo>

set-role and set-default-role accept none to remove access. protect set and runners set replace the whole list; the add and remove forms change one entry at a time. require-issues enable makes a linked issue mandatory when a new change request is opened.

A protected ref pattern is either an exact ref (refs/heads/main) or a ref prefix ending in one * (refs/heads/release/*), the two shapes the hook understands. protect add and protect set refuse anything else, because a pattern that can never match would leave the branch open while acl show listed it as protected. A misspelled category is named in the refusal:

$ vole admin acl protect add demo refs/head/main
vole: protected pattern "refs/head/main" cannot match any ref: did you mean "refs/heads/main"?

See Accounts and access and Runners.

Maintenance

vole admin gc [--dry-run] [--grace <duration>]
vole admin index rebuild

These are local-only. See Operations.

Issues

vole issue new --title <title> [-m <text> | -F <file>] [-l <label>]... [-a <account>]...
vole issue comment <id> [-m <text> | -F <file>] [--reply-to <comment-id>]
vole issue edit-comment <id> <comment-id> [-m <text> | -F <file>]
vole issue rm-comment <id> <comment-id>
vole issue edit <id> [--title <title>] [-m <text> | -F <file>] [-l <label>]... [-a <account>]... [--unassign <account>]...
vole issue assign <id> <account>...
vole issue unassign <id> <account>...
vole issue close <id>
vole issue reopen <id>
vole issue list [--state open|closed|all] [-l <label>]... [--assignee <account>]... [--format <fmt>]
vole issue show <id>

Ids and comment ids accept a unique prefix. See Issues.

Change requests

vole change new --title <title> --base <branch> [--head <rev>] [--closes <issue>]... [-m <text> | -F <file>]
vole change list [--state open|closed|all] [--format <fmt>]
vole change show <id>
vole change comment <id> [-m <text> | -F <file>] [--path <file> --line <n> [--side new|old]] [--reply-to <comment-id>]
vole change edit-comment <id> <comment-id> [-m <text> | -F <file>]
vole change rm-comment <id> <comment-id>
vole change update <id> [--base <branch>] [--head <rev>] [--closes <issue>]... [--no-closes]
vole change approve <id> [--as <account>]
vole change merge <id>
vole change close <id>
vole change reopen <id>

change also accepts request, requests and cr as compatibility aliases. An approval is only accepted from the account it names. --closes takes a full issue id (repeatable, comma lists accepted) and names the issues landing the request closes; it replaces the list on update, and --no-closes clears it. A request opened with --head <branch> follows that branch: a later push moves its head and invalidates the old approvals and checks. --head <commit> (or a tag) pins the request to that commit instead. --base <branch> on update retargets an open request, which is how a stacked request moves onto main after the branch below it lands; approvals named the old base and stop counting. See Change requests.

change merge lands the request. When the combined result is still building it prints that the request will merge when the result passes, which is the same “merge when ready” a click on the page arms. change show prints the combined result and whether the landing is ready.

Releases

vole release create <tag> (-m <text> | -F <file|->)
vole release attach <tag> <file>... [--remote <name>]

create writes the annotated tag in the current repository; push it with git push --follow-tags. See Releases.

Pages

vole pages deploy [<dir>] [--branch <name>] [-m <text> | -F <file>] [--dry-run] [--remote <name>]
vole pages status [--branch <name>] [--remote <name>]
vole pages unpublish [--branch <name>] [--remote <name>]

See Sites.

CI

vole runner --remote <ssh-url> [--once] [--jobs <n>] [--dir <path>] [--repos <name>]... [--label <name>]...
            [--container-runtime <name>] [--container-network none|bridge]
            [--container-pull missing|always|never] [--container-user <uid:gid>] [--container-userns <mode>]
            [--container-memory <size>] [--container-cpus <n>] [--container-pids <n>]
            [--cache-size <size>] [--git-cache-size <size>] [--no-cache]
vole run cancel <id> [--remote <name|url>]
vole run rerun <id> [--remote <name|url>]
vole workflow check [--dir <path>]
vole workflow list [--dir <path>]
vole workflow run [--dir <path>] [<workflow>...] [--job <id>]...
            [--container-runtime <name>]
            [--container-network none|bridge] [--container-pull missing|always|never]
            [--container-user <uid:gid>] [--container-userns <mode>]
            [--cache-size <size>] [--no-cache]
vole workflow dispatch --remote <name|url> --workflow <path> [--ref <branch>]
            [--input name=value]...

--jobs is how many jobs one runner process runs at once (default 1). Each is a session of its own, so the container limits are per job and a machine running four jobs needs room for four containers. Under --once the runner still starts --jobs jobs, and exits when they all finish.

workflow run executes steps in the working tree, in the job’s image. It needs no runner, but it does read the secrets its jobs declare from the instance the checkout’s origin names, using your key; a value it cannot read is left unset with a note, and the values it did read are masked in the output. It needs a container runtime; there is no host mode. Both it and vole runner reuse a job cache (see Runners § Caching). A runner also keeps a copy of every repository it builds and clones each job from it, so the history crosses the network once rather than once per run (see Runners § Saved checkouts). See Workflows § Check and run a workflow locally.

workflow dispatch queues a manual run on the instance instead. The workflow must declare on.workflow-dispatch; --ref defaults to the default branch, and each --input is checked against the input’s declared type, options, and default. Only a maintainer or instance admin may dispatch.

run cancel is the CLI counterpart of the cancel button on a run page. It takes the 26-character id in that page’s URL: a queued run is canceled outright, and a running one is asked to stop, with its runner stopping at the next heartbeat. run rerun queues a finished run again with the same pinned job. Only a maintainer or instance admin may do either, and data/audit.log records the account, the repository, and the run (run.cancel or run.rerun).

Internal

vole hook pre-receive|post-receive is installed in repositories by Vole. It is not intended to be run by hand.