commit 78475412bb1b58c8ea1e715cbe5d27fe9afd1919 Author: Alexis Sellier Date: Wed Feb 14 17:01:44 2024 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae58761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +_site/ +.jekyll-cache/ +_cache/ +.DS_Store +node_modules/ +vendor/ +notes/ +.vercel diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..0137309 --- /dev/null +++ b/Gemfile @@ -0,0 +1,5 @@ +source "https://rubygems.org" + +gem "jekyll", "~> 4.2" +gem "webrick" +gem "rouge" diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..c7910a4 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,72 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + colorator (1.1.0) + concurrent-ruby (1.2.3) + em-websocket (0.5.3) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0) + eventmachine (1.2.7) + ffi (1.16.3) + forwardable-extended (2.6.0) + google-protobuf (3.25.2-x86_64-linux) + http_parser.rb (0.8.0) + i18n (1.14.1) + concurrent-ruby (~> 1.0) + jekyll (4.3.3) + addressable (~> 2.4) + colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 1.0) + jekyll-sass-converter (>= 2.0, < 4.0) + jekyll-watch (~> 2.0) + kramdown (~> 2.3, >= 2.3.1) + kramdown-parser-gfm (~> 1.0) + liquid (~> 4.0) + mercenary (>= 0.3.6, < 0.5) + pathutil (~> 0.9) + rouge (>= 3.0, < 5.0) + safe_yaml (~> 1.0) + terminal-table (>= 1.8, < 4.0) + webrick (~> 1.7) + jekyll-sass-converter (3.0.0) + sass-embedded (~> 1.54) + jekyll-watch (2.2.1) + listen (~> 3.0) + kramdown (2.4.0) + rexml + kramdown-parser-gfm (1.1.0) + kramdown (~> 2.0) + liquid (4.0.4) + listen (3.8.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + mercenary (0.4.0) + pathutil (0.16.2) + forwardable-extended (~> 2.6) + public_suffix (5.0.4) + rb-fsevent (0.11.2) + rb-inotify (0.10.1) + ffi (~> 1.0) + rexml (3.2.6) + rouge (4.2.0) + safe_yaml (1.0.5) + sass-embedded (1.69.5-x86_64-linux-gnu) + google-protobuf (~> 3.23) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + unicode-display_width (2.5.0) + webrick (1.8.1) + +PLATFORMS + x86_64-linux-gnu + +DEPENDENCIES + jekyll (~> 4.2) + rouge + webrick + +BUNDLED WITH + 2.5.4 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..db20bdf --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +default: _site + jekyll build + +serve: + jekyll serve --port 3000 + +dependencies: + gem install jekyll bundler aws-sdk-s3 + +svgs: + scripts/cleanup-svgs.sh assets/images/*.svg + +publish: + vercel --prod + +.PHONY: publish diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..0ac9a33 --- /dev/null +++ b/_config.yml @@ -0,0 +1,11 @@ +url: "https://docs.radicle.xyz" +baseurl: "/" +title: "docs.radicle.xyz" +highlighter: rouge +markdown: kramdown +kramdown: + syntax_highlighter: rouge +collections: + guides: + output: true + permalink: /guides/:path/ diff --git a/_guides/protocol.md b/_guides/protocol.md new file mode 100644 index 0000000..6148306 --- /dev/null +++ b/_guides/protocol.md @@ -0,0 +1,812 @@ +--- +title: Radicle Protocol Guide +subtitle: How Radicle works under the hood +layout: guide +--- + +Heartwood, the latest release of the [Radicle][] protocol establishes a +sovereign data network for code collaboration and publishing, built on top of +[Git][]. In Radicle, users maintain local copies of their interested +repositories 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][], 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 due to Git's primary focus on version control +rather than collaboration. As a result, users frequently opt for centralized +[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 options like [Gitea][] or [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 across a [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][] 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-identifier-nid) (NID)---an [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, operates a node by +installing a [Radicle Node](#Radicle-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 an always-on server. + + + + + +### Seeding Repositories + +Users configure nodes with a *seeding policy* which specifies the list of +repositories they are interested in seeding, in addition to retention rules. +This means that nodes aren't just seeding random repositories, users have an +active role in deciding which repositories should be stored on their device. + +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. + + + +### Node Identifiers (NIDs) + + + +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][] 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. + + + +
+
did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
+
Example Node ID encoded as a DID using the did:key + method.
+
+ +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. + + + +### Running a Node + +To run a node and connect to the network, users install a Radicle *client*. +Client software is lightweight and suitable for use on both end-user devices +and seed nodes. The reference implementation can be found in the Radicle +[heartwood][] repository, and is actively maintained by a small team of +engineers. + + + +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. + + + +## 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. + +
+ +
A set of connected peers forming a gossip network.
+
+ +### Gossip Protocol + +The Radicle networking layer is designed as a gossip protocol, where messages +are sent relayed between peers to build routing tables that aid in repository +discovery and replication. The core functionality is achieved with three +message types, each fulfulling a distinct role: + +1. **Node Announcements** are used for broadcasting Node IDs and physical + addresses on which a node is publicly reachable, to facilitate peer + discovery. +2. **Inventory Announcements** are used for broadcasting repository inventories + and constructing the routing table which maps out what repositories are + hosted where. +3. **Reference Announcements** are used for broadcasting updates to + repositories, relayed only to nodes interested in the relevant repository. + +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. + +> **Tip**: Refer to [RIP-1][rip-1] to learn more details about Radicle's +> networking protocol. + +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. + +
+ +
The three gossip message types.
+
+ +### Transport Encryption & Privacy + +Connections between peers in the Radicle network are encrypted using the Noise +protocol. 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. + +
+ +
Noise XK handshake example.
+
+ +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][] 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][] 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. + + + +
+ +
Connection multiplexing in Radicle.
+
+ +### 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 bootstrap the peer discovery process and build an address +book. + +Radicle's reference implementation is pre-configured with two [bootstrap +nodes][bootstrap] that are connected to if the address book is empty: +[seed.radicle.garden][] and [seed.radicle.xyz][]. 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 bootrap nodes +and once they establish a connection, use the regular peer discovery mechanism +to find more peers. + +
+ +
Bootstrapping process.
+
+ +[bootstrap]: https://en.wikipedia.org/wiki/Bootstrapping_node + +### 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. + + + +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. + +
+ +
Federation (left) vs. peer-to-peer (right).
+
+ +## 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. + + + +Here's an example of the identity document for the [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]: + +
+
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
+
Example Repository ID for the heartwood project.
+
+ +Since the RID is derived from the *initial* verison 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 *local fork* of a +repository, as well as any other forks they have an interest in, all within the +same Git repository. These forks are then shared among users across the +network. + +Each repository fork has a *single owner and writer*, and users are only +permitted to make changes to their respective forks. + +### 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. + +
+ +
Synchronizing the working copy with the stored copy of a repository.
+
+ + +### 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]. + + + +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 +forks. + +Since the underlying storage uses Git, the storage layout below is represented +as a file tree on the file-system, with `` 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 +*fork* or *view*, and allows nodes to maintain local data for all peers in the +same physical repository. + +``` + # Storage root containing all repositories +├─ # Storage for first repository +│ └─ refs # All Git references locally stored +│ └─ namespaces # All peer source trees or "forks" +│ ├─ # 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 +│ │ +│ └─ # Second node's source tree +│ ├─ refs # Second node's references +│ └─ ... +├─ # Storage for second repository +│ ... +└─ # 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 +forks in the network. This allows the Git `fetch` and `push` commands to +operate on the correct namespace when fetching or pushing code. + +
+
rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
+
Example URL for repository z42hL2jL4XNk6K8oHQaSWfMgCL7ji and + peer z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi.
+
+ +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: + +
+/z42hL2jL4XNk6K8oHQaSWfMgCL7ji/refs/namespaces/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/refs/heads/master
+
+ +If a Node ID is not specified in the URL, Git will interact with the +repository's [canonical references](#Canonical-Repository-Version), also know +as the authoritative repository state. This is the state agreed on by the +repository [delegates](#delegates). + +
+
rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji
+
Example URL for repository + z42hL2jL4XNk6K8oHQaSWfMgCL7ji's canonical + references.
+
+ + +## 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 everytime 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. + +
+
9767b485c2aad1e23097d2b5165287ba84cfa452 refs/heads/master
+f3eaa7454e3a4714885905ae99f616fc7895b5fa refs/cobs/xyz.radicle.patch/fe31d5b6049583a42c21a543545d182b893aa4a0
+0590b78ee42b39087983e4de04164065e5aa11bc refs/cobs/xyz.radicle.patch/ffbb812ad6e7fe1c5c610b1246ca5ca9d7d16027
+
Example refs blob under a user's refs/rad/sigrefs branch. +
+
+ + + +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 +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. + + + +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). +Each issue, patch or identity document is represented by one such commit graph +that is disjoint from any other COB or source code branch. + +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 simplied *unioned* with +each other in a non-destructive, idempotent way. + +
+ +
+ + + +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. + + + +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. + +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. + +COBs are stored under the `refs/cobs` hierarchy, where Radicle's built-in COBs +co-exist with user-defined COBs. 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. + + + +[Gitea]: https://about.gitea.com/ +[Forgejo]: https://forgejo.org/ +[Radicle]: https://radicle.xyz/ +[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/en +[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/ +[seed.radicle.garden]: https://app.radicle.xyz/nodes/seed.radicle.garden +[seed.radicle.xyz]: https://app.radicle.xyz/nodes/seed.radicle.xyz +[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://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/0001-heartwood.md +[rip-2]: https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82/tree/0002-identity.md +[rip-3]: https://app.radicle.xyz/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://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z3trNYnLWS11cJWC6BbxDs5niGo82 +[heartwood]: https://app.radicle.xyz/nodes/seed.radicle.xyz/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 +[did]: https://www.w3.org/TR/did-core/ +[seed]: https://docs.radicle.xyz/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 diff --git a/_includes/toc.html b/_includes/toc.html new file mode 100644 index 0000000..f2055b4 --- /dev/null +++ b/_includes/toc.html @@ -0,0 +1,169 @@ +{% capture tocWorkspace %} + {% comment %} + Copyright (c) 2017 Vladimir "allejo" Jimenez + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + {% endcomment %} + {% comment %} + Version 1.2.1 + https://github.com/allejo/jekyll-toc + + "...like all things liquid - where there's a will, and ~36 hours to spare, there's usually a/some way" ~jaybe + + Usage: + {% include toc.html html=content sanitize=true class="inline_toc" id="my_toc" h_min=2 h_max=3 %} + + Parameters: + * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll + + Optional Parameters: + * sanitize (bool) : false - when set to true, the headers will be stripped of any HTML in the TOC + * class (string) : '' - a CSS class assigned to the TOC + * id (string) : '' - an ID to assigned to the TOC + * h_min (int) : 1 - the minimum TOC header level to use; any header lower than this value will be ignored + * h_max (int) : 6 - the maximum TOC header level to use; any header greater than this value will be ignored + * ordered (bool) : false - when set to true, an ordered list will be outputted instead of an unordered list + * item_class (string) : '' - add custom class(es) for each list item; has support for '%level%' placeholder, which is the current heading level + * submenu_class (string) : '' - add custom class(es) for each child group of headings; has support for '%level%' placeholder which is the current "submenu" heading level + * base_url (string) : '' - add a base url to the TOC links for when your TOC is on another page than the actual content + * anchor_class (string) : '' - add custom class(es) for each anchor element + + Output: + An ordered or unordered list representing the table of contents of a markdown block. This snippet will only + generate the table of contents and will NOT output the markdown given to it + {% endcomment %} + + {% capture newline %} + {% endcapture %} + {% assign newline = newline | rstrip %} + + {% capture jekyll_toc %}{% endcapture %} + {% assign orderedList = include.ordered | default: false %} + {% assign baseURL = include.base_url | default: include.baseurl | default: '' %} + {% assign minHeader = include.h_min | default: 1 %} + {% assign maxHeader = include.h_max | default: 6 %} + {% assign nodes = include.html | strip | split: ' maxHeader %} + {% continue %} + {% endif %} + + {% assign _workspace = node | split: '' | first }}>{% endcapture %} + {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %} + + {% if include.item_class and include.item_class != blank %} + {% capture listItemClass %} class="{{ include.item_class | replace: '%level%', currLevel | split: '.' | join: ' ' }}"{% endcapture %} + {% endif %} + + {% if include.submenu_class and include.submenu_class != blank %} + {% assign subMenuLevel = currLevel | minus: 1 %} + {% capture subMenuClass %} class="{{ include.submenu_class | replace: '%level%', subMenuLevel | split: '.' | join: ' ' }}"{% endcapture %} + {% endif %} + + {% capture anchorBody %}{% if include.sanitize %}{{ header | strip_html }}{% else %}{{ header }}{% endif %}{% endcapture %} + + {% if htmlID %} + {% capture anchorAttributes %} href="{% if baseURL %}{{ baseURL }}{% endif %}#{{ htmlID }}"{% endcapture %} + + {% if include.anchor_class %} + {% capture anchorAttributes %}{{ anchorAttributes }} class="{{ include.anchor_class | split: '.' | join: ' ' }}"{% endcapture %} + {% endif %} + + {% capture listItem %}{{ anchorBody }}{% endcapture %} + {% else %} + {% capture listItem %}{{ anchorBody }}{% endcapture %} + {% endif %} + + {% if currLevel > lastLevel %} + {% capture jekyll_toc %}{{ jekyll_toc }}<{{ listModifier }}{{ subMenuClass }}>{% endcapture %} + {% elsif currLevel < lastLevel %} + {% assign repeatCount = lastLevel | minus: currLevel %} + + {% for i in (1..repeatCount) %} + {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} + {% endfor %} + + {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} + {% else %} + {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} + {% endif %} + + {% capture jekyll_toc %}{{ jekyll_toc }}{{ listItem }}{% endcapture %} + + {% assign lastLevel = currLevel %} + {% assign firstHeader = false %} + {% endfor %} + + {% assign repeatCount = minHeader | minus: 1 %} + {% assign repeatCount = lastLevel | minus: repeatCount %} + + {% for i in (1..repeatCount) %} + {% capture jekyll_toc %}{{ jekyll_toc }}{% endcapture %} + {% endfor %} + + {% if jekyll_toc != '' %} + {% assign rootAttributes = '' %} + {% if include.class and include.class != blank %} + {% capture rootAttributes %} class="{{ include.class | split: '.' | join: ' ' }}"{% endcapture %} + {% endif %} + + {% if include.id and include.id != blank %} + {% capture rootAttributes %}{{ rootAttributes }} id="{{ include.id }}"{% endcapture %} + {% endif %} + + {% if rootAttributes %} + {% assign nodes = jekyll_toc | split: '>' %} + {% capture jekyll_toc %}<{{ listModifier }}{{ rootAttributes }}>{{ nodes | shift | join: '>' }}>{% endcapture %} + {% endif %} + {% endif %} +{% endcapture %}{% assign tocWorkspace = '' %}{{ jekyll_toc -}} diff --git a/_layouts/guide.html b/_layouts/guide.html new file mode 100644 index 0000000..a847d0e --- /dev/null +++ b/_layouts/guide.html @@ -0,0 +1,42 @@ + + + + + {% if page.title %} + {{ page.title }} + {% else %} + {{ site.title }} + {% endif %} + + + + + + + + + + + +
+
+ + + +
+ +
+
+

{{ page.title }}

+

{{ page.subtitle }}

+
+ {% include toc.html html=content class="toc" item_class="toc-h%level%" %} +
+ {{ content }} +
+
+ © The Radicle Team +
+ + diff --git a/assets/css/fonts.css b/assets/css/fonts.css new file mode 100644 index 0000000..b59542f --- /dev/null +++ b/assets/css/fonts.css @@ -0,0 +1,85 @@ +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("/assets/fonts/Inter-Regular.woff2"); +} + +@font-face { + font-family: "Inter"; + font-weight: 500; + font-display: swap; + src: url("/assets/fonts/Inter-Medium.woff2"); +} + +@font-face { + font-family: "Inter"; + font-weight: 600; + font-display: swap; + src: url("/assets/fonts/Inter-SemiBold.woff2"); +} + +@font-face { + font-family: "Inter"; + font-weight: 700; + font-display: swap; + src: url("/assets/fonts/Inter-Bold.woff2"); +} + +@font-face { + font-family: "JetBrains Mono"; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("/assets/fonts/JetBrainsMono-Regular.woff2"); +} + +@font-face { + font-family: "JetBrains Mono"; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url("/assets/fonts/JetBrainsMono-Medium.woff2"); +} + +@font-face { + font-family: "JetBrains Mono"; + font-weight: 600; + font-display: swap; + src: url("/assets/fonts/JetBrainsMono-SemiBold.woff2"); +} + +@font-face { + font-family: "JetBrains Mono"; + font-weight: 700; + font-display: swap; + src: url("/assets/fonts/JetBrainsMono-Bold.woff2"); +} + +:root { + --font-family-default: "Inter", sans-serif; + --font-family-code: "JetBrains Mono", monospace; + --font-weight-regular: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + --font-size-tiny: 0.75rem; /* 12px */ + --font-size-small: 0.875rem; /* 14px */ + --font-size-regular: 1rem; /* 16px */ + --font-size-medium: 1.125rem; /* 18px */ + --font-size-large: 1.5rem; /* 24px */ + --font-size-x-large: 2rem; /* 32px */ + --font-size-xx-large: 3rem; /* 48px */ +} + +html { + -ms-text-size-adjust: 100%; + -webkit-font-smoothing: antialiased; + -webkit-text-size-adjust: 100%; + font-family: var(--font-family-default); + font-feature-settings: "zero"; + font-size: 16px; + font-weight: var(--font-weight-regular); + line-height: 1.6; +} diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..01730ae --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,345 @@ +@charset "utf-8"; + +:root { + --color-bg-dark: #0a0d10; + --color-bg-light: #f4f4f4; + --color-fg-dim-0: #444; + --color-fg-dim-1: #666; + --color-fg-dim-2: #999; + --color-fg-dim-3: #ccc; + --color-fg-dim-4: #eee; + --color-fg: #1a1a1a; + --color-yellow: #ffff88; + --color-fg-highlight: #5555bb; + --color-fg-highlight-secondary: #229944; + --color-border-highlight: #aaaaee; + --color-bg-highlight: #eeeeff; +} + +::selection { + background-color: var(--color-yellow); +} + +* { + box-sizing: border-box; +} + +html { + font-family: var(--font-family-default); + color: var(--color-fg); + min-width: 1280px; +} + +body { + margin: 0; +} + +header, main, footer { + display: grid; + align-items: start; + grid-column-gap: 2rem; + grid-template-columns: + [screen-start] 1fr + [page-start kicker-start left-start] 6rem 6rem + [text-start kicker-end] 3rem 5rem 3rem 3rem + [left-end right-start] 3rem 5rem 3rem 3rem + [text-end gutter-start] 6rem 6rem + [page-end gutter-end right-end] 1fr [screen-end]; +} + +main { + align-items: first baseline; + padding-bottom: 8rem; +} + +header, footer { + color: rgba(255, 255, 255, 0.8); + font-weight: 300; +} + +header { + background-color: var(--color-bg-dark); + height: 4rem; + align-items: stretch; +} + +footer { + padding: 4rem 0; +} + +nav { + display: grid; + grid-column: right; + grid-auto-flow: column; + grid-gap: 1.5rem; + align-items: stretch; + justify-self: end; + text-transform: uppercase; +} + +header a { + display: flex; + align-items: center; +} + +main > *, footer > * { + grid-column: text; +} + +footer > *:last-child { + margin-bottom: 0; +} + +strong { + font-weight: 600; +} + +h1 { + font-size: var(--font-size-xx-large); + font-weight: var(--font-weight-semibold); + margin: 4rem 0 0.5rem; + line-height: 1.1; +} + +h2 { + font-size: var(--font-size-x-large); + font-weight: var(--font-weight-semibold); + margin: 3.75rem 0 1.5rem; + padding-bottom: 1rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); +} + +h3 { + font-size: var(--font-size-large); + font-weight: var(--font-weight-semibold); + margin: 2rem 0 1.5rem; +} + +h4 { + font-size: var(--font-size-medium); + font-weight: var(--font-weight-semibold); + margin: 1.5rem 0 1rem; +} + +h5 { + font-size: var(--font-size-small); + font-weight: var(--font-weight-semibold); + margin: 1rem 0 0.25rem; +} + +pre, code { + font-family: var(--font-family-code); +} + +pre { + background-color: var(--color-bg-light); + overflow-x: scroll; + font-size: 0.875rem; + padding: 1rem; + margin: 0; +} + +pre code { + background: none; + padding: 0; +} + +pre.center { + text-align: center; +} + +p code, ul code, ol code, table code { + font-size: 1rem; + font-weight: 600; +} + +p:last-child { + margin-bottom: 0; +} + +p + blockquote { + margin-top: 0; +} + +blockquote { + color: var(--color-fg-dim-0); + background: var(--color-bg-highlight); + padding: 1rem; + margin: 1rem 0; + border-left: 2px solid var(--color-border-highlight); +} + +.title { + grid-column: left; + display: grid; + grid-auto-flow: column; + align-items: stretch; + justify-self: start; +} + +.title img { + width: 1.75rem; + height: 1.75rem; +} + +.toc + hr { + margin-bottom: 4rem; +} + +.first { + margin-top: 60px; +} + +.subtitle { + font-size: 1.125rem; + color: var(--color-fg-dim-1); + margin: 0 0 4rem; +} + +main figure, main img { + margin: 1rem 0 2rem 0; +} + +main img, main object { + margin: 0; + padding: 0; + border: 0; + outline: none; +} + +main img.placeholder { + width: 100%; + height: 8rem; + border: none !important; + outline: none !important; + background: var(--color-fg-dim-4) !important; +} + +figure { + width: 100%; +} + +figure pre { + margin: 0; +} + +figure img, figure object { + display: block; + margin: 0 auto; +} + +figure object + figcaption { + margin-top: 1rem; +} + +figure.diagram { + grid-column-start: kicker-start; + grid-column-end: gutter-end; +} + +figure.diagram.tall { + grid-column-start: text-start; + grid-column-end: text-end; +} + +figcaption { + margin-top: 0.5rem; + width: 100%; + white-space: nowrap; + overflow-x: hidden; + text-align: center; + text-overflow: ellipsis; +} + +aside { + grid-column: gutter; +} + +aside.kicker { + grid-column: kicker; +} + +aside.diagram { + margin-top: 1.5rem; +} + +figcaption, aside { + font-size: 0.75rem; + line-height: 1.5; + color: var(--color-fg-dim-1); +} + +a { + color: unset; + text-decoration: unset; + border-bottom: 1px solid var(--color-fg-dim-2); +} + +a:hover { + border-bottom-color: var(--color-fg-dim-1); +} + +p, ol, ul, table, pre { + margin: 0 0 1rem; +} + +ol, ul { + padding-left: 2rem; +} + +li { + padding-left: 0.5rem; +} + +hr { + grid-column: screen; + width: 100%; + border: none; + border-top: 1px solid var(--color-fg-dim-4); + margin: 0; +} + +span.highlight { + color: var(--color-fg-highlight); +} + +span.highlight-secondary { + color: var(--color-fg-highlight-secondary); +} + +.toc { + display: flex; + flex-direction: column; + flex-wrap: wrap; + gap: 1rem; + font-size: 0.75rem; + margin: 2rem 0; + padding: 0; + height: 16rem; +} + +.toc a { + border-bottom-style: none; +} +.toc a:hover { + border-bottom-style: solid; +} + +.toc li, .toc ul { + padding: 0; + margin: 0; + list-style-type: none; +} + +.toc-h2 > a { + font-weight: bold !important; +} +.toc-h2 > ul { + margin-left: 0.5rem; +} +.toc-h3 > ul { + margin-left: 1rem; +} +.toc-h3 > a, .toc-h4 > a, .tok-h5 > a { + color: var(--color-fg-dim-1); +} diff --git a/assets/css/monokai.css b/assets/css/monokai.css new file mode 100644 index 0000000..427911a --- /dev/null +++ b/assets/css/monokai.css @@ -0,0 +1,2 @@ +.highlight .nl { color: #555 } +.highlight .s2 { color: #555 } diff --git a/assets/fonts/Inter-Bold.woff2 b/assets/fonts/Inter-Bold.woff2 new file mode 100644 index 0000000..2846f29 Binary files /dev/null and b/assets/fonts/Inter-Bold.woff2 differ diff --git a/assets/fonts/Inter-Medium.woff2 b/assets/fonts/Inter-Medium.woff2 new file mode 100644 index 0000000..f92498a Binary files /dev/null and b/assets/fonts/Inter-Medium.woff2 differ diff --git a/assets/fonts/Inter-Regular.woff2 b/assets/fonts/Inter-Regular.woff2 new file mode 100644 index 0000000..6c2b689 Binary files /dev/null and b/assets/fonts/Inter-Regular.woff2 differ diff --git a/assets/fonts/Inter-SemiBold.woff2 b/assets/fonts/Inter-SemiBold.woff2 new file mode 100644 index 0000000..611e90c Binary files /dev/null and b/assets/fonts/Inter-SemiBold.woff2 differ diff --git a/assets/fonts/JetBrainsMono-Bold.woff2 b/assets/fonts/JetBrainsMono-Bold.woff2 new file mode 100644 index 0000000..4917f43 Binary files /dev/null and b/assets/fonts/JetBrainsMono-Bold.woff2 differ diff --git a/assets/fonts/JetBrainsMono-Medium.woff2 b/assets/fonts/JetBrainsMono-Medium.woff2 new file mode 100644 index 0000000..669d04c Binary files /dev/null and b/assets/fonts/JetBrainsMono-Medium.woff2 differ diff --git a/assets/fonts/JetBrainsMono-Regular.woff2 b/assets/fonts/JetBrainsMono-Regular.woff2 new file mode 100644 index 0000000..40da427 Binary files /dev/null and b/assets/fonts/JetBrainsMono-Regular.woff2 differ diff --git a/assets/fonts/JetBrainsMono-SemiBold.woff2 b/assets/fonts/JetBrainsMono-SemiBold.woff2 new file mode 100644 index 0000000..5ead7b0 Binary files /dev/null and b/assets/fonts/JetBrainsMono-SemiBold.woff2 differ diff --git a/assets/fonts/Virgil.woff2 b/assets/fonts/Virgil.woff2 new file mode 100644 index 0000000..cb22225 Binary files /dev/null and b/assets/fonts/Virgil.woff2 differ diff --git a/assets/images/cob-dag.svg b/assets/images/cob-dag.svg new file mode 100644 index 0000000..3defc68 --- /dev/null +++ b/assets/images/cob-dag.svg @@ -0,0 +1,138 @@ + + + + + + + + + + Comment + + + + + + + Edit + Comment + + + + + + + Label + + + + + + + Close + + + + + + + Comment + + + + + + + Comment + + + + + + + Open + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/federation-vs-p2p.svg b/assets/images/federation-vs-p2p.svg new file mode 100644 index 0000000..2b956ea --- /dev/null +++ b/assets/images/federation-vs-p2p.svg @@ -0,0 +1,202 @@ + + + + + + + + + + Instance + + + + + + + User + + + + + + + User + + + + + + + User + + + + + + + User + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Instance + + + + + + + + + + + + + Instance + + + + + + + + + + + + + + + + + + + User + + + + + + + + + + + + + Node + + + + + + + Node + + + + + + + Node + + + + + + + Node + + + + + + + Node + + + + + + + Node + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/multiplexing.svg b/assets/images/multiplexing.svg new file mode 100644 index 0000000..eda5a8a --- /dev/null +++ b/assets/images/multiplexing.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Calyx + + + + + + + Paxel + + + Multiplexed connection + + diff --git a/assets/images/p2p-network.svg b/assets/images/p2p-network.svg new file mode 100644 index 0000000..3be5dac --- /dev/null +++ b/assets/images/p2p-network.svg @@ -0,0 +1,194 @@ + + + + + + + + + + Node + + + + + + + Node + + + + + + + Node + + + + + + + Node + + + + + + + Node + + + + + + + Node + + + + + + + + + + + + + + + + fetch + + + + + + + + + + + + + + + + fetch + + + + + + + + + + + + gossip + + + + + + + + + + + + + + + + + + + + + + fetch + + + + + + + + + + + + + + + + gossip + + + + + + + + + + + + + + + + gossip + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Gossip + + + + + + + + + + + + + Git fetch + + diff --git a/assets/images/radicle.svg b/assets/images/radicle.svg new file mode 100644 index 0000000..4310d44 --- /dev/null +++ b/assets/images/radicle.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/working-vs-stored.svg b/assets/images/working-vs-stored.svg new file mode 100644 index 0000000..b6d5c8a --- /dev/null +++ b/assets/images/working-vs-stored.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + Repository + + + + + + + Repository + + + Local Storage + + + + + + + + + + Working + Copy + + + + + + Working + Copy + + + + + + + + + + + + + + + + push + + + + + + + + + + + + + + + + pull + + + + + + Node + + + rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 + + + rad:z2ATVRDqFu2Yq2MG5ZZCWecwSDYqj + + diff --git a/scripts/cleanup-svgs.sh b/scripts/cleanup-svgs.sh new file mode 100755 index 0000000..94f715b --- /dev/null +++ b/scripts/cleanup-svgs.sh @@ -0,0 +1,43 @@ +#!/bin/sh +set -e + +if [ $# -lt 1 ]; then + echo "usage: $0 ..." + exit 1 +fi + +# Function to process a single file +process() { + FILE="$1" + + # Ensure the file exists + if [ ! -f "$FILE" ]; then + echo "error: '$FILE' does not exist." + return + fi + + # Format SVG. + xmllint --format "$FILE" --output "$FILE" + # Remove `` header. + sed -i 1d "$FILE" + + # Remove all instances of `font-family="..."` and `font-size="..."` + sed -i -e 's/font-family="[^"]*"//g' -e 's/font-size="16px"//g' "$FILE" + # Remove comments. + sed -i -e 's/\n//' "$FILE" + # Replace the contents of \ + ' "$FILE" + + echo "Processed $FILE" + } + +# Iterate over each file provided as an argument +for file in "$@"; do + process "$file" +done