Mirrors git content, issues and patches/pull requests between Forgejo and Radicle
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
jeanbaptiste 92bff6ba35 Remove propagation-test file from source
Leftover from testing graft-source's own sync mesh; not meant to live in
the real codebase.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KB7C5CeuMbQujqRtb4foki
2026-09-12 18:51:27 +02:00
cmd/sync Group dashboard activity by repo, not by sync pair 2026-09-12 18:50:53 +02:00
internal Group dashboard activity by repo, not by sync pair 2026-09-12 18:50:53 +02:00
systemd Initial commit: Forgejo <-> Radicle sync daemon 2026-09-12 17:03:32 +02:00
.gitignore Initial commit: Forgejo <-> Radicle sync daemon 2026-09-12 17:03:32 +02:00
config.example.yaml Group dashboard activity by repo, not by sync pair 2026-09-12 18:50:53 +02:00
go.mod Initial commit: Forgejo <-> Radicle sync daemon 2026-09-12 17:03:32 +02:00
go.sum Initial commit: Forgejo <-> Radicle sync daemon 2026-09-12 17:03:32 +02:00
LICENSE Initial commit: Forgejo <-> Radicle sync daemon 2026-09-12 17:03:32 +02:00
README.md Note planned Authorized Integrations path in README 2026-09-12 17:35:57 +02:00
TUTORIAL.md Initial commit: Forgejo <-> Radicle sync daemon 2026-09-12 17:03:32 +02:00

graft

A daemon that mirrors repositories between Forgejo and Radicle: git content, issues, and pull requests / patches.

It runs on a timer, keeps state in a local SQLite file, and never force-pushes. Real divergences between the two sides are reported and left for humans to resolve.

What it syncs

  • Git content: the default branch, fast-forward only.
  • Issues: Forgejo issues become Radicle issues and vice versa. Create-only for now — edits made after the first mirror aren't propagated yet.
  • Patches / pull requests: a Forgejo PR opens a Radicle patch (and back), by pushing the PR's head commit to refs/patches on the Radicle side, or as a branch + PR on the Forgejo side.

Planned: Forgejo-to-Forgejo via Authorized Integrations

Right now, two Forgejo instances stay in sync by both pairing with the same Radicle repository — Radicle is the hub. An alternative worth supporting directly: one instance's CI authenticates straight to another's API using Forgejo's Authorized Integrations (a short-lived OIDC token, no stored secret), instead of routing through Radicle at all. Not implemented yet — noted here so the config shape leaves room for it.

Why

Forgejo and Radicle solve the same problem — hosting and reviewing changes to a git repository — with opposite architectures: one is a server you point a browser at, the other is a peer-to-peer protocol with no server at all. graft lets a project exist on both.

Requirements

  • Go 1.23+ to build
  • git
  • the rad CLI, with git-remote-rad on PATH, and a Radicle node already running with its own identity (rad auth)
  • a Forgejo access token scoped to write:repository and write:issue on the repo you're syncing

Build

go build -o graft ./cmd/sync

Config

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

Tokens live in their own files, never inline in the config. graft reads them at startup and expects chmod 600.

Run

graft -config /etc/graft/config.yaml   # loop forever, per sync_interval
graft -config /etc/graft/config.yaml -once   # one pass, then exit

A systemd unit is in systemd/graft.service.

Full walkthrough, from a blank server to a running mirror: TUTORIAL.md.

License

GPLv3. See LICENSE.