detached 80d5067

mirror.md RawHistoryFolder

Push mirror

Ermite can push its repositories to a remote Git host over SSH (GitHub, another forge…), to keep a read-only copy there. The push never forces and never deletes anything on the remote.

Set up

  1. The server needs ERMITE_SECRETS_KEY; it seals the mirror SSH key. Without it, syncs fail and nothing is stored.

  2. Add the server's public mirror key to the remote account or repository, as a key allowed to push:

    ermite mirror key show | gh ssh-key add - --title "ermite mirror"
    

    mirror key show prints only the authorized_keys line. One key serves every mirror.

  3. Set the global policy (owner only). The URL template holds {repo}, replaced by the repository name; the cron is five fields, in UTC:

    ermite mirror config --url 'git@github.com:acme/{repo}.git' --cron '*/15 * * * *' --enabled
    ermite mirror config show
    

    config changes only the flags you pass. --all-branches mirrors every branch instead of the default branch.

  4. Adjust single repositories:

    ermite mirror add <repo> <url> [--cron …] [--all-branches]   # own URL (and schedule), even when the global policy is off
    ermite mirror exclude <repo>                                  # leave the global policy
    ermite mirror include <repo>                                  # back under it
    ermite mirror remove <repo>                                   # drop the override
    

    Without --cron or --all-branches, add inherits the global values.

URLs are git@host:path or ssh://[user@]host[:port]/path, with no password. The cron may not fire more often than every 5 minutes.

What is pushed

  • The default branch and all tags; every branch with --all-branches.
  • Never a force push, never a deletion: a branch or tag deleted here stays on the remote.
  • The push is atomic: if the remote refuses any ref, nothing is updated and the mirror is paused as diverged. Besides a branch the remote has moved ahead, this happens when a tag was moved or rewritten here (an existing remote tag is never overwritten), and for any other ref the remote rejects.
  • To fix a diverged branch, fast-forward ermite with the remote's commits (push them here). To fix a tag, restore it here to the commit the remote has, or delete that tag on the remote yourself (ermite never deletes anything there). Then run ermite mirror sync <repo> --force.

When it syncs

  • On the cron schedule. A push to ermite only marks the repository as changed; the next due cron tick syncs it. A tick with nothing new does nothing.
  • ermite mirror sync <repo> or ermite mirror sync --all (owner only) syncs now; a repository needs admin rights. --all skips repositories without a mirror, continues past failures, prints one row per repository and exits with status 1 if any failed.
  • A manual sync waits at most about 8 s (the edge proxy cuts responses at 10 s, the CLI at 30 s, while an SSH push to an unreachable host can take 30 s or more). --all queues every repository at once and the server pushes a few at a time (2 in parallel), so the wait stays one bound, not one per repository. A sync still running or still waiting for its turn then is shown as QUEUED true; the CLI says so on stderr, the sync continues on the server, and ermite mirror status shows its outcome.
  • --force skips the no-op check and the 60 s throttle and lifts a pause. It never force-pushes.
  • Overlapping syncs of a repository coalesce; only one push runs at a time per repository.

Limits against spam

Rule Value
No-op nothing changed since the last successful push: no connection
Manual sync one per repository per 60 s, unless --force
Cron at least 5 minutes between firings
Backoff after a transient failure 5 min, doubling, up to 6 h
Paused after 5 consecutive transient failures
Paused at once diverged remote, key refused, remote not found, host key changed

A paused mirror is not retried by the schedule. Changing the mirror configuration of a repository clears its failures.

See what happens

  • ermite mirror status [<repo>] [--json]: state (ok, failing, paused, disabled), reason, last success, next run, failures.
  • A git push to a repository whose mirror is failing or paused prints a remote: warning, only to people with admin rights on the repository: it names the remote and the reason, which other pushers do not see. The same holds for the URL and the reason in mirror status and the web badge.
  • The repository page shows a badge to owners and admins.
  • ermite watch receives mirror.failed once when a mirror starts failing, and mirror.recovered when it succeeds again. See events.

Security

  • The private key is sealed at rest with ERMITE_SECRETS_KEY. It is never printed and never in logs; URLs are logged without credentials or user.

  • Host keys are trusted on first use (TOFU), kept in <data dir>/mirror_known_hosts. The first connection to a host accepts whatever key it presents, so to be sure of it, pre-seed the file before configuring the mirror and compare with what the host publishes:

    ssh-keyscan -t ed25519,rsa,ecdsa github.com | tee -a <data dir>/mirror_known_hosts
    ssh-keygen -lf <data dir>/mirror_known_hosts   # compare with https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints
    

    Remove any line whose fingerprint does not match. If a remote's key changes, the mirror is paused (host_key). After checking the change is legitimate, remove that host's line from the file and run ermite mirror sync <repo> --force.

  • ermite mirror key rotate (owner only) makes a new key at once and prints the fingerprint of the replaced one on stderr. Every mirror fails until the new key is added to the remotes.

  • The scheduler runs in the server process: run a single ermite instance per data directory.

