Link guides to radicle.dev instead of hosting them

The guides live on radicle.dev. The old URLs redirect to the respective
radicle.dev pages.
This commit is contained in:
Rūdolfs Ošiņš
2026-10-08 17:51:07 +02:00
parent 244c5711df
commit e888a949c2
13 changed files with 36 additions and 3311 deletions
+8
View File
@@ -1 +1,9 @@
/faq https://radicle.dev/faq 301
/guides/getting-started https://radicle.dev/guides/quick-start/ 301
/guides/getting-started/ https://radicle.dev/guides/quick-start/ 301
/guides/protocol https://radicle.dev/guides/protocol/ 301
/guides/protocol/ https://radicle.dev/guides/protocol/ 301
/guides/seeder https://radicle.dev/guides/seeder/ 301
/guides/seeder/ https://radicle.dev/guides/seeder/ 301
/guides/user https://radicle.dev/guides/user/ 301
/guides/user/ https://radicle.dev/guides/user/ 301
+2 -86
View File
@@ -205,7 +205,7 @@
}
/* On section-based pages, each top-level block rises and fades in, staggered,
when the page mounts. Markdown docs (glossary, guides) are a flat list
when the page mounts. Markdown docs (glossary, download) are a flat list
of paragraphs, so they rise in as a single block instead. */
@keyframes section-rise {
from {
@@ -233,7 +233,7 @@
animation-delay: 350ms;
}
/* Markdown docs (glossary, guides) are a flat element list. Animate the
/* Markdown docs (glossary, download) are a flat element list. Animate the
elements with the same timing (no stagger) so the doc rises in as one block.
This lives on the children, not the reused .markdown-content container, so it
replays on client-side navigation and not only on hard reload. */
@@ -349,90 +349,6 @@
line-height: 1.6;
}
.marketing .guide-hero {
height: 20rem;
object-fit: cover;
border-radius: var(--border-radius-small);
margin-bottom: 2rem;
}
.marketing .guides-markdown .markdown-content {
max-width: min(66rem, 100%);
}
.marketing .guides-markdown .markdown-content > :not(aside) {
max-width: none;
width: min(42rem, calc(100% - clamp(15rem, 26vw, 20rem) - 2rem));
}
.marketing .guides-markdown .markdown-content aside {
position: static;
float: right;
clear: right;
margin-left: 0;
margin-right: 0;
margin-bottom: 1rem;
margin-inline-start: 0;
width: clamp(15rem, 26vw, 20rem);
font: var(--txt-medium-14);
line-height: 1.6;
color: var(--color-text-tertiary);
background: none !important;
overflow-wrap: anywhere;
word-break: break-word;
}
@media (max-width: 72rem) {
.marketing .guides-markdown .markdown-content {
max-width: min(62rem, 100%);
}
.marketing .guides-markdown .markdown-content > :not(aside) {
width: min(38rem, calc(100% - clamp(13.5rem, 30vw, 17rem) - 1.25rem));
}
.marketing .guides-markdown .markdown-content aside {
width: clamp(13.5rem, 30vw, 17rem);
margin-inline-start: 1.25rem;
}
}
@media (max-width: 56rem) {
.marketing .guides-markdown .markdown-content > :not(aside) {
width: min(36rem, calc(100% - clamp(12rem, 32vw, 15rem) - 1rem));
}
.marketing .guides-markdown .markdown-content aside {
width: clamp(12rem, 32vw, 15rem);
margin-inline-start: 1rem;
}
}
@media (max-width: 44rem) {
.marketing .guides-markdown .markdown-content {
max-width: 48rem;
}
.marketing .guides-markdown .markdown-content > :not(aside) {
max-width: 100%;
width: 100%;
}
.marketing .guides-markdown .markdown-content aside {
float: none;
clear: none;
width: auto;
margin: 1rem 0;
padding: 0.75rem 1rem;
background: var(--color-surface-subtle) !important;
overflow-wrap: anywhere;
}
.marketing .guides-markdown .markdown-content aside p {
margin: 0;
}
}
.marketing .markdown-content h1 {
font: var(--txt-bold-48);
letter-spacing: -0.02em;
+1 -1
View File
@@ -496,7 +496,7 @@ export function extractBaseUrl(hostAndPort: string): BaseUrl {
// identifiers are decoded: revision and path segments are deliberately kept
// encoded, because branch and tag lookups are keyed on the encoded name.
function urlToRoute(url: URL): Route | null {
// Normalize a trailing slash (`/guides/protocol/` → `/guides/protocol`) so
// Normalize a trailing slash (`/glossary/` → `/glossary`) so
// both the strict marketing resolver and the lenient repo resolver agree.
// Without this the residual empty segment makes marketing routes 404 while
// repo routes silently absorb it. `|| "/"` keeps the root path intact.
+6 -3
View File
@@ -3,7 +3,6 @@
import CopyCommand from "@app/marketing/CopyCommand.svelte";
import Meta from "@app/marketing/Meta.svelte";
import PlatformBadges from "@app/marketing/PlatformBadges.svelte";
import { link } from "@app/marketing/link";
const features = [
{
@@ -125,8 +124,12 @@
<h2 class="txt-bold-22 get-started-heading">Get started</h2>
<CopyCommand />
<PlatformBadges />
<a href="/guides/user" class="product-link arrow-link" use:link>
Read the user guide <span class="link-arrow link-arrow-right">→</span>
<a
href="https://radicle.dev/guides/user/"
class="product-link arrow-link"
target="_blank"
rel="noopener noreferrer">
Read the user guide <span class="link-arrow link-arrow-up-right">↗</span>
</a>
</section>
+1 -32
View File
@@ -7,28 +7,6 @@
export let page: DocsPage;
export let component: ComponentType;
const guideHeroes: Record<string, { src: string; alt: string }> = {
"guides/getting-started": {
src: "/marketing/images/learn/getting-started.jpg",
alt: "Getting Started",
},
"guides/protocol": {
src: "/marketing/images/learn/protocol.jpg",
alt: "Protocol Guide",
},
"guides/seeder": {
src: "/marketing/images/learn/seeder.jpg",
alt: "Seeder Guide",
},
"guides/user": {
src: "/marketing/images/learn/user.jpg",
alt: "User Guide",
},
};
$: isGuide = page.startsWith("guides/");
$: hero = guideHeroes[page];
$: isGlossary = page === "glossary";
// Copy a term's permalink to the clipboard instead of navigating to it, and
@@ -195,16 +173,7 @@
}
</style>
{#if isGuide}
<div class="guides-markdown">
<MarkdownLayout>
{#if hero}
<img class="guide-hero" src={hero.src} alt={hero.alt} />
{/if}
<svelte:component this={component} />
</MarkdownLayout>
</div>
{:else if isGlossary}
{#if isGlossary}
<div class="doc-glossary" use:copyTermLinks>
<MarkdownLayout>
<svelte:component this={component} />
+5 -1
View File
@@ -73,7 +73,11 @@
<div class="footer-links">
<div class="link-column">
<span class="column-header txt-medium-14">Learn</span>
<a href="/guides/getting-started" class="txt-medium-14" use:link>
<a
href="https://radicle.dev/guides/quick-start/"
class="txt-medium-14"
target="_blank"
rel="noopener noreferrer">
Get started
</a>
<a href="/learn#guides" class="txt-medium-14" use:link>Guides</a>
@@ -1,401 +0,0 @@
---
title: Getting Started with Radicle
subtitle: A quick start guide to getting up and running with Radicle
---
<script>
import Meta from '@app/marketing/Meta.svelte';
</script>
<svelte:head>
<title>Getting Started | Radicle</title>
</svelte:head>
<Meta title="Getting Started | Radicle" description="A quick start guide to installing Radicle, creating your identity, and opening your first issue and patch." canonical="https://radicle.dev/guides/quick-start" />
# Getting started with Radicle
This quick start guide will take you through the basic installation and usage of the Radicle toolchain.
## Installing the Radicle toolchain
Radicle is an ecosystem for code collaboration over a decentralized network.
You can start by installing the command-line toolchain, which includes:
- the Radicle CLI (`rad`): the command-line interface for managing your identity, repositories, issues, and patches
- the Radicle node (`radicle-node`): the daemon that connects to the peer-to-peer network and replicates repositories
- a [Git remote helper] (`git-remote-rad`): the program Git calls to push and fetch from Radicle repositories with the `rad://` URL prefix.
An [installation script](#install-script) is provided, or you can use your favorite package manager from the list below.
You can check that you have successfully installed the toolchain and see what version you are using by running:
```
$ rad --version
```
### Install script
The quickest way to get started:
```
$ curl -sSLf https://radicle.dev/install | sh
```
This installs the toolchain into `~/.radicle/bin` and adds it to your `PATH`.
### From source
> Requires the Rust toolchain.
You can install the Radicle toolchain from source, by running the following
commands from inside this repository:
cargo install --path crates/radicle-cli --force --locked --root ~/.radicle
cargo install --path crates/radicle-node --force --locked --root ~/.radicle
cargo install --path crates/radicle-remote-helper --force --locked --root ~/.radicle
Or directly from our seed node:
cargo install --force --locked --root ~/.radicle \
--git https://seed.radicle.dev/z3gqcJUoA1n9HaHKufZs5FCSGazv5.git \
crates/radicle-cli crates/radicle-node crates/radicle-remote-helper
### Debian / Ubuntu (apt)
```
curl -LO https://radicle.dev/apt/radicle-archive-keyring.deb
sudo apt install ./radicle-archive-keyring.deb
echo "deb [signed-by=/usr/share/radicle/radicle-archive-keyring.asc] https://radicle.dev/apt release main" \
| sudo tee -a /etc/apt/sources.list
sudo apt-get update && sudo apt-get install radicle
```
### Homebrew
```
$ brew install radicle
```
### Arch Linux (pacman)
Install [`radicle`](https://archlinux.org/packages/extra/x86_64/radicle/) via pacman.
### Nix
Install [`radicle-node`](https://search.nixos.org/packages?show=radicle-node&query=radicle) from Nixpkgs.
### Windows
Currently, the best way to install on Windows is building it [from source](#from-source).
### Binary download
Pre-built binaries and verification instructions are available on the [download page](/download).
## Creating your Radicle identity
Before using Radicle, you need to create an identity — a cryptographic key pair that uniquely identifies you and your node on the network:
```
$ rad auth
```
You will be prompted for an **alias** (a human-readable name for your node) and a **passphrase** to protect your key pair:
```
Initializing your radicle 👾 identity
✓ Enter your alias: alice
✓ Enter a passphrase: ********
✓ Creating your Ed25519 keypair...
✓ Adding your radicle key to ssh-agent...
✓ Your Radicle DID is did:key:z6Mkux1...
✓ You're all set.
```
Your [DID] (Decentralized Identifier) is your unique identity across the network.
You can view it at any time with `rad self`.
```
$ rad self --did
did:key:z6Mkux1...
```
> 👻
>
> Your passphrase and private key **cannot be recovered** if lost.
> Store them somewhere safe.
> Losing access to your key means losing your identity and any repositories associated with it.
> 🧠
>
> Radicle uses `ssh-agent` to cryptographically sign data that is pushed to the network.
> After you have initialized your identity, `rad auth` will allow you to add the private key to the `ssh-agent`.
> 🧠
>
> Radicle does not currently support using a single identity across multiple devices, nor rotating keys.
> Each device requires its own identity.
## Your node and the network
Radicle is a decentralized code forge — there is no central server.
You participate in the network by running a node, which connects to other nodes to discover and replicate repositories.
To start your node, run:
```
$ rad node start
```
You can then check that it is running and connected to other nodes:
```
$ rad node status
✓ Node is running with Node ID z6Mkux1… and listening on 0.0.0.0:8776.
╭────────────────────────────────────────────────────────────────────────╮
│ Node ID Address ? Since │
├────────────────────────────────────────────────────────────────────────┤
│ z6Mkv7G… seed.example.xyz:8776 ✓ ↗ 3m │
│ z6MkszH… radicle.example.net:8776 ✓ ↗ 3m │
│ z6Mko5W… garden.example.org:8776 ✗ ↗ 2m │
╰────────────────────────────────────────────────────────────────────────╯
```
The status table shows your connections to other nodes on the network.
A `✓` indicates that your node is connected to the other node.
Conversely, an `✗` indicates that you are disconnected from the other node.
The arrow `↗` indicates an outbound connection (a connection that you initiated), whereas `↘` indicates an inbound connection (one that the other node initiated).
You can stop your node at any time by running:
```
$ rad node stop
```
> 🧠
>
> It is also possible to manage your node as a [systemd] service.
> An example systemd user service is available in the [heartwood repository][systemd-service].
> 🧠
>
> Your node configuration lives in `~/.radicle/config.json` — see the [User Guide](/guides/user) for details on customizing your setup.
[systemd-service]: https://radicle.network/nodes/iris.radicle.network/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/tree/systemd/user/radicle-node.service
## Your first repository
Radicle is built on top of Git, so any existing Git repository can be published to the network.
To publish your first repository, you can navigate to the repository and run `rad init`.
When initializing the repository, you will have an option to choose its “Visibility”.
A `public` repository means that this repository will be announced to the network by your node.
Once the project has been announced and replicated, **deleting this repository is nearly impossible**.
Instead, if this is a repository that you do not wish to announce to the network and/or wish to test out Radicle with,
then choose `private`.
Note that the workflow for a `private` repository differs from `public`, and you can see how it works by [following the User Guide](/guides/user#3-selectively-revealing-repositories).
```
$ cd /path/to/my-project
$ rad init
Initializing radicle 👾 project in .
✓ Name: my-project
✓ Description: A short description of my project
✓ Default branch: main
✓ Visibility: public
✓ Project my-project created.
Your project's Repository ID (RID) is rad:z3gqc…
You can show it any time by running `rad .` from this directory.
✓ Project successfully announced to the network.
```
<aside>The <code>rad init</code> command provides a setup wizard and will prompt you for these details.</aside>
Your repository is now on the Radicle network!
You can see which nodes have replicated your repository by using `rad sync status`.
```
$ rad sync status
╭──────────────────────────────────────────────────────────────────╮
│ Node ID Alias ? SigRefs Since │
├──────────────────────────────────────────────────────────────────┤
│ (you) alice ✓ a1b2c3d 3m ago │
│ z6Mkv7G… seed.example.xyz ✓ a1b2c3d 2m ago │
│ z6MkszH… rad.example.net ✓ a1b2c3d 2m ago │
│ z6Mko5W… garden.example.org ✗ f4e5d6c 1d ago │
╰──────────────────────────────────────────────────────────────────╯
```
A `✓` means the node is in sync with your latest changes, while `✗` means it is out of sync.
To push new changes, just use Git as usual:
```
$ git push
```
If you ever think the node has not announced changes, or that it needs to fetch changes, then you can always manually synchronize with the network:
```
$ rad sync
```
> 🧠
>
> You can also set your repository’s visibility to `private` during `rad init`.
> Private repositories are only replicated to nodes you explicitly allow.
> See the [User Guide](/guides/user#3-selectively-revealing-repositories) for details on managing access.
## Cloning a repository
You can clone any repository from the network using its Repository ID (RID).
For example, to clone Heartwood, the Radicle protocol implementation itself:
```
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
✓ Seeding policy updated for rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 with scope 'followed'
✓ Fetching rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 from z6Mkk4R…SBiyXVM..
✓ Fetching rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 from z6Mksmp…1DN6QSz..
✓ Creating checkout in ./heartwood..
✓ Repository successfully cloned under ./heartwood/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 8 issues · 14 patches │
╰────────────────────────────────────╯
```
This fetches the repository from connected peers and creates a working copy, just like `git clone`.
The difference is that Radicle will first ask the network for the repository, and clone it from the first available node.
## Your first issue
Once you are working within a Radicle repository, it is easy to open an issue, whether your node is running or you are offline.
Running `rad issue open` will open your editor, and you will be prompted to write the title and body of the issue.
Similar to commit messages, the first line is taken as the title of the issue, and below that is the body.
You can easily list issues using `rad issue list`, or show an issue with `rad issue show`.
Here is an example of an issue in the `heartwood` repository:
```
╭───────────────────────────────────────────────────────────────────╮
│ Title contributing: Feedback from the community │
│ Issue b163597fae51a3f076d654294f695803dd5e221c │
│ Author ade z6MkwGo…yS2aagA │
│ Status open │
│ │
│ Derived from the Zulip conversation. │
│ │
│ - [ ] First contribution guide │
│ - [ ] "good first issues" label usage │
│ - [ ] Issue / Patch templates │
│ - [ ] Source code overview map │
│ - [ ] Increase time-to-first-feedback for new contributors │
│ - [ ] Frequent and recurring open session for community to attend │
│ - [ ] Local CI checks │
╰───────────────────────────────────────────────────────────────────╯
```
## Your first patch
At this stage, you will likely want to propose changes to a repository.
As mentioned before, Radicle is built on top of Git, which means your `git` (or `jj`) workflow does not need to change too much.
The `git-remote-rad` binary allows you to push changes to a special reference called `refs/patches` in the `rad` remote.
When the remote helper sees this happening it will prompt you with your editor to write a title and body for the patch.
Similar to commit messages, the first line is taken as the title of the issue, and below that is the body.
The most common Git revision to push to `refs/patches` is the `HEAD` revision.
This will create a patch starting from the commit you are currently on, and the base of the patch will be the latest commit of your default branch (chosen during `rad init`).
From your command line you would run:
```
$ git push rad HEAD:refs/patches
```
In fact, this is so common that we would recommend adding the following to your `.git/config`:
```
[alias]
patch = push self HEAD:refs/patches
```
Which allows you to create a new patch by running `git patch`.
At any point, you can view a repository’s patches using `rad patch`, or you can show a specific one with `rad patch show`.
Here is an example of a patch in the `heartwood` repository:
```
╭──────────────────────────────────────────────────────────────────────────╮
│ Title fetch: Update gix packages │
│ Patch 76f1369e7bab4126e97b175bcddea58f55f3c741 │
│ Author fintohaps (you) │
│ Head aed9c2603884a8cd76c433262657d72289bd36bb │
│ Base caee776c388ffac2ea55cc9d1e3d7fa108ca6df5 │
│ Commits ahead 1, behind 0 │
│ Status open │
│ │
│ Several vulnerabilities were found in the gix packages[^1][^2][^3]. │
│ │
│ Update the packages to their latest versions which include the fixes. │
│ │
│ The only required change is in `ls_refs`. │
│ `RefPrefixes` was introduced and this fixes the issue with `ref-prefix`. │
│ This should now improve the ls-refs stage and only return references │
│ that the client is asking for, as opposed to all references. │
│ │
│ [^1]: https://github.com/radicle-dev/heartwood/security/dependabot/38 │
│ [^2]: https://github.com/radicle-dev/heartwood/security/dependabot/39 │
│ [^3]: https://github.com/radicle-dev/heartwood/security/dependabot/36 │
├──────────────────────────────────────────────────────────────────────────┤
│ aed9c26 fetch: Update gix packages │
├──────────────────────────────────────────────────────────────────────────┤
│ ● Revision 76f1369 @ caee776..aed9c26 by fintohaps (you) 5 hours ago │
╰──────────────────────────────────────────────────────────────────────────╯
```
We never write the perfect set of changes the first time (or at least I do not), so you may need to update a patch after you have modified or added new commits.
The process is similar to before; however, the reference you target with the remote helper should now contain the identifier of the patch.
For example, to update the patch above, you would run:
```
$ git push rad HEAD:patches/76f1369e7bab4126e97b175bcddea58f55f3c741
```
For convenience, another way is to create a branch called `patches/76f1369e7bab4126e97b175bcddea58f55f3c741` and run:
```
$ git push rad patches/76f1369e7bab4126e97b175bcddea58f55f3c741
```
Alternatively, you can create an upstream branch that points to `patches/76f1369e7bab4126e97b175bcddea58f55f3c741`.
In fact, `rad patch checkout` will do this for you.
## Next steps
Now you know enough about Radicle to be dangerous!
If you want to learn more about how to collaborate, set up your own seed, or dive into the protocol details, then here are some useful links:
- **Collaborate** — Learn about issues and patches in the [User Guide](/guides/user#2-collaborating-the-radicle-way)
- **Install [Radicle Desktop]** — A simple, intuitive way to interact with the Radicle network
- **Run a seed node** — Help the network by seeding repositories with the [Seeder Guide](/guides/seeder)
- **Understand the protocol** — Dive into how Radicle works in the [Protocol Guide](/guides/protocol)
We are a growing community who love to help out when we can.
If you need help or want to chat to us further about everything Radicle, join us on [Zulip](https://radicle.zulipchat.com)!
[Git remote helper]: https://git-scm.com/docs/gitremote-helpers
[DID]: https://www.w3.org/TR/did-1.0/
[systemd]: https://systemd.io/
[Radicle Desktop]: /install
-532
View File
@@ -1,532 +0,0 @@
---
title: Radicle Protocol Guide
subtitle: How Radicle works under the hood
---
<script>
import Meta from '@app/marketing/Meta.svelte';
</script>
<svelte:head>
<title>Protocol Guide | Radicle</title>
</svelte:head>
<Meta title="Protocol Guide | Radicle" description="Gossip, replication, identity, and the data structures behind Radicle." canonical="https://radicle.dev/guides/protocol" />
# Radicle protocol guide
Heartwood, the latest generation of the [Radicle](/) protocol establishes a sovereign data network for code collaboration and publishing, built on top of [Git][git]. In Radicle, users maintain local copies of their repositories of interest and related social artifacts such as issues and patches. Instead of depending on a centralized service like GitHub, each participant in Radicle operates a node that is capable of running on a personal computer, and is connected via a peer-to-peer network.
Nodes, identified by [public keys][pkc], host and synchronize Git repositories across the network, using a novel gossip protocol for peer and repository discovery, alongside Git’s [protocol][git-v2] for data replication. In combination, this allows nodes to locate, replicate, and verify any repository published to the network, provided at least one other peer seeding the repository is online. Since Radicle is built on Git, it can easily interoperate with existing tools and workflows.
Radicle’s architecture is [local-first][local-first], ensuring continuous access to one’s repositories directly from their device, regardless of internet connectivity. Repositories have unique identifiers and are self-certifying, meaning all actions, from committing code to adding a comment to an issue, are performed locally and [cryptographically signed][signing], allowing peers to verify authenticity and data provenance once propagated to the network. This allows trust to be established without reliance on a centralized authority.
Radicle is designed for extensibility, allowing for diverse use cases without necessitating modifications at the protocol level. This guide delves into the capabilities of Radicle’s initial release, which has a focus on code collaboration and code publishing. Nonetheless, a range of other applications is foreseen in the future and possible today, including knowledge sharing, project coordination, and data set collaboration.
## Introduction
Git, the most widely-used distributed version control system, enables users to maintain and modify personal copies of data repositories, commonly for source code control. Its structure for direct user-to-user collaboration, while feasible, is often cumbersome since Git primarily focuses on version control rather than collaboration. As a result, users frequently opt for centralized [forges][forges] like GitHub or GitLab, which offer enhanced interfaces and collaborative tools on top of Git, such as project management and code review. This dependency, however, can result in vendor lock-in since it places a project’s social artifacts (e.g. issues, comments, pull requests) out of user control, potentially compromising data sovereignty and other user freedoms.
Traditional self-hosted forges like [Gitea][gitea] or [Forgejo][forgejo] provide more sovereignty but often lead to fragmented collaboration environments, as users must create separate profiles for each hosted instance. This simultaneously limits a project’s exposure to the wider open source community, a key advantage of platforms like GitHub, which have grown significantly due to network effects. This isolation can impact the visibility and collaborative potential of projects that choose traditional self-hosted solutions.
The Radicle protocol, in contrast, extends Git’s capabilities with a decentralized identity system, novel gossip protocol, and integrated social artifacts, forming a _self-hosted network for code collaboration_. Radicle locates, serves, and replicates Git repositories -- including artifacts -- across a [peer-to-peer][peer-to-peer] network while maintaining data authenticity via cryptographic signatures, so peers can directly exchange data without the need for a trusted third party. This enables communities to both self-host and share their repositories across a distributed protocol, contributing to the emergence of a new sovereign network for code collaboration and more.
## Nodes
Radicle is a [peer-to-peer][peer-to-peer] system, which means that there is no traditional client-server model. Peers on the Radicle network are referred to as _nodes_, and are indistinguishable from users at the protocol level. Nodes, identified by their [Node ID](#node-identifiers-nids) (NID) -- an [Ed25519][ed25519] public key -- are responsible for seeding Git repositories, each identified by a unique [Repository ID](#repository-identifier-rid) (RID). The seeding process involves both hosting the repository data and synchronizing changes with other nodes. Every Radicle user, irrespective of their role or activity, runs a node on their device. No specialized equipment is necessary for operating a node as a typical end-user; nodes can run on a personal computer without requiring a server.
<aside> In the peer-to-peer model, nodes act as <em>both clients and
servers</em>. </aside>
### Seeding repositories
Whenever a user clones, initializes, or opts to seed a repository, their node’s seeding policy is changed to reflect their choices. This policy establishes the repositories they are interested in and sets the rules for data retention, and synchronization, allowing users to have direct control over which repositories are kept on their device and offered to the network.
The typical end-user may choose to only seed the repositories they are actively collaborating on. However, more dedicated users may opt to run an always-on **seed node**, offering their infrastructure to the wider Radicle network or to their community. Seed nodes significantly enhance the network’s capacity to provide continuous access to a broad range of repositories. They can vary in their seeding policies, from _public seed nodes_ that openly seed all repositories to _community seed nodes_ that selectively seed repositories from a group of trusted peers.
<aside> For more details on how to run a seed node, refer to the <a
href="/guides/seeder">Radicle Seeder’s Guide</a>.
</aside>
### Node identifiers (NIDs)
<aside class="kicker"> DIDs are a new identifier standard established by the
W3C. </aside>
Radicle node identity is based on [public-key cryptography][pkc], which makes it easy to verify the authenticity of messages within the network through [digital signatures][signing]. This also allows for consistent identification even as a user’s physical address varies. When setting up a node, users generate their unique key pair, an [Ed25519][ed25519] public and private key, the public part of which is encoded and shared as a [Decentralized Identifier][did] (DID). Creating a node identity requires no permission or coordination: the key pair can be created while offline without providing an email address or any personal identifying information.
<aside> DIDs enable verifiable, self-sovereign digital identities that are
fully under the control of the DID subject, independent of any centralized
registry, identity provider, or certificate authority. </aside>
<figure>
<pre class="center">did:key:<span class="highlight">z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK</span></pre>
<figcaption>Example Node ID encoded as a DID using the <a
href="https://w3c-ccg.github.io/did-method-key/"><code>did:key</code>
method</a>.</figcaption>
</figure>
It is important to safeguard one’s private key, as if it is either lost or compromised, one will have to generate a new node identity. A changeable, non-unique **alias** can optionally be associated to each node, for easier human identification across the network.
<aside> In the Radicle protocol, the terms “Node ID”, “Peer ID”, and “Public
Key” all refer to the same thing and can be used interchangeably. </aside>
### Running a node
To run a node and connect to the network, users install Radicle client software that is lightweight and suitable for use on both end-user devices and seed nodes. The reference implementation can be found in the Radicle [Heartwood][heartwood] repository, and is actively maintained by a small team of engineers.
<aside> Radicle’s reference implementation is written in <a
href="https://www.rust-lang.org/">Rust</a>, a high-performance, memory-safe
language. </aside>
The Radicle _stack_ is comprised of both the network client and a command line interface (CLI), which can be optionally supplemented with a web frontend. Radicle is released under the open source MIT and Apache 2.0 licenses, to encourage the development of diverse clients and applications. All client software adheres to the Radicle protocol specification, as outlined in the [Radicle Improvement Proposals][rips] (RIPs) repository, ensuring consistent functionality across implementations.
<aside> For a guided install of the Radicle stack, check out the <a
href="/guides/user">user guide</a>. For reproducible binaries and verification, see the <a href="/download">download</a> page. </aside>
## Peer-to-peer protocol
Radicle adopts a local-first, peer-to-peer (P2P) architecture, which draws inspiration from Secure Scuttlebutt (SSB) and Bitcoin’s [Lightning Network][ln].
Nodes on the Radicle network subscribe to repository data they are interested in, and peers announce updates that in turn trigger fetches for the underlying content. Just like SSB and Lightning, updates are [gossiped][gossip] on the network until they reach all interested peers.
Peer connections in Radicle are secured thanks to a [Noise protocol][noise] handshake. Radicle uses the [Noise XK][noise-xk] pattern specifically, just like the Lightning Network with the Node ID as the _static key_. This requires nodes to know the Node IDs of their peers before connecting to them, which takes place through the exchange of peer information over the gossip protocol.
Unlike SSB’s focus on social networking via append-only logs, Radicle focuses on code collaboration by incorporating Git’s [object model][git-odb] and [transfer protocol][git-pack] into a peer-to-peer context. This architecture not only leverages Git’s proven efficiency and reliability but also gives users complete autonomy over their social artifacts. Radicle’s peer-to-peer architecture, in contrast to federated systems, ensures no centralized points of failure, allowing the network to persist as long as users operate nodes.
<figure class="diagram">
<img src="/marketing/images/guides/p2p-network.svg" alt="A set of connected peers forming a gossip network" />
<figcaption>A set of connected peers forming a gossip network.</figcaption>
</figure>
### Gossip protocol
The Radicle networking layer is designed as a gossip protocol, where messages are relayed between peers to build routing tables that aid in repository discovery and replication. The core functionality is achieved with three message types, each fulfilling a distinct role:
**Node Announcements** are used for broadcasting Node IDs and physical addresses on which a node is publicly reachable, to facilitate peer discovery.
| Node Announcement | |
| ----------------- | ------------------------------------- |
| **`version`** | `u8` - Supported protocol version |
| **`features`** | `u64` - Advertised node capabilities |
| **`timestamp`** | `u64` - Message timestamp (unix time) |
| **`alias`** | `u8[]` - Non-unique alias (UTF-8) |
| **`addresses`** | `Address[]` - External addresses |
| **`nonce`** | `u64` - Nonce used for DoS protection |
| **`agent`** | `u8[]` - User-agent (UTF-8) |
**Inventory Announcements** are used for broadcasting repository inventories and constructing the routing table which maps out what repositories are hosted where.
| Inventory Announcement | |
| ---------------------- | ------------------------------------- |
| **`inventory`** | `RepoID[]` - Repository inventory |
| **`timestamp`** | `u64` - Message timestamp (unix time) |
**Reference Announcements** are used for broadcasting updates to repositories, relayed only to nodes interested in the relevant repository.
| Refs Announcement | |
| ----------------- | ------------------------------------------------------- |
| **`rid`** | `RepoID` - Repository that was updated |
| **`refs`** | `{NodeID, OID}[]` - Updated signed refs (`rad/sigrefs`) |
| **`timestamp`** | `u64` - Message timestamp (unix time) |
<aside> <code>OID</code> stands for <em>Object ID</em> and represents the SHA-1
hashes used by Git to identify objects.</aside>
To prevent endless propagation, nodes drop any message already encountered. However, for the sake of broadcasting messages to new nodes, gossip messages may be temporarily stored and replayed to nodes joining the network for the first time, or after a long period of being offline.
Each announcement includes the originating _Node ID_ along with a _cryptographic signature_ and _timestamp_, allowing network participants to verify the authenticity of messages before relaying them to peers.
<figure>
<img src="/marketing/images/guides/announcement-msg.svg" alt="Announcement message structure" />
<figcaption>Announcement message structure.</figcaption>
</figure>
> **Tip**: Refer to [RIP-1][rip-1] to learn more details about Radicle’s networking protocol.
### Transport encryption & privacy
Connections between peers in the Radicle network are encrypted using a [Noise][noise] protocol handshake. This begins with two peers performing a [Diffie-Hellman][ecdh] key exchange to agree on a shared session key that is used for the duration of the connection.
Radicle uses the [XK][noise-xk] handshake pattern, which requires the connection responder’s _static key_ to be known in advance by the initiator. This _pre-sharing_ takes place over the gossip network via the `NodeAnnouncement` message, since the static key is simply the Node ID.
<aside> The Noise framework calls the node that is receiving an inbound
connection the <em>responder</em>, and the node that is initiating the
connection the <em>initiator</em>. </aside>
<figure class="diagram">
<img src="/marketing/images/guides/noisexk-1.svg" alt="Noise XK handshake phase 1" />
</figure>
Once the static key is known, a connection to the node can be initiated securely, by generating an ephemeral key from the static key, using Diffie-Hellman. The last step involves the initiating node sending its own static key over the secure channel.
<figure class="diagram">
<img src="/marketing/images/guides/noisexk-2.svg" alt="Noise XK handshake phase 2" />
</figure>
After the handshake phase is completed, all data exchanged between peers is fully encrypted and benefits from strong [forward secrecy][fs], ensuring secure and private communications across the network.
> **Tip**: Radicle also supports [Tor][tor] addresses. Users can leverage Tor to hide their IP address from peers, and connect to `.onion` addresses on the Tor network.
### Replication via Git
While gossip is used to exchange metadata, actual repository data is transferred via replication using the [Git protocol][git-pack]. The process begins with a node establishing a secure connection to one or more of the repository’s seeds, upon receiving a _reference_ or _inventory_ announcement of interest.
Once connected, the node initiates a Git _fetch_ protocol, which involves negotiating which objects should be sent or skipped by the remote node. The objects are then downloaded into the node’s storage, making them accessible to other nodes via the same process.
Since Radicle uses a [framing][framing] protocol for all its sessions, the fetch protocol is able to take place over the same physical connection between nodes as the gossip protocol. This allows for a more efficient use of resources and avoids certain problems with [NATs][nat]. Although Git’s protocols are typically connection-based, Radicle’s design allows for multiple concurrent Git fetches to take place over a single connection.
<aside> The idea of re-using a network connection for multiple concurrent
protocols is called <a
href="https://en.wikipedia.org/wiki/Multiplexing">multiplexing</a>. </aside>
<figure class="diagram">
<img src="/marketing/images/guides/multiplexing.svg" alt="Connection multiplexing in Radicle" />
<figcaption>Connection multiplexing in Radicle.</figcaption>
</figure>
### Bootstrap nodes
A node joining the network for the first time will not know any peers. Hence, it’s useful to pre-configure network clients with addresses of well-known nodes that can be used to initiate or _bootstrap_ the peer discovery process and build an address book.
Radicle’s reference implementation is pre-configured with two bootstrap nodes that are connected to if the address book is empty: `iris.radicle.network` and `rosa.radicle.network`. These are nodes run by the Radicle team and have large address books that are shared with connecting peers.
In the bootstrapping process, nodes connect to an initial set of bootstrap nodes and once they establish a connection, use the regular peer discovery mechanism to find more peers.
### Federation vs. peer-to-peer
Federation allows for a degree of sovereignty, as each node can set its content policies, but user experience and identity are ultimately tied and mediated by these nodes’ administrators rather than by the users themselves.
> **federation** /ˌfɛdəˈreɪʃn/ _n._
>
> _(Computing)_ A system architecture where multiple independent servers or nodes operate under a common set of standards and protocols, allowing them to share data, resources, and functionalities across boundaries while maintaining autonomy. This model enables interoperability and collective services among diverse systems without fully centralizing control, thereby enhancing privacy, scalability, and resilience. Each node in a federated network can set its policies, manage its users, and control its data.
<aside> Examples of federated systems include <a href="https://atproto.com">AT
Protocol</a>, <a href="https://joinmastodon.org/">Mastodon</a> and plain old
E-mail. </aside>
Although federated models promote a level of decentralization, they face unique challenges, such as when node operators decide to block other nodes, taking that choice away from its users and restricting the free flow of information.
Federated systems also face challenges related to incentives; specifically, when the operational costs of maintaining a node exceed the perceived benefits, node operators are often compelled to shut down. This can disrupt access for users, undermining the platform’s reliability and the continuity of service.
While Radicle seed nodes face similar challenges, this has little bearing on the end user: seed nodes are interchangeable and offer an undifferentiated service; they are not tied to a user’s identity or access to the network.
<figure class="diagram">
<img src="/marketing/images/guides/federation-vs-p2p.svg" alt="Federation vs. peer-to-peer" />
<figcaption>Federation (left) vs. peer-to-peer (right).</figcaption>
</figure>
## Repositories
**Repositories** are central to the Radicle network, serving as the primary data abstraction and object shared between peers. A repository in Radicle is fundamentally a Git repository, supplemented with a unique repository identifier (RID) and metadata essential for validating the authenticity of its contents.
Radicle repositories, which can be either public or private, can accommodate diverse content including source code, documentation, and arbitrary data sets. All repositories are initialized with an [identity document](#identity-document) from which a unique Repository ID (RID) is derived.
The identity document is where repository permissions and ownership are defined, as well as identifying metadata such as name and description.
### Delegates
Repositories are managed and owned by what are called _delegates_. A delegate is an individual, group, or bot, identified by a [DID][did]. Delegates are responsible for critical tasks such as merging patches, addressing issues, and modifying repository permissions. A repository always begins with one delegate, its creator, and can eventually grow to multiple delegates.
### Identity document
Before a repository can be published on Radicle, it needs to be initialized with an _identity document_. This JSON document, stored under the `refs/rad/id` reference in Git, encapsulates key metadata such as the repository’s name, description, and default branch. It also includes the DIDs of the repository’s delegates and the _threshold_ of delegate signatures required to authorize changes to the repository’s default branch.
<aside> The identity document is stored in Canonical JSON form. Canonical JSON
form standardizes JSON encoding to ensure identical byte representation for the
same data structures, making it particularly useful for cryptographic
operations like hashing. </aside>
Here’s an example of the identity document for the [heartwood][heartwood] repository:
```json
{
"delegates": ["did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"],
"threshold": 1,
"payload": {
"xyz.radicle.project": {
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"defaultBranch": "master"
}
}
}
```
### Private repositories
Radicle supports **private repositories** where access is restricted to a designated group of trusted peers. This is achieved by setting the `visibility` attribute in the identity document. For example, the following snippet sets the visibility to _private_, while also allowing a specific peer to have access to the repository.
```json
{
...
"visibility": {
"type": "private",
"allow": ["did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk"]
}
}
```
This ensures only nodes in the privacy set can replicate and access the data, maintaining confidentiality. While the data is not encrypted at rest, these repositories rely on selective replication through the _allow list_ for privacy, which renders them invisible and inaccessible to other nodes in the Radicle network.
Note that repository delegates _always_ have access to their private repositories.
### Repository identifier (RID)
To ensure uniqueness and easy identification of repositories, a stable and globally unique identifier, known as the Repository Identifier (RID), is assigned to each repository. The RID is deterministically derived from the initial version of the repository’s identity document. This process involves using Git’s `hash-object` function to produce a 160-bit SHA-1 digest of the document. This is then encoded using [multibase][mb] encoding with the `base-58-btc` alphabet, and prefixed with `rad:`, making it a valid [URN][urn]:
<figure>
<pre class="center">rad:<span class="highlight">z3gqcJUoA1n9HaHKufZs5FCSGazv5</span></pre>
<figcaption>Example Repository ID for the heartwood project.</figcaption>
</figure>
Since the RID is derived from the _initial_ version of the repository’s identity document, the document is able to change while the RID remains the same.
[urn]: https://datatracker.ietf.org/doc/html/rfc8141
[mb]: https://w3c-ccg.github.io/multibase/
> **Tip**: Refer to [RIP-2][rip-2] for more details about how repository identity works in the Radicle protocol.
## Local-first storage
Storage is designed in such a way that it’s easy to transfer data between peers over the network using an unmodified Git protocol. Radicle repositories are simply Git repositories stored in a special location on disk. Peer data is stored within the same repository using Git [namespaces][gns], where Node IDs are used as the namespace. This allows storage to be managed through a partitioned approach where each user maintains their own _namespace_ of a repository, as well as namespaces of other users they have an interest in, all within the same Git repository. These copies are then shared among users across the network.
We can view a peer’s namespace as their “soft-fork” of the repository. It is their own space for making changes to the repository without affecting other namespaces. The peer is the _sole owner and writer_ of this namespace, identified, and verified, by their Node ID.
### Working vs. stored copy
Storage is accessed directly by the node to report its inventory to other nodes, and by the end user through either specialized tooling or the `git` command line tool. Users are typically interacting with two repository copies: the _working copy_, and a remote _stored copy_ that is interacted with via `git push` and `git fetch`, using Radicle’s [git-remote-helper][grh].
This workflow is akin to what most developers are used to, when synchronizing changes between their working copy, and the `origin` remote, which is typically a repository on a hosted Git forge.
Changes to the stored copy are automatically propagated to the network when the user is connected to the internet, but can also be made while offline. This local-first design not only enhances the user experience by making offline work frictionless, but also eliminates the need for centralized servers.
<figure class="diagram">
<img src="/marketing/images/guides/working-vs-stored.svg" alt="Synchronizing the working copy with the stored copy of a repository" />
<figcaption>Synchronizing the working copy with the stored copy of a repository.</figcaption>
</figure>
### Storage layout
Radicle’s storage layout is designed to support multiple repositories and multiple peers per repository. Each repository is a _bare_ Git repository, stored under a common base directory, identified uniquely with its Repository ID or RID. Instead of each of the repository’s peers storing data in a separate Git repository with a separate [object database][git-odb] (ODB), peer data is stored within the same Git repository using Git [namespaces][gns].
<aside> A <em>bare</em> Git repository is a repository stripped of its working
directory, containing only the version history (the contents of the
<code>.git</code> directory). It’s used primarily as a remote repository that
developers can push to and pull from, but cannot directly edit files or commit
changes in. </aside>
For each peer, including the local peer, their unique Node ID (NID) is used as the namespace. Thus, each peer has its own namespaced references (eg. `refs/heads`, and `refs/tags`), while sharing the underlying objects (i.e. commits and blobs) with other namespaces via a shared object database. This design ensures only one copy of each object is stored across all repository namespaces.
Since the underlying storage uses Git, the storage layout below is represented as a file tree on the file-system, with `<storage>` representing the storage root, or top-level directory under which all repositories are stored on a user’s device. For every repository, each peer associated with that repository must have a separate, logical Git source tree -- which contains all the usual reference categories. This _logical repository_ is also known as the repository _namespace_ or _view_, and allows nodes to maintain local data for all peers in the same physical repository.
```
<storage> # Storage root containing all repositories
├─ <rid> # Storage for first repository
│ └─ refs # All Git references locally stored
│ └─ namespaces # All peer source trees or "namespaces"
│ ├─ <nid> # First node's source tree
│ │ └─ refs # First node's Git references
│ │ ├─ heads # First node's branches
│ │ │ └─ master # First node's master branch
│ │ └─ tags # First node's tags
│ │
│ └─ <nid> # Second node's source tree
│ ├─ refs # Second node's references
│ └─ ...
├─ <rid> # Storage for second repository
│ ...
└─ <rid> # etc.
...
```
Though this storage tree is browsable by the user with standard file system commands, it is not meant to be interacted with directly by users, for risk of corrupting the data. Additionally, Git is free to pack the objects, which means they may not always appear as individual files.
> **Tip**: Refer to [RIP-3][rip-3] to learn more about storage in the Radicle protocol.
### Git URL scheme
The Radicle protocol uses its own URL scheme to point to specific repository namespaces in the network. This allows the Git `fetch` and `push` commands to operate on the correct namespace when fetching or pushing code.
<figure>
<pre class="center">rad://<span class="highlight">z42hL2jL4XNk6K8oHQaSWfMgCL7ji</span>/<span class="highlight-secondary">z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi</span></pre>
<figcaption> Example URL for repository <code>z42hL2jL4XNk6K8oHQaSWfMgCL7ji</code> and
peer <code>z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi</code>. </figcaption>
</figure>
The Radicle [remote helper][grh] is what allows Git to interpret URLs with the `rad://` scheme. By using this scheme, the user instructs Git to invoke the `git-remote-rad` executable during `git push` or `git fetch`, which allows the user to interact with the network through the storage layer.
For example, the above URL would map a push to the `master` branch to the following path under the local storage root:
```
/<rid>/refs/namespaces/<nid>/refs/heads/master
```
If a Node ID is not specified in the URL, Git will interact with the repository’s canonical references, also know as the authoritative repository state. This is the state agreed on by the repository [delegates](#delegates).
<figure>
<pre class="center">rad://<span class="highlight">z42hL2jL4XNk6K8oHQaSWfMgCL7ji</span></pre>
<figcaption> Example URL for repository
<code>z42hL2jL4XNk6K8oHQaSWfMgCL7ji</code>’s canonical
references.</figcaption>
</figure>
## Trust through self-certification
Unlike centralized forges such as GitHub, where repositories are deemed authentic based on their location (e.g. `https://github.com/bitcoin/bitcoin`), in a decentralized network like Radicle, location is not enough. Instead, we need a way to automatically verify the data we get from _any given location_. This is because peers in a decentralized network may be dishonest. Radicle’s approach hinges on the self-certifying nature of its repositories, anchored in the repository [identity document](#identity-document).
### Canonical branches
When repositories are hosted in a known, trusted location, updating the repository’s canonical branch (eg. `master`) is simply a matter of pushing to that repository’s branch. Permission to push is granted to a small set of maintainers, and any one maintainer is allowed to update the branch.
In Radicle, lacking a central location where repositories are hosted, the canonical branch is established _dynamically_ based on the signature **threshold** defined in the repository’s identity document. For example, if a threshold of two out of three delegates is set, with the default branch set to `master`, and two delegates have pushed the same commit to their `master` branches, that commit is recognized as the authoritative, canonical state of the repository.
> **Note**: Currently, only the branch specified under the `defaultBranch` attribute of the identity document is set automatically based on a signature threshold. In the future, additional branches may be supported.
### Self-certifying repositories
Together, a repository’s RID and its identity document create a cryptographic proof that serves as the basis for verifying all repository states leading up to its current state. For this reason, we say that Radicle repositories are _self-certifying_: the process of verification doesn’t require any inputs other than the repository itself.
For repositories to be self-certifying, delegates authenticate every change to the repository data and metadata via cryptographic signatures. This includes all Git references published to the network.
#### Signed refs
To enable the verification of Git references beyond commits to the source code, Radicle automatically signs the entirety of a node’s references every time they change. This signature is then placed in a Git blob under a special branch referenced under `refs/rad/sigrefs`, along with the references that were signed.
<figure>
<pre>9767b485c2aad1e23097d2b5165287ba84cfa452 refs/heads/master
f3eaa7454e3a4714885905ae99f616fc7895b5fa refs/cobs/xyz.radicle.patch/fe31d5b6049583a42c21a543545d182b893aa4a0
0590b78ee42b39087983e4de04164065e5aa11bc refs/cobs/xyz.radicle.patch/ffbb812ad6e7fe1c5c610b1246ca5ca9d7d16027</pre>
<figcaption>Example <code>refs</code> blob under a user’s <code>refs/rad/sigrefs</code> branch.
</figcaption>
</figure>
<aside> The signed references are in the same format as the <code>git
show-ref</code> command. </aside>
Signed refs are key to establishing a repository’s canonical state and are updated whenever there are changes to a repository.
Given an RID and a Radicle repository clone, anyone can retrieve the initial identity document and authenticate all subsequent repository updates without a trusted third party. This verification model draws inspiration from [The Update Framework][tuf] (TUF), a framework designed to secure software update systems.
## Collaborative Objects
In the Radicle protocol, Collaborative Objects (COBs) play an important role in supplementing Git with social artifacts such as issues, code reviews and discussions, which are not inherently supported by Git. Typically, these artifacts are only found on centralized platforms like GitHub or GitLab, or their self-hosted counterparts.
In Radicle, COBs allow for social artifacts to be stored directly inside a repository, and replicated between peers. This means that social artifacts inherit the same properties as source code: they are local-first, user-owned, and cryptographically signed.
Radicle includes three predefined collaborative object types to support code collaboration: _Issues_, _Patches_, and _Identities_, but users have full control to customize them or extend Radicle with entirely new COB types.
- **Issues** (`xyz.radicle.issue`) are used for tracking bugs or feature requests, and support discussions, labeling and assigning.
- **Patches** (`xyz.radicle.patch`) are used to propose changes to a branch, and support reviews, versioning of changes and discussions.
- **Identities** (`xyz.radicle.id`) are used to represent identity documents.
<aside> The repository’s identity document is entirely defined and managed
through an <em>id</em> COB. </aside>
COBs are identified by a unique type name in [reverse domain name notation][reverse-dns] and a unique Object ID.
### Concurrency & consistency
In a system with no central server, operation concurrency is the norm. If two users comment on an issue at the same time, those comments will reach peers in the network at different times and in different orders. To maintain a good user experience, it’s important that these factors don’t determine the issue’s final state. All users must eventually converge to the same exact state and see the same thing.
To achieve this, Radicle makes use of Git’s native synchronization primitives, and encodes COBs as a set of commits in a [directed acyclic graph][dag] (DAG). The identity document is represented by one such commit graph that is disjoint from any other COB or source code branch. Issues are represented as a graph that points to the identity document graph – allowing for inspection of the document at evaluation. Patches are a graph that combines pointing to both the identity document and source code commits. This combination allows for checking if the patch is merged, for example.
This representation gives Radicle a few things for free:
1. Data integrity is guaranteed by Git.
2. Synchronization is handled by Git.
3. Causal dependencies can be modeled as commit parent-child relationships.
It may be useful to think of Radicle’s usage of Git commit histories as a form of [conflict-free replicated data type][crdt] (CRDT). When the histories of two peers are synchronized, the commit graphs are simply _unioned_ with each other in a non-destructive, idempotent way.
<aside> <strong>Idempotence</strong> is the property of certain operations in
mathematics and computer science whereby they can be applied multiple times
without changing the result. </aside>
<figure class="diagram">
<img src="/marketing/images/guides/cob-dag.svg" alt="Git DAG representing an Issue COB" />
</figure>
Then, to materialize the state that is displayed to the user, this new graph is _reduced_ in [topological order][topo], starting from the root of the graph and going up to the tips. This ordering happens to be [causally consistent][causality], ensuring that changes that have observed other changes are traversed in causal order. Since topological ordering may yield _partial_ orders in the face of concurrency, a merge function is also defined, the simplest of which is to traverse the partially-ordered graph vertices sorted by their commit hash. If a merge function cannot be defined, a COB may be configured to treat this as a _conflict_ which can be bubbled up to the user.
<aside> A <em>reduce</em> or <em>fold</em> operation is a way of getting a
single value by applying a function to a list of values. </aside>
In summary, this mechanism supports multiple users independently interacting without coordination. Each COB records the initial version of an object and tracks all subsequent modifications made across the network. Each modification is stored as a separate Git _commit_ object to ensure that the CRDT change graph is compatible with Git’s fetch protocol. To retrieve the current state of an object, the system replays all the changes to the object in a deterministic and causally-consistent order.
### Extending COBs
Radicle’s predefined COB types are stored under the `refs/cobs` hierarchy. These are associated with unique namespaces, such as `xyz.radicle.issue` and `xyz.radicle.patch`, to prevent naming collisions.
This hierarchical arrangement under `refs/cobs` not only houses Radicle’s predefined COBs but also accommodates user-defined ones. For example, if a user or organization were to define a new COB type under their domain, it might look something like `com.acme.task`, for some hypothetical “task” COB under the `acme.com` domain.
This extensibility allows for an unlimited set of new collaboration primitives to be defined by users, without requiring coordination with the broader network or user-base.
## Conclusion
The Radicle Heartwood protocol introduces a new approach to code collaboration and hosting rooted in sovereignty. Built upon Git’s well-established protocol, Radicle can easily interoperate with existing systems and workflows that are already familiar. Users have full control and ownership over their identity and data. Repositories are self-certifying data structures, meaning updates are cryptographically signed and can be verified by anyone, without needing a trusted third party. Every user in Radicle is self-hosting while remaining connected to a wider network. Radicle is highly extensible with its Collaborative Objects that enable full control and customization over workflows and datatypes, while also opening the possibility for Radicle to support a wider range of functionalities, potentially extending far beyond code collaboration.
<!-- References -->
[gitea]: https://about.gitea.com/
[forgejo]: https://forgejo.org/
[git]: https://git-scm.com/
[gns]: https://git-scm.com/docs/gitnamespaces
[grh]: https://git-scm.com/docs/gitremote-helpers
[git-pack]: https://git-scm.com/docs/pack-protocol
[git-odb]: https://git-scm.com/book/en/v2/Git-Internals-Git-Objects
[git-v2]: https://git-scm.com/docs/protocol-v2
[tor]: https://torproject.org/
[ed25519]: https://ed25519.cr.yp.to/
[reverse-dns]: https://en.wikipedia.org/wiki/Reverse_domain_name_notation
[tuf]: https://theupdateframework.github.io/specification/latest/
[noise]: http://www.noiseprotocol.org/noise.html
[noise-xk]: https://noiseexplorer.com/patterns/XK/
[ssb]: https://en.wikipedia.org/wiki/Secure_Scuttlebutt
[ln]: https://en.wikipedia.org/wiki/Lightning_Network
[gossip]: https://en.wikipedia.org/wiki/Gossip_protocol
[causality]: https://en.wikipedia.org/wiki/Causal_consistency
[dag]: https://en.wikipedia.org/wiki/Directed_acyclic_graph
[topo]: https://en.wikipedia.org/wiki/Topological_sorting
[rip-1]: https://radicle.network/nodes/seed.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/0001-heartwood.md
[rip-2]: https://radicle.network/nodes/seed.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/0002-identity.md
[rip-3]: https://radicle.network/nodes/seed.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/0003-storage-layout.md
[nat]: https://en.wikipedia.org/wiki/Network_address_translation
[framing]: https://en.wikipedia.org/wiki/Frame_(networking)
[fs]: https://en.wikipedia.org/wiki/Forward_secrecy
[ecdh]: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
[rips]: https://radicle.network/nodes/seed.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82
[heartwood]: https://radicle.network/nodes/seed.radicle.xyz/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
[did]: https://www.w3.org/TR/did-core/
[seed]: /guides/seeder
[peer-to-peer]: https://en.wikipedia.org/wiki/Peer-to-peer
[forges]: https://en.wikipedia.org/wiki/Forge_(software)
[local-first]: https://www.inkandswitch.com/local-first/
[signing]: https://en.wikipedia.org/wiki/Digital_signature
[pkc]: https://en.wikipedia.org/wiki/Public-key_cryptography
[crdt]: https://en.wikipedia.org/wiki/Conflict-free_replicated_data_type
<style>
:global(.markdown-content figure) {
margin: 1.5rem 0;
}
:global(.markdown-content figcaption) {
font-size: 0.85em;
color: var(--color-text-muted, #666);
margin-top: 0.5rem;
}
:global(.markdown-content .center) {
text-align: center;
}
:global(.markdown-content .highlight) {
color: var(--color-primary, #4ade80) !important;
}
:global(.markdown-content .highlight-secondary) {
color: var(--color-secondary, #60a5fa) !important;
}
</style>
-528
View File
@@ -1,528 +0,0 @@
---
title: Radicle Seeder Guide
subtitle: How to run a Radicle seed node
---
<script>
import Meta from '@app/marketing/Meta.svelte';
</script>
<svelte:head>
<title>Seeder Guide | Radicle</title>
</svelte:head>
<Meta title="Seeder Guide | Radicle" description="Run a public seed node to keep repositories available 24/7." canonical="https://radicle.dev/guides/seeder" />
# Radicle seeder guide
To seed is to give back. By seeding repositories on the Radicle network, you offer bandwidth, storage, and data availability to Radicle users.
In this guide, we’ll go through the various steps required to set up a Radicle _seed node_ on a Linux system. Seeding only requires an internet connection, a public, static IP address, and modest hardware.
We’ll cover public seed nodes with an _open_ seeding policy, as well as community seed nodes with more _selective_ policies.
## Introduction
The term _seed node_ originally comes from [BitTorrent][bittorrent]. In the BitTorrent protocol, nodes that possess the data for a given torrent file and begin uploading it to peers are called _seeders_.
In Radicle, while all users contribute to the network by seeding data, the term _seed node_ specifically refers to a server that is accessible through the internet and hosts content for peers. Seed nodes need to remain online and accessible to provide their service to the network.
### The need for seed nodes
While a peer-to-peer network without seed nodes is feasible, it is impractical. This is because regular “user” nodes go online and offline all the time, so finding a user from which to download a certain piece of content can be challenging, or even impossible if all users with that content are offline.
Therefore, a healthy peer-to-peer network necessitates at least _some_ highly available nodes that participate in the network like regular peers, but seldom go offline. These are called seed nodes.
> By running a _permissive_ node (see below for further explanation), you are contributing to the growth and resilience of the Radicle network, but it is important to be aware of the potential implications of hosting data in a permissionless network. Use your best judgment. If you have any concerns, consider starting with the selective seeding policy, described in the following section, and expanding as you become more comfortable with the network.
## Getting started
For this guide, we’ll focus on setting up a seed node using `systemd`. If you’re running a different service manager, you should be able to follow along just fine, as we’ll be explaining the steps. Using a service manager is not required, but highly recommended.
### What you will need
To run a Radicle seed node, you will need a server with:
- `curl`
- `git`
- `systemctl` version `232` or newer
- `sudo` privileges
- A DNS name pointing to your server
### Server
If you already have a VPS (Virtual Private Server) or home server running a Linux distribution, you should be all set. If not, any VPS will do. Between 1-2GB of RAM with a shared CPU and 10GB of disk space should be enough to get started. Once you’ve logged into your server, proceed with user setup and installation. For this setup process, you’ll need `sudo` capabilities.
> **Tip**: If you have trouble choosing a VPS, we recommend getting a small instance on [Hetzner][hetzner] or [Digital Ocean][do]. These are priced fairly and have good documentation and support.
[hetzner]: https://www.hetzner.com/
[do]: https://www.digitalocean.com/
### User setup
If you wish to create a new user under which to run the seed node services, this is the time. In this guide, we’ve chosen to run our services under a user called `seed`. Radicle data and configuration will be stored under this user’s home folder. This ensures that if your Radicle services were to be compromised, the attacker would have very limited access to your system.
Here’s an example user setup with `seed` as the user and group name:
```bash
sudo groupadd --system seed
sudo useradd --system --gid seed --create-home seed
```
If you’re administering your server via a non-root account, you’ll want to add it to the `seed` group as well:
```bash
sudo usermod -a -G seed $(whoami)
```
### Installation
For a seed node, you will need to install at minimum the Radicle CLI (`rad`), and network daemon (`radicle-node`).
To install these, head over to the [download](/install#cli) page and follow the instructions there. You will have to download, verify and extract the binaries and manuals to your preferred location.
For this guide, we recommend installing Radicle under `/usr/local`. This will require you to have write permissions to `/usr/local/bin` and `/usr/local/man`. You can give yourself these permission by changing ownership of these directories to the current user and group:
```bash
sudo chown $(whoami): /usr/local/{bin,man,man/man1}
```
You should then be able to extract the archive you downloaded and verified with:
```bash
tar -xvJf <archive> --strip-components=1 -C /usr/local/
```
Finally, login as the `seed` user and proceed with installation:
```bash
sudo su seed
```
To check your installation, run the `rad` command, and confirm where your Radicle home is with the `rad path` command.
To offer web browsing and HTTP access to your seed node, you will additionally need to run `radicle-httpd`, the HTTP daemon. We’ll cover this after we’ve set up our basic node.
### Creating a profile
Once the Radicle binaries are installed, we can create a Radicle _profile_. This consists of an Ed25519 key pair and a directory under which Radicle stores user data.
```bash
rad auth --alias seed.radicle.example
```
The above command will create a profile with the given node _alias_ in `~/.radicle`, or `$RAD_HOME` if set. We recommend setting your alias to the domain name that points to your node’s host.
For seed nodes, it’s not generally necessary to set up a passphrase for your key, so you can simply skip that prompt by pressing _enter_.
<aside>
Seed nodes do not typically sign any permanent artifacts with their key. It
is mainly used for securing connections with peers.
</aside>
> **Tip**: If you wish to set a passphrase anyway, you will have to set the `RAD_PASSPHRASE` environment variable in your node’s `systemd` unit file.
Once your profile is initialized, a decentralized identifier (DID) will be output. The part after the `did:key:` prefix is your Node ID. This is how your seed node will be identified on the network. The Node ID is the public part of the Ed25519 key pair generated above, via `rad auth`.
You can view information about your Radicle profile by running `rad self`. The `--nid` and `--alias` flags can be used to return the Node ID and alias.
## Configuring your node
There are a couple of things we need to set up a seed node.
First, we must set the node’s default _seeding policy_.
The seeding policy tells the node which repositories and reference namespaces to fetch and offer to the network. For _public seed nodes_, a _permissive_ seeding policy is often set, such that _all data_ on the network is stored and replicated.
Second, we must set an _External Address_ for the node to be reached on the network. This address will be advertised to peers, allowing them to connect to your seed node. Generally, this will be a DNS name with port `8776`, for example `seed.radicle.example:8776`.
Here’s an example minimal configuration file with a permissive seeding policy and an external address set:
```json
{
"node": {
"alias": "seed.radicle.example",
"listen": ["0.0.0.0:8776"],
"externalAddresses": ["seed.radicle.example:8776"],
"seedingPolicy": {
"default": "allow",
"scope": "all"
}
}
}
```
> In the above example the server listen to any IPv4 interface. If your host supports only IPv6 or if you want to support both IPv4 and IPv6, replace `0.0.0.0` with `[::]`.
Your node is configured with a file named `config.json` in your Radicle home directory. You can get the full path of the config file with the `rad self --config` command. Additionally, you can output the current configuration with `rad config`. Attributes that aren’t set in `config.json` will take on default values. You can open your configuration file for editing with `rad config edit`.
Let’s start by configuring your seeding policy.
### Seeding policies
The most important setting when it comes to seeding is your _default seeding policy_. This setting will determine what content your seed node fetches and replicates on the network when it encounters a repository it doesn’t know or hasn’t been given special instructions for.
The setting is configured under the `node.seedingPolicy` field in your configuration (`~/.radicle/config.json`). You can open your configuration file directly using the `rad config edit` command, or use your preferred editor. You can also enter the following command to display your current default policy:
```bash
rad config get node.seedingPolicy
```
When a seeding policy is _not set_ for a specific repository or node, the default policy is applied, hence the importance of this setting.
Broadly, there are two options for the default policy.
#### A _permissive_ seeding policy
A _permissive_ or “open” policy is said to be _fully-replicating_, meaning your seed will try to have a full copy of all repository data available on the network.
An example of a node with this policy is `iris.radicle.network`, a node operated by the Radicle team, for the Radicle community.
This is a good default for seeders who want to support the network without having to think about it too much.
Set `node.seedingPolicy.default` to `allow` and `node.seedingPolicy.scope` to `all` to configure your node this way.
```json
{
"node": {
...
"seedingPolicy": {
"default": "allow",
"scope": "all"
}
}
}
```
#### A _selective_ seeding policy
A _selective_ or “restricted” policy requires you, the operator, to _manually allow repositories to be seeded_. This means that the node will ignore all repositories, except the ones that are pre-configured to allow seeding.
An example is the `seed.radicle.xyz` node, which only seeds Radicle team repositories.
This is a good policy for communities, teams, companies and individuals who want to limit the data hosted by their seed, or node operators who want to require some form of authentication or payment for seeding.
Set `node.seedingPolicy.default` to `block` to enable this, and call `rad seed` to configure `allow` policies for specific repositories you want to seed. Your seed node won’t seed anything until you explicitly allow it to.
```json
{
"node": {
...
"seedingPolicy": {
"default": "block"
}
}
}
```
#### Setting a repository’s seeding policy
To override the default policy for a specific repository, the `seed` or `block` commands are used.
For example, in the case of a _permissive_ seeding policy, where the default is `allow`, we can explicitly _block_ certain repositories from being seeded:
```bash
rad block rad:z9DV738hJpCa6aQXqvQC4SjaZvsi
```
This will override the default policy and ensure that this repository is never replicated on your node.
On the other hand, in the case of a _selective_ seeding policy, where the default policy is `block`, we can decide to _allow_ a repository to be seeded by calling `rad seed`. For example:
```bash
rad seed rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
```
This will override the default `block` rule for this one specific repository.
To remove an override configured for a repository in either case, `unseed` can be used:
```bash
rad unseed rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
```
For both cases, the default policy will apply if a seeding policy is not defined for a repository.
#### Viewing seeding policies
You can view your node’s _default policy_ by entering `rad config get node.seedingPolicy`.
To view the policy of a specific repository, use the `rad inspect` command. For example:
```bash
rad inspect rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 --policy
```
This will return the default policy if you haven’t set a specific policy for that repository.
Finally, to view all seeding policies that have been configured on repositories, simply enter `rad seed` with no options.
### Addressing
Now that you’ve configured your seeding policy, it’s time to consider how your node is addressed on the network.
#### Your external address
You should set your node’s external address. This is a public address, typically a DNS name that points to your node. Though a single external address is sufficient, you are free to set up to 16 external addresses.
You’ll find this setting in your configuration file, under `node.externalAddresses`. External addresses are JSON strings of the form `<host>:<port>`, for example `seed.radicle.example:8776`, where `<host>` is a DNS name, and `<port>` is usually `8776`.
```json
{
"node": {
...
"externalAddresses": ["seed.radicle.example:8776"]
}
}
```
Once at least one external address is set, you’re ready to start your node for the first time.
#### Your node address
For others to be able to connect to your node directly, they need your _Node Address_. This is a combination of your Node ID and your External Address.
If you’ve configured one or more external addresses, simply entering the following command from the `seed` user will output your node addresses:
```bash
rad node config --addresses
```
Share this with others, and they will be able to connect to your node using `rad node connect <address>`, or by adding your address to their configuration, under the `node.connect` field.
#### DNS-based service discovery
If you would like to facilitate discovery of your node via DNS, you can do so via [DNS-Based Service Discovery (DNS-SD)][dnssd]. This requires you to set three DNS records per node you are running.
1. A `SRV` record, which communicates the external address of your node (DNS name and TCP port number).
2. A `TXT` record, which communicates the Node ID of your node.
3. A `PTR` record, which makes the other two records discoverable.
Taken together, the `SRV` and `TXT` records contain all the information required to reconstruct your node address.
Suppose the node address you want others to discover is `z6MkXB…Z6sTAz@seed.radicle.example:58776` and the domain name you want it to be discovered for is `radicle.example`, then the three DNS records required are:
```
seed._radicle-node._tcp.radicle.example. 3600 IN SRV 32767 32767 58776 seed.radicle.example.
seed._radicle-node._tcp.radicle.example. 3600 IN TXT "nid=z6MkXB…Z6sTAz"
_radicle-node._tcp.radicle.example. 3600 IN PTR seed._radicle-node._tcp.radicle.example.
```
With most nameservers, there are shorter versions to denote these records, like dropping TTL and record class, as well as abbreviating names by omitting the origin. The variant above is purposely verbose to avoid ambiguity. Of course, you may choose different values for the TTL.
With these records set, anyone can discover your nodes from the domain name `radicle.example`. This method of discovery is ideal if you maintain a set of nodes, and want to have them discovered by a group of people, such as your collaborators or coworkers. It also works if your nodes addresses and/or ports change frequently.
To find out more about the the interpretation of `SRV` records, refer to [RFC 2782][srv].
## Running your node
Before setting up your node as a system service, it’s a good idea to run it once to make sure everything is working properly. Enter the following command to start your node in the foreground:
```bash
rad node start --foreground
```
You should see log output as your node starts to sync with the network. If there are any errors or issues connecting to the network, you should see errors in the output.
If the node started without problem, stop it with `Ctrl-C`, and exit the `seed` session by entering:
```bash
exit
```
This should return you to the original session you opened via SSH.
### Configuring your node’s system service
Though it’s possible to simply run the Radicle node as a background process, it’s recommended to set up a service to ensure the node is started on boot.
In this guide, we will only cover setup using `systemd`, but the process is fairly similar for other service managers.
The first thing to do is to get a copy of the [`radicle-node.service`][radicle-node] unit file. Place it in `/etc/systemd/system/radicle-node.service` for it to be found by `systemctl`:
```bash
curl -sSL https://seed.radicle.xyz/raw/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/570a7eb141b6ba001713c46345d79b6fead1ca15/systemd/radicle-node.service -o /etc/systemd/system/radicle-node.service
```
Make sure it fits your needs by editing the file directly, or creating an override using `systemctl edit`.
> The downloaded `systemd` unit file is configured to run your node process as the `seed` user and group, for security reasons. If you set up a different user or group name, or set a passphrase during `rad auth`, or if you want to listen on an IPv6 interface, you will have to make edits to this file before proceeding.
When you’re ready, you can **enable** and **run** the service:
```bash
systemctl enable --now radicle-node
```
### Checking your node status
To check the service status of your node, run:
```bash
systemctl status radicle-node
```
Besides using `systemctl status`, you can also check your node’s status using `rad node status`. This will give you information on the peers connected to your node. You’ll have to run this as the `seed` user, like so:
```bash
sudo -u seed -- rad node status
```
To tail your node’s logs, use:
```bash
journalctl --unit radicle-node --follow
```
### Changing your node’s configuration
If you change your node’s configuration while the node is running, you’ll need to restart your node for the changes to take effect.
First, verify that the new configuration is valid by running `rad config`, then restart your node with:
```bash
systemctl restart radicle-node
```
### Securing the `seed` user
Now that your node is configured and working, you can secure the `seed` user by disabling shell access. This is optional, though recommended.
```bash
chsh -s /usr/sbin/nologin seed
```
This will prevent anyone from logging in as the `seed` user. Note that from this point onwards, if you chose to disable the `seed` user’s shell, you’ll have to run all commands via `sudo`, like we’ve been doing so far.
> To facilitate running commands as the `seed` user by simply using `rad` as usual, add this alias to your admin shell’s init scripts:
>
> ```bash
> alias rad='sudo -u seed -- rad'
> ```
### Firewalls
If you are running a firewall, ensure that port `8776` is open for TCP connections. This will allow inbound connections to your node.
> It’s recommended to run a basic firewall to further lock down your server, using something like `iptables`, though this is out of scope for this guide.
## Running the HTTP daemon
In the prior sections, we set up `radicle-node`, a background process that actively and continuously discovers and replicates repositories on the network, based on your seeding policy. This node allows users to collaborate, host, share, and publish repositories on the network via the Radicle CLI or any compatible application. However, at this stage, the repositories on your seed node cannot be browsed or viewed without cloning them first, using the Radicle CLI. To enable web browsing of the content of your seed node, the Radicle HTTP Daemon `radicle-httpd` needs to be deployed alongside `radicle-node`.
The HTTP Daemon is a background process that functions as a _gateway_ between the Radicle protocol and the HTTP protocol. It is configured to have direct read-only access to the node’s storage and database and expose this data via an HTTP JSON API. For seed nodes, the HTTP Daemon is always configured as a _read-only_ service over the node’s state.
### Installation
Head over to the [download](/install#cli) page, and follow the instructions there. The process is the same as for the Radicle Node. You will have to download, verify and extract the binary (`radicle-httpd`) and manuals to your preferred location.
We recommend installing the daemon under `/usr/local`, just as we did for the node.
### Configuring your HTTP daemon’s system service
As with `radicle-node`, we can start by downloading an example `systemd` unit file for the daemon:
```bash
curl -sSL https://seed.radicle.xyz/raw/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/570a7eb141b6ba001713c46345d79b6fead1ca15/systemd/radicle-httpd.service -o /etc/systemd/system/radicle-httpd.service
```
Make sure it fits your needs by editing the file directly, or creating an override using `systemctl edit`.
> In this guide, all `systemd` unit files are configured to run the process as the `seed` user and group, for security reasons. If you set up a different user or group name, you will have to make edits to these files before running the services.
Then, **enable** and **run** the service:
```bash
systemctl enable --now radicle-httpd
```
You can check that your node service is running with:
```bash
systemctl status radicle-httpd
```
You can query the API with `curl` to ensure everything is working properly:
```bash
curl http://127.0.0.1:8080/api/v1
```
### Adding support for HTTPS
For your HTTP Daemon to be accessible from a frontend such as <https://radicle.network>, it needs to respond to HTTPS requests. To do this, we recommend using [Caddy][caddy].
Start by installing `caddy`; most Linux distributions have a package you can install. If you are using Debian or Ubuntu, you can run:
```bash
apt-get install caddy
```
If you’re having trouble installing Caddy, check the [installation guide][caddy-install]. Once installed, run `caddy version` to ensure that everything was installed correctly.
```
caddy version
v2.6.4 h1:2hwYqiRwk1tf3VruhMpLcYTg+11fCdr8S3jhNAdnPy8=
```
Then, download the `caddy` unit file from Caddy’s GitHub repository:
```bash
curl https://raw.githubusercontent.com/caddyserver/dist/master/init/caddy.service -o /etc/systemd/system/caddy.service
```
Edit the file and change the `User` and `Group` attributes to `seed`, as we have for the other services. Also ensure that `ExecStart` and `ExecReload` are set to the correct path. You can find the path under which `caddy` is installed by running the `which caddy` command.
Finally, edit or create the `Caddyfile` at `/etc/caddy/Caddyfile`, and _replace_ its contents with the following configuration, using the correct domain name for your seed:
```
seed.radicle.example {
reverse_proxy 127.0.0.1:8080
}
```
This will proxy all HTTPS requests from port `443` to your HTTP daemon running on port `8080`. Make sure your firewall has port `443` open for incomning TCP connections.
Finally, enable and start the Caddy service:
```bash
systemctl enable --now caddy
```
If you encounter issues setting up Caddy, you can try following their [guide][caddy-guide] instead.
If everything worked, you should now have HTTPS support for your daemon. To check, run the following command, replacing `seed.radicle.example` with your seed’s domain:
```bash
curl https://seed.radicle.example/api/v1
```
You should now be able to visit your seed node via any Radicle web frontend as well. For example, <https://radicle.network/nodes/seed.radicle.example>.
> On some distributions, installing `caddy` will start the system service automatically. If you’re not able to connect to your HTTP daemon from the outside, try running `systemctl reload caddy` after you’ve updated the configuration.
## You’re all set
If you got this far, congratulations, you now have a Radicle seed node up and running!
Come join us on our community chat and tell us about your seed node on the [#seeds][zulip] channel.
[radicle-node]: https://seed.radicle.xyz/raw/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/570a7eb141b6ba001713c46345d79b6fead1ca15/systemd/radicle-node.service
[caddy]: https://caddyserver.com/
[caddy-guide]: https://caddyserver.com/docs/running#linux-service
[caddy-install]: https://caddyserver.com/docs/install
[zulip]: https://radicle.zulipchat.com/#narrow/stream/384534-seeds
[bittorrent]: https://en.wikipedia.org/wiki/BitTorrent
[dnssd]: https://datatracker.ietf.org/doc/html/rfc6763
[srv]: https://datatracker.ietf.org/doc/html/rfc2782
<style>
aside {
background: var(--color-background-alt, #f5f5f5);
padding: 1rem;
border-radius: 0.5rem;
margin: 1rem 0;
font-size: 0.9em;
}
</style>
File diff suppressed because it is too large Load Diff
+8 -8
View File
@@ -14,35 +14,35 @@ export const guides: Guide[] = [
title: "Getting started",
description:
"New to Radicle? This quick-start walks you through installing the stack, creating your identity, and opening your first patch.",
href: "/guides/getting-started",
href: "https://radicle.dev/guides/quick-start/",
image: "/marketing/images/learn/getting-started.jpg",
external: false,
external: true,
video: false,
},
{
title: "Protocol guide",
description:
"Gossip, replication, identity, and the data structures behind it all.",
href: "/guides/protocol",
href: "https://radicle.dev/guides/protocol/",
image: "/marketing/images/learn/protocol.jpg",
external: false,
external: true,
video: false,
},
{
title: "Seeder guide",
description: "Run a public seed node to keep repositories available 24/7.",
href: "/guides/seeder",
href: "https://radicle.dev/guides/seeder/",
image: "/marketing/images/learn/seeder.jpg",
external: false,
external: true,
video: false,
},
{
title: "User guide",
description:
"Create an identity, open patches, manage issues, and collaborate from the CLI.",
href: "/guides/user",
href: "https://radicle.dev/guides/user/",
image: "/marketing/images/learn/user.jpg",
external: false,
external: true,
video: false,
},
{
+4 -22
View File
@@ -1,8 +1,6 @@
import type { ComponentType } from "svelte";
import type { DocsLoadedRoute, DocsPage, DocsRoute } from "./types";
import { isDocsPage } from "./types";
export type {
CliRoute,
DesktopRoute,
@@ -24,19 +22,11 @@ const docsLoaders = new Map<
>([
["glossary", () => import("./docs/glossary.md")],
["download", () => import("./docs/download.md")],
["guides/getting-started", () => import("./docs/guides/getting-started.md")],
["guides/protocol", () => import("./docs/guides/protocol.md")],
["guides/seeder", () => import("./docs/guides/seeder.md")],
["guides/user", () => import("./docs/guides/user.md")],
]);
const docsTitles = new Map<DocsPage, string>([
["glossary", "Glossary"],
["download", "Download"],
["guides/getting-started", "Getting started"],
["guides/protocol", "Protocol guide"],
["guides/seeder", "Seeder guide"],
["guides/user", "User guide"],
]);
export function docsTitle(page: DocsPage): string[] {
@@ -84,18 +74,10 @@ export function marketingRoute(
return segments.length === 0
? { resource: "docs", params: { page: resource } }
: null;
case "guides": {
// Bare `/guides` is the all-guides index; `/guides/<page>` is a doc.
if (segments.length === 0) {
return { resource: "guides", params: undefined };
}
const guide = segments.shift();
const page = guide ? `guides/${guide}` : undefined;
if (page && isDocsPage(page) && segments.length === 0) {
return { resource: "docs", params: { page } };
}
return null;
}
case "guides":
return segments.length === 0
? { resource: "guides", params: undefined }
: null;
default:
return null;
}
+1 -20
View File
@@ -35,26 +35,7 @@ export interface PrinciplesRoute {
params: undefined;
}
export type DocsPage =
| "glossary"
| "download"
| "guides/getting-started"
| "guides/protocol"
| "guides/seeder"
| "guides/user";
const docsPages: DocsPage[] = [
"glossary",
"download",
"guides/getting-started",
"guides/protocol",
"guides/seeder",
"guides/user",
];
export function isDocsPage(page: string): page is DocsPage {
return (docsPages as string[]).includes(page);
}
export type DocsPage = "glossary" | "download";
export interface DocsRoute {
resource: "docs";