Federated via graft
4.3 kB
134 lines
1# Tutorial
2
3This walks through mirroring one repository between a Forgejo instance and a Radicle node, from nothing. Replace the names below with your own:
4
5| Placeholder | Stands for |
6|---|---|
7| `git.example.org` | your Forgejo instance |
8| `seed.example.org` | your Radicle node's `radicle-httpd` address |
9| `alice` | the Forgejo account that owns the repo |
10| `my-project` | the repository |
11
12## Prerequisites
13
14- A Forgejo instance you can create repos and tokens on.
15- A machine with `git`, the `rad` CLI, and a running `radicle-node` — see [Radicle's own seeder guide](https://radicle.dev/guides/seeder) if you don't have one yet.
16- Go 1.23+, to build `graft`.
17
18## 1. Build graft
19
20```sh
21git clone https://git.example.org/alice/graft.git
22cd graft
23go build -o graft ./cmd/sync
24sudo mv graft /usr/local/bin/graft
25```
26
27## 2. Create a Forgejo token
28
29`graft` needs a token scoped to `write:repository` and `write:issue` on the repo it syncs.
30
31```sh
32curl -X POST -H "Authorization: token $ADMIN_TOKEN" \
33 -H "Content-Type: application/json" \
34 -d '{"name":"graft","scopes":["write:repository","write:issue"]}' \
35 https://git.example.org/api/v1/users/alice/tokens
36```
37
38Save the returned token somewhere `graft` can read and nowhere else can:
39
40```sh
41mkdir -p /etc/graft
42echo "$TOKEN" > /etc/graft/my-project.token
43chmod 600 /etc/graft/my-project.token
44```
45
46## 3. Create the repo on Forgejo
47
48```sh
49curl -X POST -H "Authorization: token $TOKEN" \
50 -H "Content-Type: application/json" \
51 -d '{"name":"my-project","auto_init":true,"default_branch":"main"}' \
52 https://git.example.org/api/v1/user/repos
53```
54
55`auto_init` gives it one commit, so there's something to mirror.
56
57## 4. Create the repo on Radicle
58
59Radicle repos are created from an existing git checkout. Clone the one you just made and initialize it:
60
61```sh
62git clone https://git.example.org/alice/my-project.git
63cd my-project
64rad init --name my-project --default-branch main --public
65```
66
67This prints a Repository ID (RID), something like `rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5`. Write it down — the config needs it.
68
69If you have other Radicle nodes that should also carry this repo, tell them to seed it:
70
71```sh
72rad seed rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
73```
74
75## 5. Write the config
76
77```yaml
78# /etc/graft/config.yaml
79sync_interval: 5m
80state_db: /var/lib/graft/state.db
81
82repos:
83 - name: my-project
84 forgejo:
85 base_url: https://git.example.org
86 owner: alice
87 repo: my-project
88 token_file: /etc/graft/my-project.token
89 radicle:
90 rid: rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
91 http_base_url: https://seed.example.org
92 rad_home: /home/graft/.radicle
93 sync:
94 git: true
95 issues: true
96 patches: true
97```
98
99`rad_home` is the `RAD_HOME` of the identity `graft` pushes as — usually a dedicated one, created with `rad auth` under whatever user runs `graft`.
100
101## 6. Run it once
102
103```sh
104mkdir -p /var/lib/graft
105graft -config /etc/graft/config.yaml -once
106```
107
108No output means no errors. Check both sides:
109
110```sh
111curl -s https://seed.example.org/api/v1/repos/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
112```
113
114If you open an issue on the Forgejo repo and run `graft -once` again, it shows up on Radicle within that one pass.
115
116## 7. Run it as a service
117
118```sh
119sudo cp systemd/graft.service /etc/systemd/system/
120sudo systemctl daemon-reload
121sudo systemctl enable --now graft
122```
123
124It now mirrors both repos every `sync_interval`, in both directions, on its own.
125
126## Troubleshooting
127
128**"token does not have at least one of required scope(s)"** — the Forgejo token is missing `write:issue` or `write:repository`. Regenerate it with both.
129
130**"forgejo and rad diverged since last sync"** — both sides got commits since the last pass that aren't ancestors of each other. `graft` won't guess which one wins; merge them by hand on either side and it'll pick up cleanly next pass.
131
132**"'remote-rad' is not a git command"** — `git-remote-rad` (installed alongside `rad`) isn't on `PATH` for the user running `graft`. Add its directory to the systemd unit's `Environment=PATH=...`.
133
134**A patch/PR never shows up on the other side** — check that the corresponding repo isn't still empty on the destination. The first git sync has to land before patches/issues referencing that content can be mirrored.