source
1# Push mirror
2
3Ermite can push its repositories to a remote Git host over SSH (GitHub, another forge…), to keep a read-only copy there. The push never forces and never deletes anything on the remote.
4
5## Set up
6
71. The server needs `ERMITE_SECRETS_KEY`; it seals the mirror SSH key. Without it, syncs fail and nothing is stored.
82. Add the server's public mirror key to the remote account or repository, as a key allowed to push:
9
10   ```sh
11   ermite mirror key show | gh ssh-key add - --title "ermite mirror"
12   ```
13
14   `mirror key show` prints only the `authorized_keys` line. One key serves every mirror.
153. Set the global policy (owner only). The URL template holds `{repo}`, replaced by the repository name; the cron is five fields, in UTC:
16
17   ```sh
18   ermite mirror config --url 'git@github.com:acme/{repo}.git' --cron '*/15 * * * *' --enabled
19   ermite mirror config show
20   ```
21
22   `config` changes only the flags you pass. `--all-branches` mirrors every branch instead of the default branch.
234. Adjust single repositories:
24
25   ```sh
26   ermite mirror add <repo> <url> [--cron …] [--all-branches]   # own URL (and schedule), even when the global policy is off
27   ermite mirror exclude <repo>                                  # leave the global policy
28   ermite mirror include <repo>                                  # back under it
29   ermite mirror remove <repo>                                   # drop the override
30   ```
31
32   Without `--cron` or `--all-branches`, `add` inherits the global values.
33
34URLs are `git@host:path` or `ssh://[user@]host[:port]/path`, with no password. The cron may not fire more often than every 5 minutes.
35
36## What is pushed
37
38- The default branch and all tags; every branch with `--all-branches`.
39- Never a force push, never a deletion: a branch or tag deleted here stays on the remote.
40- The push is atomic: if the remote refuses **any** ref, nothing is updated and the mirror is **paused** as diverged. Besides a branch the remote has moved ahead, this happens when a tag was moved or rewritten here (an existing remote tag is never overwritten), and for any other ref the remote rejects.
41- To fix a diverged branch, fast-forward ermite with the remote's commits (push them here). To fix a tag, restore it here to the commit the remote has, or delete that tag on the remote yourself (ermite never deletes anything there). Then run `ermite mirror sync <repo> --force`.
42
43## When it syncs
44
45- On the cron schedule. A push to ermite only marks the repository as changed; the next due cron tick syncs it. A tick with nothing new does nothing.
46- `ermite mirror sync <repo>` or `ermite mirror sync --all` (owner only) syncs now; a repository needs admin rights. `--all` skips repositories without a mirror, continues past failures, prints one row per repository and exits with status 1 if any failed.
47- A manual sync waits at most about 8 s (the edge proxy cuts responses at 10 s, the CLI at 30 s, while an SSH push to an unreachable host can take 30 s or more). `--all` queues every repository at once and the server pushes a few at a time (2 in parallel), so the wait stays one bound, not one per repository. A sync still running or still waiting for its turn then is shown as `QUEUED true`; the CLI says so on stderr, the sync continues on the server, and `ermite mirror status` shows its outcome.
48- `--force` skips the no-op check and the 60 s throttle and lifts a pause. It never force-pushes.
49- Overlapping syncs of a repository coalesce; only one push runs at a time per repository.
50
51## Limits against spam
52
53| Rule | Value |
54|---|---|
55| No-op | nothing changed since the last successful push: no connection |
56| Manual sync | one per repository per 60 s, unless `--force` |
57| Cron | at least 5 minutes between firings |
58| Backoff after a transient failure | 5 min, doubling, up to 6 h |
59| Paused | after 5 consecutive transient failures |
60| Paused at once | diverged remote, key refused, remote not found, host key changed |
61
62A paused mirror is not retried by the schedule. Changing the mirror configuration of a repository clears its failures.
63
64## See what happens
65
66- `ermite mirror status [<repo>] [--json]`: state (`ok`, `failing`, `paused`, `disabled`), reason, last success, next run, failures.
67- A `git push` to a repository whose mirror is failing or paused prints a `remote:` warning, **only to people with admin rights on the repository**: it names the remote and the reason, which other pushers do not see. The same holds for the URL and the reason in `mirror status` and the web badge.
68- The repository page shows a badge to owners and admins.
69- `ermite watch` receives `mirror.failed` once when a mirror starts failing, and `mirror.recovered` when it succeeds again. See [events](ci-events.md#mirror-events).
70
71## Security
72
73- The private key is sealed at rest with `ERMITE_SECRETS_KEY`. It is never printed and never in logs; URLs are logged without credentials or user.
74- Host keys are trusted on first use (TOFU), kept in `<data dir>/mirror_known_hosts`. The first connection to a host accepts whatever key it presents, so to be sure of it, pre-seed the file before configuring the mirror and compare with what the host publishes:
75
76  ```sh
77  ssh-keyscan -t ed25519,rsa,ecdsa github.com | tee -a <data dir>/mirror_known_hosts
78  ssh-keygen -lf <data dir>/mirror_known_hosts   # compare with https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints
79  ```
80
81  Remove any line whose fingerprint does not match. If a remote's key changes, the mirror is paused (`host_key`). After checking the change is legitimate, remove that host's line from the file and run `ermite mirror sync <repo> --force`.
82- `ermite mirror key rotate` (owner only) makes a new key at once and prints the fingerprint of the replaced one on stderr. Every mirror fails until the new key is added to the remotes.
83- The scheduler runs in the server process: run a single ermite instance per data directory.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 4

Tags 45

↑↓ moveenter openesc close