Federated via graft
0

Configure Feed

Select the types of activity you want to include in your feed.

Tutorial#

This walks through mirroring one repository between a Forgejo instance and a Radicle node, from nothing. Replace the names below with your own:

Placeholder Stands for
git.example.org your Forgejo instance
seed.example.org your Radicle node's radicle-httpd address
alice the Forgejo account that owns the repo
my-project the repository

Prerequisites#

  • A Forgejo instance you can create repos and tokens on.
  • A machine with git, the rad CLI, and a running radicle-node — see Radicle's own seeder guide if you don't have one yet.
  • Go 1.23+, to build graft.

1. Build graft#

git clone https://git.example.org/alice/graft.git
cd graft
go build -o graft ./cmd/sync
sudo mv graft /usr/local/bin/graft

2. Create a Forgejo token#

graft needs a token scoped to write:repository and write:issue on the repo it syncs.

curl -X POST -H "Authorization: token $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"graft","scopes":["write:repository","write:issue"]}' \
  https://git.example.org/api/v1/users/alice/tokens

Save the returned token somewhere graft can read and nowhere else can:

mkdir -p /etc/graft
echo "$TOKEN" > /etc/graft/my-project.token
chmod 600 /etc/graft/my-project.token

3. Create the repo on Forgejo#

curl -X POST -H "Authorization: token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-project","auto_init":true,"default_branch":"main"}' \
  https://git.example.org/api/v1/user/repos

auto_init gives it one commit, so there's something to mirror.

4. Create the repo on Radicle#

Radicle repos are created from an existing git checkout. Clone the one you just made and initialize it:

git clone https://git.example.org/alice/my-project.git
cd my-project
rad init --name my-project --default-branch main --public

This prints a Repository ID (RID), something like rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5. Write it down — the config needs it.

If you have other Radicle nodes that should also carry this repo, tell them to seed it:

rad seed rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5

5. Write the config#

# /etc/graft/config.yaml
sync_interval: 5m
state_db: /var/lib/graft/state.db

repos:
  - name: my-project
    forgejo:
      base_url: https://git.example.org
      owner: alice
      repo: my-project
      token_file: /etc/graft/my-project.token
    radicle:
      rid: rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
      http_base_url: https://seed.example.org
      rad_home: /home/graft/.radicle
    sync:
      git: true
      issues: true
      patches: true

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.

6. Run it once#

mkdir -p /var/lib/graft
graft -config /etc/graft/config.yaml -once

No output means no errors. Check both sides:

curl -s https://seed.example.org/api/v1/repos/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5

If you open an issue on the Forgejo repo and run graft -once again, it shows up on Radicle within that one pass.

7. Run it as a service#

sudo cp systemd/graft.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now graft

It now mirrors both repos every sync_interval, in both directions, on its own.

Troubleshooting#

"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.

"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.

"'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=....

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.