docs: document CI in the README
ERMITE_OWNER and ERMITE_CI_LOG_RETENTION, the run and runner commands, the runner token preset, and a short CI section: on once a runner is registered, the merge rule, unsupported features, logs on disk, and a link to ermite-runner. The help of --failing-checks-block no longer says there are no checks.
- commit
- ac92e76fd15962ebbd4a39bc97f3cca7b3a79354
- author
- brice · · 2026-10-12 19:24 UTC
- parent
- 081981c
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +16 -3
M README.md +15 -2
| @@ -27,6 +27,8 @@ ermite serve | |||
| 27 | 27 | | `ERMITE_LOG_FORMAT` | `json` | `json` or `console` | | |
| 28 | 28 | | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges | | |
| 29 | 29 | | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges | | |
| 30 | + | | `ERMITE_OWNER` | `owner` | owner login that CI shows in `github.repository` (`<owner>/<repo>`) and `github.repository_owner`; follows the rules of repository names | | |
| 31 | + | | `ERMITE_CI_LOG_RETENTION` | `90d` | how long CI runs, jobs, checks and logs are kept: `Nd` (days) or a Go duration such as `720h`, at least one hour | | |
| 30 | 32 | ||
| 31 | 33 | `ermite version` prints the version (and the server's when `ERMITE_URL` is set). | |
| 32 | 34 | ||
| @@ -37,11 +39,14 @@ The same binary drives a running server over its API. It reads `ERMITE_URL` (the | |||
| 37 | 39 | ```sh | |
| 38 | 40 | export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_... | |
| 39 | 41 | ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes] | |
| 40 | - | ermite token create <label> [--preset cli|agent|ci] [--scope repo:read …] [--repo name …] [--expires 30d] # prints the secret once | |
| 42 | + | ermite token create <label> [--preset cli|agent|ci|runner] [--scope repo:read …] [--repo name …] [--expires 30d] # prints the secret once | |
| 41 | 43 | ermite token list | revoke <id> | |
| 42 | 44 | ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff] | |
| 43 | 45 | ermite mr comment <repo> <n> -m … [--path … --line … --side old|new] | resolve <repo> <n> <comment> | |
| 44 | 46 | ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <repo> <n> | |
| 47 | + | ermite run list <repo> [--workflow …] [--branch …] [--status …] | view <repo> <id> [--attempt n] [--log [--job …]] | |
| 48 | + | ermite run watch <repo> <id> [--log] | cancel <repo> <id> | rerun <repo> <id> [--failed] | |
| 49 | + | ermite runner list | remove <id> | |
| 45 | 50 | ermite web login # prints a one-time sign-in link (60 s) | |
| 46 | 51 | ermite web logout --all # signs every browser out | |
| 47 | 52 | ermite signing-key show # public key and allowed_signers line of squash commits | |
| @@ -51,7 +56,7 @@ ermite watch [repo] [--json] # follows events, reconnects and resumes | |||
| 51 | 56 | ||
| 52 | 57 | An agent skill describing the CLI and the forge lives in [`skills/ermite`](skills/ermite/SKILL.md); link or copy that folder into your agent's skills directory. | |
| 53 | 58 | ||
| 54 | - | `--json` on `repo list|show|create|edit`, `token create|list` and `version` prints stable field names, listed in each command's `--help`; `watch --json` prints one object per line. Errors are one line on standard error (`ermite: …`) and exit status 1. `ermite completion bash|zsh|fish` prints a shell completion script that completes repository names from the server. | |
| 59 | + | `--json` on `repo list|show|create|edit`, `token create|list`, `run list|view|cancel|rerun`, `runner list` and `version` prints stable field names, listed in each command's `--help`; `watch --json` prints one object per line. Errors are one line on standard error (`ermite: …`) and exit status 1. `ermite completion bash|zsh|fish` prints a shell completion script that completes repository names from the server. | |
| 55 | 60 | ||
| 56 | 61 | ## Git over HTTP | |
| 57 | 62 | ||
| @@ -66,6 +71,14 @@ Use the admin token (`ERMITE_ADMIN_TOKEN`) or a stored token as the password; th | |||
| 66 | 71 | ||
| 67 | 72 | A merge request proposes a branch for its target (the default branch unless set). The merge is refused when the branch moved since the reviewed head (`--head`) or when the target is not an ancestor of it. Fast-forward moves the target; squash writes one commit signed by the server's SSH key (`ermite signing-key show` prints the `allowed_signers` line for `git verify-commit`). `ermite repo edit --merge-modes` limits the modes per repository. `--delete-branch` (a checkbox on the web) deletes the source branch after the merge, unless it is the default branch or another open request uses it. Tokens need `mr:write` to open and comment, and `mr:merge` to merge (and delete the merged branch). | |
| 68 | 73 | ||
| 74 | + | ## CI | |
| 75 | + | ||
| 76 | + | ermite runs the `.github/workflows/*.yml` workflows of a repository on `push` (a branch or a tag, and the merge of a merge request) and shows each job as a check on the commit, the branch list and the merge request. CI is on once a runner is registered; there is no switch. Before that a push makes no run and no check. | |
| 77 | + | ||
| 78 | + | Runs execute on [ermite-runner](https://git.bnema.dev/bnema/ermite-runner) hosts, which call ermite over its API. Create a token for the runner host with `ermite token create <label> --preset runner` (the `runner:register` scope, which also gives access to the code and the secrets of every repository: give it only to runners you operate), then register the runner with it. `ermite runner list` shows the runners and whether they are online; `ermite runner remove <id>` stops one. A job goes to a runner that has every label of its `runs-on`; a job no runner takes within 24 hours fails. | |
| 79 | + | ||
| 80 | + | `ermite run list|view|watch|cancel|rerun` and the `Actions` tab of a repository read and control the runs; `cancel` and `rerun` need the `ci:write` scope. With `failing_checks_block` on (the default, `ermite repo edit --failing-checks-block=false`), a merge is refused while a check of the head failed or has not finished. A workflow that uses a feature the runner does not support yet (`uses:` steps, `container:`, `services:`, `environment:`, `concurrency`, `continue-on-error`) fails with `not supported yet: <feature>`, never silently. Logs are files under `$ERMITE_DATA_DIR/ci/logs`. | |
| 81 | + | ||
| 69 | 82 | ## Web sign-in | |
| 70 | 83 | ||
| 71 | 84 | `ermite web login`, run with the admin token or a token with every scope, prints a link that works once within 60 seconds. Opening it and confirming sets a session cookie for 12 hours (never longer than the token that asked for the link). It needs `https` in `ERMITE_PUBLIC_URL` (except on localhost). Browser writes require the session and an `Origin` equal to `ERMITE_PUBLIC_URL`. `ermite web logout --all`, or revoking a token with every scope, ends every session. | |
| @@ -27,6 +27,8 @@ ermite serve | |||||
| 27 | | `ERMITE_LOG_FORMAT` | `json` | `json` or `console` | | 27 | | `ERMITE_LOG_FORMAT` | `json` | `json` or `console` | | ||
| 28 | | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges | | 28 | | `ERMITE_COMMITTER_NAME` | `ermite` | committer of squash merges | | ||
| 29 | | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges | | 29 | | `ERMITE_COMMITTER_EMAIL` | `ermite@<public host>` | committer email of squash merges | | ||
| 30 | + | | `ERMITE_OWNER` | `owner` | owner login that CI shows in `github.repository` (`<owner>/<repo>`) and `github.repository_owner`; follows the rules of repository names | | |||
| 31 | + | | `ERMITE_CI_LOG_RETENTION` | `90d` | how long CI runs, jobs, checks and logs are kept: `Nd` (days) or a Go duration such as `720h`, at least one hour | | |||
| 30 | 32 | ||||
| 31 | `ermite version` prints the version (and the server's when `ERMITE_URL` is set). | 33 | `ermite version` prints the version (and the server's when `ERMITE_URL` is set). | ||
| 32 | 34 | ||||
| @@ -37,11 +39,14 @@ The same binary drives a running server over its API. It reads `ERMITE_URL` (the | |||||
| 37 | ```sh | 39 | ```sh | ||
| 38 | export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_... | 40 | export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_... | ||
| 39 | ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes] | 41 | ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes] | ||
| 40 | - | ermite token create <label> [--preset cli|agent|ci] [--scope repo:read …] [--repo name …] [--expires 30d] # prints the secret once | 42 | + | ermite token create <label> [--preset cli|agent|ci|runner] [--scope repo:read …] [--repo name …] [--expires 30d] # prints the secret once |
| 41 | ermite token list | revoke <id> | 43 | ermite token list | revoke <id> | ||
| 42 | ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff] | 44 | ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff] | ||
| 43 | ermite mr comment <repo> <n> -m … [--path … --line … --side old|new] | resolve <repo> <n> <comment> | 45 | ermite mr comment <repo> <n> -m … [--path … --line … --side old|new] | resolve <repo> <n> <comment> | ||
| 44 | ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <repo> <n> | 46 | ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <repo> <n> | ||
| 47 | + | ermite run list <repo> [--workflow …] [--branch …] [--status …] | view <repo> <id> [--attempt n] [--log [--job …]] | |||
| 48 | + | ermite run watch <repo> <id> [--log] | cancel <repo> <id> | rerun <repo> <id> [--failed] | |||
| 49 | + | ermite runner list | remove <id> | |||
| 45 | ermite web login # prints a one-time sign-in link (60 s) | 50 | ermite web login # prints a one-time sign-in link (60 s) | ||
| 46 | ermite web logout --all # signs every browser out | 51 | ermite web logout --all # signs every browser out | ||
| 47 | ermite signing-key show # public key and allowed_signers line of squash commits | 52 | ermite signing-key show # public key and allowed_signers line of squash commits | ||
| @@ -51,7 +56,7 @@ ermite watch [repo] [--json] # follows events, reconnects and resumes | |||||
| 51 | 56 | ||||
| 52 | An agent skill describing the CLI and the forge lives in [`skills/ermite`](skills/ermite/SKILL.md); link or copy that folder into your agent's skills directory. | 57 | An agent skill describing the CLI and the forge lives in [`skills/ermite`](skills/ermite/SKILL.md); link or copy that folder into your agent's skills directory. | ||
| 53 | 58 | ||||
| 54 | - | `--json` on `repo list|show|create|edit`, `token create|list` and `version` prints stable field names, listed in each command's `--help`; `watch --json` prints one object per line. Errors are one line on standard error (`ermite: …`) and exit status 1. `ermite completion bash|zsh|fish` prints a shell completion script that completes repository names from the server. | 59 | + | `--json` on `repo list|show|create|edit`, `token create|list`, `run list|view|cancel|rerun`, `runner list` and `version` prints stable field names, listed in each command's `--help`; `watch --json` prints one object per line. Errors are one line on standard error (`ermite: …`) and exit status 1. `ermite completion bash|zsh|fish` prints a shell completion script that completes repository names from the server. |
| 55 | 60 | ||||
| 56 | ## Git over HTTP | 61 | ## Git over HTTP | ||
| 57 | 62 | ||||
| @@ -66,6 +71,14 @@ Use the admin token (`ERMITE_ADMIN_TOKEN`) or a stored token as the password; th | |||||
| 66 | 71 | ||||
| 67 | A merge request proposes a branch for its target (the default branch unless set). The merge is refused when the branch moved since the reviewed head (`--head`) or when the target is not an ancestor of it. Fast-forward moves the target; squash writes one commit signed by the server's SSH key (`ermite signing-key show` prints the `allowed_signers` line for `git verify-commit`). `ermite repo edit --merge-modes` limits the modes per repository. `--delete-branch` (a checkbox on the web) deletes the source branch after the merge, unless it is the default branch or another open request uses it. Tokens need `mr:write` to open and comment, and `mr:merge` to merge (and delete the merged branch). | 72 | A merge request proposes a branch for its target (the default branch unless set). The merge is refused when the branch moved since the reviewed head (`--head`) or when the target is not an ancestor of it. Fast-forward moves the target; squash writes one commit signed by the server's SSH key (`ermite signing-key show` prints the `allowed_signers` line for `git verify-commit`). `ermite repo edit --merge-modes` limits the modes per repository. `--delete-branch` (a checkbox on the web) deletes the source branch after the merge, unless it is the default branch or another open request uses it. Tokens need `mr:write` to open and comment, and `mr:merge` to merge (and delete the merged branch). | ||
| 68 | 73 | ||||
| 74 | + | ## CI | |||
| 75 | + | ||||
| 76 | + | ermite runs the `.github/workflows/*.yml` workflows of a repository on `push` (a branch or a tag, and the merge of a merge request) and shows each job as a check on the commit, the branch list and the merge request. CI is on once a runner is registered; there is no switch. Before that a push makes no run and no check. | |||
| 77 | + | ||||
| 78 | + | Runs execute on [ermite-runner](https://git.bnema.dev/bnema/ermite-runner) hosts, which call ermite over its API. Create a token for the runner host with `ermite token create <label> --preset runner` (the `runner:register` scope, which also gives access to the code and the secrets of every repository: give it only to runners you operate), then register the runner with it. `ermite runner list` shows the runners and whether they are online; `ermite runner remove <id>` stops one. A job goes to a runner that has every label of its `runs-on`; a job no runner takes within 24 hours fails. | |||
| 79 | + | ||||
| 80 | + | `ermite run list|view|watch|cancel|rerun` and the `Actions` tab of a repository read and control the runs; `cancel` and `rerun` need the `ci:write` scope. With `failing_checks_block` on (the default, `ermite repo edit --failing-checks-block=false`), a merge is refused while a check of the head failed or has not finished. A workflow that uses a feature the runner does not support yet (`uses:` steps, `container:`, `services:`, `environment:`, `concurrency`, `continue-on-error`) fails with `not supported yet: <feature>`, never silently. Logs are files under `$ERMITE_DATA_DIR/ci/logs`. | |||
| 81 | + | ||||
| 69 | ## Web sign-in | 82 | ## Web sign-in | ||
| 70 | 83 | ||||
| 71 | `ermite web login`, run with the admin token or a token with every scope, prints a link that works once within 60 seconds. Opening it and confirming sets a session cookie for 12 hours (never longer than the token that asked for the link). It needs `https` in `ERMITE_PUBLIC_URL` (except on localhost). Browser writes require the session and an `Origin` equal to `ERMITE_PUBLIC_URL`. `ermite web logout --all`, or revoking a token with every scope, ends every session. | 84 | `ermite web login`, run with the admin token or a token with every scope, prints a link that works once within 60 seconds. Opening it and confirming sets a session cookie for 12 hours (never longer than the token that asked for the link). It needs `https` in `ERMITE_PUBLIC_URL` (except on localhost). Browser writes require the session and an `Origin` equal to `ERMITE_PUBLIC_URL`. `ermite web logout --all`, or revoking a token with every scope, ends every session. | ||
M internal/app/cli_repo.go +1 -1
| @@ -275,7 +275,7 @@ func newRepoEditCmd(env *cliEnv) *cobra.Command { | |||
| 275 | 275 | Long: "Change the fields given as flags and leave the others alone; --description \"\" clears the\n" + | |
| 276 | 276 | "description. The default branch must already exist. --merge-modes lists the modes a merge request\n" + | |
| 277 | 277 | "may be merged with (fast-forward, squash; default for a new repository: both), and\n" + | |
| 278 | - | "--failing-checks-block says whether a failing check prevents a merge (there are no checks yet).\n" + | |
| 278 | + | "--failing-checks-block says whether a failing check prevents a merge (a failed or unfinished CI check of the head; see README, CI).\n" + | |
| 279 | 279 | "Needs the repo:admin scope." + jsonRepoHelp, | |
| 280 | 280 | Args: cobra.ExactArgs(1), | |
| 281 | 281 | ValidArgsFunction: completeRepoNames(env), | |
| @@ -275,7 +275,7 @@ func newRepoEditCmd(env *cliEnv) *cobra.Command { | |||||
| 275 | Long: "Change the fields given as flags and leave the others alone; --description \"\" clears the\n" + | 275 | Long: "Change the fields given as flags and leave the others alone; --description \"\" clears the\n" + | ||
| 276 | "description. The default branch must already exist. --merge-modes lists the modes a merge request\n" + | 276 | "description. The default branch must already exist. --merge-modes lists the modes a merge request\n" + | ||
| 277 | "may be merged with (fast-forward, squash; default for a new repository: both), and\n" + | 277 | "may be merged with (fast-forward, squash; default for a new repository: both), and\n" + | ||
| 278 | - | "--failing-checks-block says whether a failing check prevents a merge (there are no checks yet).\n" + | 278 | + | "--failing-checks-block says whether a failing check prevents a merge (a failed or unfinished CI check of the head; see README, CI).\n" + |
| 279 | "Needs the repo:admin scope." + jsonRepoHelp, | 279 | "Needs the repo:admin scope." + jsonRepoHelp, | ||
| 280 | Args: cobra.ExactArgs(1), | 280 | Args: cobra.ExactArgs(1), | ||
| 281 | ValidArgsFunction: completeRepoNames(env), | 281 | ValidArgsFunction: completeRepoNames(env), | ||