Federated via graft
0

Configure Feed

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

48 1 2

Clone this repository

https://tangled.org/graft.pds.cyberwild.org/graft-source https://tangled.org/did:plc:7qwsrnhghgdiiakveogb42os
git@knot.cyberwild.org:graft.pds.cyberwild.org/graft-source git@knot.cyberwild.org:did:plc:7qwsrnhghgdiiakveogb42os

For self-hosted knots, clone URLs may differ based on your setup.


README.md

graft#

A daemon and web-UI that mirrors repositories between Forgejo instances. Also connect to Radicle, Activity Pub and ATProto: git content, issues, comments 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.
  • Comments: on issues and patches, both directions, from four origins — native Forgejo ↔ Radicle, a Mastodon reply (ActivityPub), and a Bluesky reply (AT Proto). Dedup'd by content hash, prefixed with where it actually came from. See TUTORIAL.md § Comments.

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.

Dashboard#

graft serves a status page (/) alongside the sync daemon: one row per federation, one cell per mirrored commit/issue/patch/comment, colored by kind. A mirror in sync with the rest of its federation shows the same cells as everyone else, even for content it didn't itself log; a mirror whose last sync pass actually failed shows only what it has confirmed, plus a visible error badge — the heatmap is never a silent source of "why does this one look behind."

Three buttons next to it (+ Add Forgejo peer, + Add Radicle peer, + New repo) let a peer join a federation or start a new one without shell access — instantly with the admin password, or as a request the admin approves from /admin/pending without one. Each is covered next to its manual equivalent in TUTORIAL.md; the shared mechanics are in § Self-service forms.

Fediverse / AT Proto#

Each mirrored series can also speak ActivityPub: @series@your-host is followable from Mastodon, posting a note for every commit/issue/patch, with replies bridged back as real comments (see Comments above). A minimal AT Proto client can post the same to Bluesky. Both are off by default — set public_host in the config to turn ActivityPub on for a series.

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.