explorer: run prettier format

This commit is contained in:
Daniel Norman
2026-10-27 11:10:00 +01:00
parent 563cafbc85
commit 692da482fb
9 changed files with 311 additions and 225 deletions
+24 -24
View File
@@ -3,10 +3,10 @@
The Svelte web app for browsing Radicle repositories, its TypeScript HTTP
client, and the `radicle-httpd` and `radicle-search` backends it talks to.
The project lives in `explorer/` of the radicle-gui monorepo. It is its own
npm package and Cargo workspace: run every command here from `explorer/`,
and all paths in this file are relative to it. Conventions shared with
desktop are in the root `AGENTS.md`.
The project lives in `explorer/` of the radicle-gui monorepo. It is its own npm
package and Cargo workspace: run every command here from `explorer/`, and all
paths in this file are relative to it. Conventions shared with desktop are in
the root `AGENTS.md`.
## Checks and linting (tsc, svelte-check, eslint, prettier)
@@ -29,10 +29,10 @@ npm run test:e2e -- --project chromium
npm run test:e2e -- tests/e2e/<file>.spec.ts --project chromium
```
`SKIP_SETUP=true` skips fixture creation for faster iteration.
Only use it when you are solely editing `.spec.ts` files and fixtures
already exist from a previous full run. Any change to app code, httpd,
or test fixtures requires a full run.
`SKIP_SETUP=true` skips fixture creation for faster iteration. Only use it when
you are solely editing `.spec.ts` files and fixtures already exist from a
previous full run. Any change to app code, httpd, or test fixtures requires a
full run.
```sh
SKIP_SETUP=true npm run test:e2e -- --project chromium
@@ -50,9 +50,9 @@ cargo test --workspace --all-features
cargo test -p radicle-httpd
```
E2E and http-client tests run against a stable pre-built httpd by
default. When changing `crates/radicle-httpd/`, also test against the
local build and run both to verify backwards compatibility:
E2E and http-client tests run against a stable pre-built httpd by default. When
changing `crates/radicle-httpd/`, also test against the local build and run both
to verify backwards compatibility:
```sh
npm run test:e2e:local -- --project chromium
@@ -62,22 +62,22 @@ npm run test:http-client:unit:local
## API changes across layers
Adding or changing an API endpoint typically touches three layers:
1. `crates/radicle-httpd/` — Rust handler (add route, types, logic)
2. `http-client/` — TypeScript client (add fetch function, types)
3. `src/` — Svelte UI (consume the new data)
**Backwards compatibility:** Only add to the backend API — never rename
or remove fields/endpoints unless explicitly deprecating. The frontend
must handle older nodes gracefully: treat new API fields as optional
and fall back when they're absent.
**Backwards compatibility:** Only add to the backend API — never rename or
remove fields/endpoints unless explicitly deprecating. The frontend must handle
older nodes gracefully: treat new API fields as optional and fall back when
they're absent.
## Pre-push checklist
`npm run check:ci` runs the checks, unit, http-client, e2e and build smoke
tests from the GitHub workflows, and stops at the first failure. It logs
each step's output, duration and exit code to `tests/tmp/check-ci.log`. If
Rust code changed, also run the Rust commands and the `:local` variants
above.
`npm run check:ci` runs the checks, unit, http-client, e2e and build smoke tests
from the GitHub workflows, and stops at the first failure. It logs each step's
output, duration and exit code to `tests/tmp/check-ci.log`. If Rust code
changed, also run the Rust commands and the `:local` variants above.
## Svelte components
@@ -87,13 +87,13 @@ above.
- Avoid `{@html}` with backend-derived data (branch/ref names, aliases,
descriptions); when unavoidable, its argument must be DOMPurify-sanitized
(`sanitizeConfig` in `src/lib/markdown.ts`) or built from `escape()`'d
parts, in the component that renders it.
(`sanitizeConfig` in `src/lib/markdown.ts`) or built from `escape()`'d parts,
in the component that renders it.
## TypeScript / HTTP client
- Derive types from Zod schemas (`z.infer<typeof fooSchema>`), never
hand-write interfaces that duplicate a schema
- Derive types from Zod schemas (`z.infer<typeof fooSchema>`), never hand-write
interfaces that duplicate a schema
- Schema naming: `fooSchema` (camelCase + Schema suffix) → type `Foo`
- API timestamps are in seconds; multiply by 1000 for JS `Date`
- Relative imports inside `http-client/` use `.js` extension (ESM)
+69 -26
View File
@@ -2,54 +2,97 @@
## radicle-httpd + radicle-search 0.30.0
- **`tar.zst` archive downloads**: `/raw/{rid}/archive/{ref}.tar.zst` joins the tar, tar.gz and zip formats; it needs the `zstd` binary in `PATH`
- **Archive ref errors**: Unknown refs now return 404 and malformed ones 400, instead of 500
- **Runtime log format**: `RADICLE_HTTPD_LOG_FORMAT` selects `text` (default) or `json` logs at startup, with the access log emitted as structured fields in JSON; this replaces the `logfmt` build feature
- **Batched search reindexing**: radicle-search ignores seed events and reindexes each touched repo once per batch (`RADICLE_SEARCH_BATCH_SECS`, default 30s), so a flapping peer no longer pins meilisearch at 100% CPU
- Updated `rustls-webpki` and `webpki-roots` to clear advisories reported by `cargo audit`
- **`tar.zst` archive downloads**: `/raw/{rid}/archive/{ref}.tar.zst` joins the
tar, tar.gz and zip formats; it needs the `zstd` binary in `PATH`
- **Archive ref errors**: Unknown refs now return 404 and malformed ones 400,
instead of 500
- **Runtime log format**: `RADICLE_HTTPD_LOG_FORMAT` selects `text` (default) or
`json` logs at startup, with the access log emitted as structured fields in
JSON; this replaces the `logfmt` build feature
- **Batched search reindexing**: radicle-search ignores seed events and
reindexes each touched repo once per batch (`RADICLE_SEARCH_BATCH_SECS`,
default 30s), so a flapping peer no longer pins meilisearch at 100% CPU
- Updated `rustls-webpki` and `webpki-roots` to clear advisories reported by
`cargo audit`
## radicle-httpd + radicle-search 0.29.0
- **Complete commit lists for merge-commit diffs**: The diff endpoint no longer stops the walk early when the head merges the target branch in, which reduced such patches to a single commit
- **Complete commit lists for merge-commit diffs**: The diff endpoint no longer
stops the walk early when the head merges the target branch in, which reduced
such patches to a single commit
## radicle-httpd + radicle-search 0.28.0
- **Releases API**: New `/repos/{rid}/releases` endpoints serve radicle-artifact release COBs, with Releases and Release views in the UI that degrade gracefully on older nodes
- **`Git-Protocol` header passed through**: Git HTTP routes forward the header to `git-http-backend`, fixing clients that require Git wire protocol version 2
- Updated `chacha20`, `h2` and `lru` to clear advisories reported by `cargo audit`
- **Releases API**: New `/repos/{rid}/releases` endpoints serve radicle-artifact
release COBs, with Releases and Release views in the UI that degrade
gracefully on older nodes
- **`Git-Protocol` header passed through**: Git HTTP routes forward the header
to `git-http-backend`, fixing clients that require Git wire protocol version 2
- Updated `chacha20`, `h2` and `lru` to clear advisories reported by
`cargo audit`
## radicle-httpd + radicle-search 0.27.0
- **Commit-count endpoint**: New `/repos/{rid}/stats/commits/{sha}` counts reachable commits from the commit-graph, avoiding the full history walk of `/stats/tree`
- **Diff-stats endpoint**: New `/repos/{rid}/diff/{base}/{oid}/stats` returns line counts without building hunks, 6–15x faster than `/diff`; it skips rename detection, so counts can differ slightly on renamed or copied files
- **Faster file browsing**: Blob requests no longer walk the whole history to find a file's last commit, which took seconds to minutes on large repositories
- **Repository reads off the async workers**: Synchronous libgit2 and COB reads run on the blocking pool, so one slow read no longer stalls queued requests
- **Repo aliases in the JSON and raw APIs**: `--alias` path segments resolve there too, not just on the git routes, and the repo endpoint exposes an optional `alias` field
- **Higher open-file limit**: Raised at startup, fixing spurious "missing object" errors in storage with thousands of packfiles
- **Commit-count endpoint**: New `/repos/{rid}/stats/commits/{sha}` counts
reachable commits from the commit-graph, avoiding the full history walk of
`/stats/tree`
- **Diff-stats endpoint**: New `/repos/{rid}/diff/{base}/{oid}/stats` returns
line counts without building hunks, 6–15x faster than `/diff`; it skips rename
detection, so counts can differ slightly on renamed or copied files
- **Faster file browsing**: Blob requests no longer walk the whole history to
find a file's last commit, which took seconds to minutes on large repositories
- **Repository reads off the async workers**: Synchronous libgit2 and COB reads
run on the blocking pool, so one slow read no longer stalls queued requests
- **Repo aliases in the JSON and raw APIs**: `--alias` path segments resolve
there too, not just on the git routes, and the repo endpoint exposes an
optional `alias` field
- **Higher open-file limit**: Raised at startup, fixing spurious "missing
object" errors in storage with thousands of packfiles
- **Empty job COBs excluded**: Job listings omit jobs with no runs
- **`RUST_LOG` in the default logger**: Honoured without the `logfmt` feature, defaulting to `info`
- **`RUST_LOG` in the default logger**: Honoured without the `logfmt` feature,
defaulting to `info`
- Updated radicle crates, cargo dependencies and the Rust toolchain (1.97.1)
## radicle-httpd + radicle-search 0.26.0
- **Repository search**: New optional `radicle-search` indexing daemon backs a `/repos/search?q=…` endpoint for typo-tolerant search over repo names and descriptions, plus faster `/repos?sort=activity|seeding` listings; httpd falls back to its built-in storage walk when the daemon is absent, so existing behavior is unchanged
- **Git archive downloads**: Additional formats ZIP and TAR are now supported. Also, a prefix is added to the archive by default, which can be disabled by using the query `?prefix=false`. This query string is immediately deprecated. It only exists as a workaround for users to adapt their download scripts etc. to accommodate for the prefix. It will be removed in a future version.
- **Streaming archive downloads**: Archives are streamed to the client instead of being buffered in memory, avoiding excessive memory use on large repositories
- **Faster archive HEAD requests**: `HEAD` requests no longer build an archive that is then discarded
- **Streaming git operations**: Clone, fetch, and push stream request and response bodies and accept gzip-compressed requests, reducing memory use on large operations
- **Repository search**: New optional `radicle-search` indexing daemon backs a
`/repos/search?q=…` endpoint for typo-tolerant search over repo names and
descriptions, plus faster `/repos?sort=activity|seeding` listings; httpd falls
back to its built-in storage walk when the daemon is absent, so existing
behavior is unchanged
- **Git archive downloads**: Additional formats ZIP and TAR are now supported.
Also, a prefix is added to the archive by default, which can be disabled by
using the query `?prefix=false`. This query string is immediately deprecated.
It only exists as a workaround for users to adapt their download scripts etc.
to accommodate for the prefix. It will be removed in a future version.
- **Streaming archive downloads**: Archives are streamed to the client instead
of being buffered in memory, avoiding excessive memory use on large
repositories
- **Faster archive HEAD requests**: `HEAD` requests no longer build an archive
that is then discarded
- **Streaming git operations**: Clone, fetch, and push stream request and
response bodies and accept gzip-compressed requests, reducing memory use on
large operations
## radicle-httpd 0.25.0
- **Canonical and peer refs in repo API**: The repo endpoint now exposes canonical refs and per-peer refs, letting clients discover branches and tags resolved by delegate quorum as well as those from individual nodes
- **Job COBs endpoint**: New `/repos/{rid}/jobs/{sha}` endpoint serves job collaborative objects, enabling clients to query CI/CD job state
- **Canonical and peer refs in repo API**: The repo endpoint now exposes
canonical refs and per-peer refs, letting clients discover branches and tags
resolved by delegate quorum as well as those from individual nodes
- **Job COBs endpoint**: New `/repos/{rid}/jobs/{sha}` endpoint serves job
collaborative objects, enabling clients to query CI/CD job state
## radicle-httpd 0.24.0
- **Windows support**: The HTTP daemon now runs on Windows with platform-specific adaptations
- **Windows support**: The HTTP daemon now runs on Windows with
platform-specific adaptations
- **Faster API responses**: Improved `/node` endpoint performance
- **Git namespace paths**: Direct access to node ref namespaces via `/{rid}.git/{nid}/` remote URLs, simplifying clones and fetches from specific nodes
- **Git namespace paths**: Direct access to node ref namespaces via
`/{rid}.git/{nid}/` remote URLs, simplifying clones and fetches from specific
nodes
- Updated dependencies and internal improvements
## radicle-httpd 0.23.0
- **Live config reload**: Update configuration without restarting by sending a SIGHUP signal
- **Live config reload**: Update configuration without restarting by sending a
SIGHUP signal
+55 -65
View File
@@ -9,20 +9,20 @@ It lives in the `explorer/` folder of the radicle-gui repository, next to the
**Deployment Options:**
* **Public Server:** The Radicle team maintains a public instance at [radicle.network][app]
* **Local-first:** Run the UI locally
* **Self-hosting:** Deploy the UI on your own server
- **Public Server:** The Radicle team maintains a public instance at
[radicle.network][app]
- **Local-first:** Run the UI locally
- **Self-hosting:** Deploy the UI on your own server
## Local-first
**Prerequisites:**
* Recent versions of [Node.js][nod] (24 or higher) and [npm][npm] installed
- Recent versions of [Node.js][nod] (24 or higher) and [npm][npm] installed
Run the following commands to access the web UI locally:
``` shell
```shell
git clone https://seed.radicle.dev/z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git radicle-gui
cd radicle-gui/explorer
npm install
@@ -31,7 +31,6 @@ npm start
Then open http://localhost:3000 in your favourite browser.
## Self-Hosting the UI
There are several ways to deploy the UI publicly. Here are two common options:
@@ -68,8 +67,8 @@ There are two ways to configure the UI: **at build time** and **at run time**.
### Build-Time Configuration
This method is recommended when deploying to static hosting platforms such
as Vercel.
This method is recommended when deploying to static hosting platforms such as
Vercel.
#### Option 1: Create a `local.json` file
@@ -86,7 +85,6 @@ as Vercel.
> For advanced configuration options, refer to the [`node-config`][nco]
> documentation.
### Run-Time Configuration
This method is useful when the app is distributed as a precompiled static
@@ -105,42 +103,40 @@ VITE_RUNTIME_CONFIG=true npm run build
This will inject a blocking script into the `index.html` that attempts to load
the configuration from a pre-defined location (`/config.json`) on the server.
The config file must be served as static content and must be publicly accessible.
The structure of the runtime `config.json` must match the shape of the
application's base configuration defined in `config/default.json`.
The config file must be served as static content and must be publicly
accessible. The structure of the runtime `config.json` must match the shape of
the application's base configuration defined in `config/default.json`.
### Homepage
The `nodes.homepage` key controls what is rendered at the root URL (`/`):
* `"node"` (default) — show the node view of the user's primary seed
directly, as if the user had navigated to `/nodes/<your-node>`. The
primary seed is resolved from `preferredSeeds` (see [`default.json`][def]).
This is the default so that a self-hosted deployment acts as a dedicated
front-end for its own seed.
* `"landing"` — serve the full Radicle marketing site: the landing page and
its `/learn`, `/install` and documentation (`/glossary`, `/guides/*`)
sub-routes. This is opt-in and intended for the canonical
[radicle.network][app] deployment; the marketing sub-routes are only
reachable in this mode.
- `"node"` (default) — show the node view of the user's primary seed directly,
as if the user had navigated to `/nodes/<your-node>`. The primary seed is
resolved from `preferredSeeds` (see [`default.json`][def]). This is the
default so that a self-hosted deployment acts as a dedicated front-end for its
own seed.
- `"landing"` — serve the full Radicle marketing site: the landing page and its
`/learn`, `/install` and documentation (`/glossary`, `/guides/*`) sub-routes.
This is opt-in and intended for the canonical [radicle.network][app]
deployment; the marketing sub-routes are only reachable in this mode.
The cross-seed Explore page is always available at `/explore`, regardless of
the `homepage` setting.
The cross-seed Explore page is always available at `/explore`, regardless of the
`homepage` setting.
#### radicle.network deployment
`default.json` keeps `homepage` set to `"node"`, so self-hosted deployments
are unaffected. The canonical radicle.network deployment opts into the
marketing site at **build time** without changing that default. To deploy
radicle.network, run `npm run deploy`.
`default.json` keeps `homepage` set to `"node"`, so self-hosted deployments are
unaffected. The canonical radicle.network deployment opts into the marketing
site at **build time** without changing that default. To deploy radicle.network,
run `npm run deploy`.
#### Node-homepage deployment
If you operate your own seed node and want the explorer to act as a
dedicated front-end for it (the default behaviour), put your own node first
(and ideally as the only entry) in `preferredSeeds` so it is always picked
as the primary seed.
If you operate your own seed node and want the explorer to act as a dedicated
front-end for it (the default behaviour), put your own node first (and ideally
as the only entry) in `preferredSeeds` so it is always picked as the primary
seed.
Example `local.json`:
@@ -156,26 +152,25 @@ Example `local.json`:
}
```
With this configuration, opening `/` lands the visitor on the node view
for `seed.example.com`. Keeping `preferredSeeds` to a single entry
ensures the primary seed is deterministic; if you list multiple, users
are bucketed across them and may land on a different node.
With this configuration, opening `/` lands the visitor on the node view for
`seed.example.com`. Keeping `preferredSeeds` to a single entry ensures the
primary seed is deterministic; if you list multiple, users are bucketed across
them and may land on a different node.
## Repository aliases
`radicle-httpd` can map a short, human-readable alias to a repository ID
(RID), so a repository can be referred to by name instead of its full
`rad:` identifier. Aliases are configured on the node with the
`--alias <name> <rid>` flag, which may be repeated:
`radicle-httpd` can map a short, human-readable alias to a repository ID (RID),
so a repository can be referred to by name instead of its full `rad:`
identifier. Aliases are configured on the node with the `--alias <name> <rid>`
flag, which may be repeated:
```sh
radicle-httpd --alias heartwood rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
```
An alias works anywhere the RID is accepted: `git clone`, the HTTP API, the
raw endpoints, and Explorer URLs — the router resolves it transparently.
With the alias above, these two Explorer URLs point at the same repository:
An alias works anywhere the RID is accepted: `git clone`, the HTTP API, the raw
endpoints, and Explorer URLs — the router resolves it transparently. With the
alias above, these two Explorer URLs point at the same repository:
```
https://radicle.network/nodes/seed.example.com/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/issues
@@ -188,7 +183,6 @@ and the repository can be cloned over Git by its alias:
git clone https://seed.example.com/heartwood.git
```
## Development
Run `npm run check` before proposing a patch, along with the test suites:
@@ -204,44 +198,40 @@ SKIP_SETUP=true npm run test:e2e -- tests/e2e/repo/commits.spec.ts:90 --project
```
When changing `radicle-httpd`, `npm run test:e2e:local` compiles it from
`crates/radicle-httpd/` and runs the suite against that build instead of
the released binary.
If Playwright's browsers don't support your OS, `scripts/test-e2e-docker`
runs the suite in a container (Docker with the Compose plugin). It forwards
its arguments and `SKIP_SETUP` to `npm run test:e2e`, and `--local` tests
against httpd built from source. The first run downloads Chromium and is
slow.
`crates/radicle-httpd/` and runs the suite against that build instead of the
released binary.
If Playwright's browsers don't support your OS, `scripts/test-e2e-docker` runs
the suite in a container (Docker with the Compose plugin). It forwards its
arguments and `SKIP_SETUP` to `npm run test:e2e`, and `--local` tests against
httpd built from source. The first run downloads Chromium and is slow.
## Contributing
See [CONTRIBUTING.md][con] for how to propose changes and write commit
messages. Open an [issue][iss] or a [patch][pat] using Radicle.
See [CONTRIBUTING.md][con] for how to propose changes and write commit messages.
Open an [issue][iss] or a [patch][pat] using Radicle.
## Getting in touch
To get in touch with the maintainers, sign up to our
[official chat on Zulip][zul].
To get in touch with the maintainers, sign up to our [official chat on
Zulip][zul].
## License
The UI is distributed under the terms of GPLv3. See [LICENSE][lic] for details.
[app]: https://radicle.network
[con]: ../CONTRIBUTING.md
[def]: ./config/default.json
[env]: ./config/custom-environment-variables.json
[iss]: https://radicle.network/nodes/iris.radicle.network/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5/issues
[iss]:
https://radicle.network/nodes/iris.radicle.network/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5/issues
[lic]: ./LICENSE
[nco]: https://github.com/node-config/node-config/wiki/Configuration-Files
[nod]: https://nodejs.org
[npm]: https://www.npmjs.com
[pat]: https://radicle.network/nodes/iris.radicle.network/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5/patches
[pat]:
https://radicle.network/nodes/iris.radicle.network/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5/patches
[rad]: https://radicle.dev
[try]: https://caddyserver.com/docs/caddyfile/patterns#single-page-apps-spas
[zul]: https://radicle.zulipchat.com/#narrow/stream/369278-web
+25 -11
View File
@@ -3,10 +3,10 @@
`radicle-httpd` and `radicle-search` are released **together** from this
workspace: a single tag, a single changelog, and one build run that produces
artifacts for both binaries. They share the same version number, bumped in
lockstep. On [files.radicle.dev][files] each binary gets its own release
folder — `releases/radicle-httpd/<version>/` and, as a sibling,
`releases/radicle-search/<version>/` — so existing `radicle-httpd` download
URLs are unaffected and `radicle-search` is downloaded independently.
lockstep. On [files.radicle.dev][files] each binary gets its own release folder
— `releases/radicle-httpd/<version>/` and, as a sibling,
`releases/radicle-search/<version>/` — so existing `radicle-httpd` download URLs
are unaffected and `radicle-search` is downloaded independently.
The release tooling lives in [`crates/radicle-httpd/build/`][build].
@@ -14,8 +14,8 @@ The release tooling lives in [`crates/radicle-httpd/build/`][build].
Releases cover **only** the `radicle-httpd` and `radicle-search` binaries. The
web front-end (`radicle-explorer`) is not released this way: it is deployed
continuously (rolling release) and must always remain compatible with the
latest `radicle-httpd`/`radicle-search` release.
continuously (rolling release) and must always remain compatible with the latest
`radicle-httpd`/`radicle-search` release.
## Prerequisites
@@ -30,6 +30,7 @@ latest `radicle-httpd`/`radicle-search` release.
## Prepare Release
1. Create release branch:
```sh
git checkout -b release/httpd/<version>
```
@@ -40,12 +41,14 @@ latest `radicle-httpd`/`radicle-search` release.
- `crates/radicle-search/Cargo.toml`
3. Update lockfiles:
```sh
cargo build
```
4. Generate changelog (the version is read from
`crates/radicle-httpd/Cargo.toml`):
```sh
crates/radicle-httpd/build/changelog >> CHANGELOG.md
```
@@ -54,6 +57,7 @@ latest `radicle-httpd`/`radicle-search` release.
change belongs to `radicle-httpd` or `radicle-search`.
6. Commit release:
```sh
git commit -m "Release <version>"
```
@@ -71,13 +75,15 @@ latest `radicle-httpd`/`radicle-search` release.
## Tag Release
Create and sign the release tag (one tag covers both binaries):
```sh
crates/radicle-httpd/build/tag <version>
```
Publish the tag to Radicle. Push only this tag: every tag under `releases/`
that a delegate pushes becomes canonical, so `--tags` would also publish any
stray local ones.
Publish the tag to Radicle. Push only this tag: every tag under `releases/` that
a delegate pushes becomes canonical, so `--tags` would also publish any stray
local ones.
```sh
git push rad refs/tags/releases/httpd/<version>
rad sync
@@ -87,11 +93,13 @@ rad sync
Run the reproducible build (Linux x86_64 or macOS). A single run compiles the
workspace and packages **both** binaries — one archive per binary per target:
```sh
crates/radicle-httpd/build/build
```
This writes to `crates/radicle-httpd/build/artifacts/`:
- `radicle-httpd-<version>-<target>.tar.xz` (binary + man page)
- `radicle-search-<version>-<target>.tar.xz` (binary)
@@ -104,6 +112,7 @@ each with a `.sig` and `.sha256` alongside.
The upload publishes each binary into its own sibling folder on
[files.radicle.dev][files] and creates the version-less and `latest` symlinks
for both:
```sh
SSH_LOGIN=<username> crates/radicle-httpd/build/upload
```
@@ -119,6 +128,7 @@ SSH_LOGIN=<username> crates/radicle-httpd/build/upload
**Topic:** `radicle-httpd + radicle-search <version>`
**Message:**
````markdown
# 👾 radicle-httpd + radicle-search <version>
@@ -127,6 +137,7 @@ SSH_LOGIN=<username> crates/radicle-httpd/build/upload
## Installation
Download binaries from:
- https://files.radicle.dev/releases/radicle-httpd/<version>
- https://files.radicle.dev/releases/radicle-search/<version>
- https://radicle.dev/download
@@ -141,18 +152,21 @@ SSH_LOGIN=<username> crates/radicle-httpd/build/upload
<copy output from crates/radicle-httpd/build/checksums>
```
:info: This release was built on <platform>. Build reproducibility across platforms is unsupported; checksums may differ if rebuilt elsewhere.
:info: This release was built on <platform>. Build reproducibility across
platforms is unsupported; checksums may differ if rebuilt elsewhere.
````
## Notes
> **Important:**
>
> - macOS binaries are unsigned and not notarized; download via CLI only
> - Build reproducibility across platforms is not guaranteed
## Podman Setup
We use `podman` for reproducible builds across machines. It's preferred over Docker because it runs without a daemon and doesn't require root access.
We use `podman` for reproducible builds across machines. It's preferred over
Docker because it runs without a daemon and doesn't require root access.
On first run, you may need to configure UID/GID ranges:
+45 -45
View File
@@ -1,13 +1,13 @@
# radicle-search
Optional indexing daemon for Radicle. Runs alongside `radicle-node` and
maintains a Meilisearch index of repo metadata that `radicle-httpd` can
use to serve fast, typo-tolerant repo listings and full-text search.
maintains a Meilisearch index of repo metadata that `radicle-httpd` can use to
serve fast, typo-tolerant repo listings and full-text search.
When configured, httpd routes `/repos?sort=activity|seeding` and
`/repos/search?q=…` through the index. When absent or unreachable, httpd
transparently falls back to its built-in storage walk — no configuration
on the client side, no API contract changes.
transparently falls back to its built-in storage walk — no configuration on the
client side, no API contract changes.
## What each repository document contains
@@ -29,23 +29,24 @@ Only public repos this node seeds are indexed.
## How it stays up to date
1. **Bootstrap.** On startup and after every event-stream reconnect, the
daemon walks the storage tree end-to-end and re-upserts everything.
1. **Bootstrap.** On startup and after every event-stream reconnect, the daemon
walks the storage tree end-to-end and re-upserts everything.
2. **Real time.** Subscribes to the node's control socket (same stream as
`rad node events`) and reacts to:
- `RefsFetched`, `LocalRefsAnnounced`, `CanonicalRefUpdated`, `RefsSynced`
→ re-index the affected repo.
- `RefsAnnounced` → re-index only if the rid is one we locally seed
(filtered against an in-memory cache, so gossip about repos we don't
host is dropped at near-zero cost).
- `RefsFetched`, `LocalRefsAnnounced`, `CanonicalRefUpdated`, `RefsSynced` →
re-index the affected repo.
- `RefsAnnounced` → re-index only if the rid is one we locally seed (filtered
against an in-memory cache, so gossip about repos we don't host is dropped
at near-zero cost).
- `SeedDiscovered` and `SeedDropped` are ignored; the seeding count they
affect is refreshed by the periodic rescan.
Affected repos are collected and re-indexed together every
`RADICLE_SEARCH_BATCH_SECS` (default 30s), so a burst of events for the
same repo costs a single re-index.
3. **Periodic rescan.** Every `RADICLE_SEARCH_RESCAN_SECS` (default 1h)
as a safety net for missed events.
`RADICLE_SEARCH_BATCH_SECS` (default 30s), so a burst of events for the same
repo costs a single re-index.
3. **Periodic rescan.** Every `RADICLE_SEARCH_RESCAN_SECS` (default 1h) as a
safety net for missed events.
## Setup
@@ -77,19 +78,18 @@ For production set a master key with `--master-key <KEY>` and run with
### 2. Build and run radicle-search
`radicle-search` is a member of the Cargo workspace rooted at the
repository root (it lives under `crates/`), so build it from there. Cargo
places the binary in the workspace-wide `target/`, not under this crate's
directory:
`radicle-search` is a member of the Cargo workspace rooted at the repository
root (it lives under `crates/`), so build it from there. Cargo places the binary
in the workspace-wide `target/`, not under this crate's directory:
```sh
cargo build --release -p radicle-search
./target/release/radicle-search
```
The daemon picks up the Radicle profile from `RAD_HOME` and the node
control socket from `RAD_SOCKET` (or their defaults). On first launch it
bootstraps the index in a few seconds, then listens for node events.
The daemon picks up the Radicle profile from `RAD_HOME` and the node control
socket from `RAD_SOCKET` (or their defaults). On first launch it bootstraps the
index in a few seconds, then listens for node events.
### 3. Point httpd at the index
@@ -99,35 +99,35 @@ Set `RADICLE_SEARCH_URL` (plus optional friends) before launching httpd:
RADICLE_SEARCH_URL=http://127.0.0.1:7700 radicle-httpd
```
On boot httpd logs `search backend enabled: url=… index=repos`. With the
env var absent, httpd works exactly as before.
On boot httpd logs `search backend enabled: url=… index=repos`. With the env var
absent, httpd works exactly as before.
## Configuration
All via environment variables:
| Variable | Default | Purpose |
|---|---|---|
| `RADICLE_SEARCH_MEILI_URL` | `http://localhost:7700` | Meilisearch instance to connect to. |
| `RADICLE_SEARCH_MEILI_KEY` | _(none)_ | Meilisearch master key (production mode). |
| `RADICLE_SEARCH_INDEX_NAME` | `repos` | Index name this daemon maintains. |
| `RADICLE_SEARCH_RESCAN_SECS` | `3600` | Interval between safety-net full rescans. |
| `RADICLE_SEARCH_RECONNECT_BACKOFF_SECS` | `5` | Delay before reconnecting after an event-stream disconnect. |
| `RADICLE_SEARCH_BATCH_SECS` | `30` | Interval at which repos touched by node events are re-indexed. |
| `RAD_HOME` | `~/.radicle` | Standard Radicle profile path. |
| `RAD_SOCKET` | `$RAD_HOME/node/control.sock` | Standard node control socket. |
| Variable | Default | Purpose |
| --------------------------------------- | ----------------------------- | -------------------------------------------------------------- |
| `RADICLE_SEARCH_MEILI_URL` | `http://localhost:7700` | Meilisearch instance to connect to. |
| `RADICLE_SEARCH_MEILI_KEY` | _(none)_ | Meilisearch master key (production mode). |
| `RADICLE_SEARCH_INDEX_NAME` | `repos` | Index name this daemon maintains. |
| `RADICLE_SEARCH_RESCAN_SECS` | `3600` | Interval between safety-net full rescans. |
| `RADICLE_SEARCH_RECONNECT_BACKOFF_SECS` | `5` | Delay before reconnecting after an event-stream disconnect. |
| `RADICLE_SEARCH_BATCH_SECS` | `30` | Interval at which repos touched by node events are re-indexed. |
| `RAD_HOME` | `~/.radicle` | Standard Radicle profile path. |
| `RAD_SOCKET` | `$RAD_HOME/node/control.sock` | Standard node control socket. |
httpd reads a parallel set to decide, at runtime, whether to use the index:
| Variable | Default | Purpose |
|---|---|---|
| `RADICLE_SEARCH_URL` | _(none)_ | Meilisearch instance httpd queries. **Unset disables search** — httpd serves listings and search from its storage walk. |
| `RADICLE_SEARCH_KEY` | _(none)_ | Meilisearch API key. |
| `RADICLE_SEARCH_INDEX_NAME` | `repos` | Index name to query. |
| `RADICLE_SEARCH_TIMEOUT_MS` | `500` | Per-query timeout in milliseconds (must be a non-zero integer). |
| Variable | Default | Purpose |
| --------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- |
| `RADICLE_SEARCH_URL` | _(none)_ | Meilisearch instance httpd queries. **Unset disables search** — httpd serves listings and search from its storage walk. |
| `RADICLE_SEARCH_KEY` | _(none)_ | Meilisearch API key. |
| `RADICLE_SEARCH_INDEX_NAME` | `repos` | Index name to query. |
| `RADICLE_SEARCH_TIMEOUT_MS` | `500` | Per-query timeout in milliseconds (must be a non-zero integer). |
Use the same URL/key/index values in both processes. A single
`radicle-httpd` binary handles both modes — no build-time feature flag is
involved. When `RADICLE_SEARCH_URL` is unset, or when a query to the
backend fails or times out, httpd transparently falls back to the storage
walk, so the API behaves identically either way.
Use the same URL/key/index values in both processes. A single `radicle-httpd`
binary handles both modes — no build-time feature flag is involved. When
`RADICLE_SEARCH_URL` is unset, or when a query to the backend fails or times
out, httpd transparently falls back to the storage walk, so the API behaves
identically either way.
+22 -17
View File
@@ -39,15 +39,18 @@ sudo apt install radicle
### Pacman (Arch Linux)
Install [`radicle`](https://archlinux.org/packages/extra/x86_64/radicle/) via pacman.
Install [`radicle`](https://archlinux.org/packages/extra/x86_64/radicle/) via
pacman.
### Nix
Install `radicle-node` from [Nixpkgs](https://search.nixos.org/packages?query=radicle-node).
Install `radicle-node` from
[Nixpkgs](https://search.nixos.org/packages?query=radicle-node).
## Reproducible binaries
Radicle publishes reproducible builds for Linux and macOS. Each release includes binaries, SHA256 checksums, and SSH signatures.
Radicle publishes reproducible builds for Linux and macOS. Each release includes
binaries, SHA256 checksums, and SSH signatures.
### Radicle
@@ -59,12 +62,12 @@ curl -O -L https://files.radicle.dev/releases/latest/radicle-$TARGET.tar.xz
Replace `$TARGET` with one of:
| Platform | Target |
|----------|--------|
| Linux x86_64 | `x86_64-unknown-linux-musl` |
| Linux ARM64 | `aarch64-unknown-linux-musl` |
| macOS x86_64 | `x86_64-apple-darwin` |
| macOS ARM64 | `aarch64-apple-darwin` |
| Platform | Target |
| ------------ | ---------------------------- |
| Linux x86_64 | `x86_64-unknown-linux-musl` |
| Linux ARM64 | `aarch64-unknown-linux-musl` |
| macOS x86_64 | `x86_64-apple-darwin` |
| macOS ARM64 | `aarch64-apple-darwin` |
### Radicle HTTP daemon
@@ -82,11 +85,13 @@ Extract the archive to `~/.radicle/`:
tar -xf radicle-$TARGET.tar.xz -C ~/.radicle/
```
This places binaries in `~/.radicle/bin` and man pages in `~/.radicle/man`. Make sure `~/.radicle/bin` is in your `PATH`.
This places binaries in `~/.radicle/bin` and man pages in `~/.radicle/man`. Make
sure `~/.radicle/bin` is in your `PATH`.
## Verification
Each archive is accompanied by a `.sig` (SSH signature) and `.sha256` (checksum) file.
Each archive is accompanied by a `.sig` (SSH signature) and `.sha256` (checksum)
file.
### Signature verification
@@ -98,13 +103,13 @@ ssh-keygen -Y check-novalidate -n file -s ARCHIVE.tar.xz.sig < ARCHIVE.tar.xz
The following team members sign releases:
| Name | Public key |
|------|-----------|
| Name | Public key |
| --------------------- | ---------------------------------------------------------------------------------- |
| cloudhead@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL460KIEccS4881p7PPpiiQBsxF+H5tgC6De6crw9rbU` |
| erik@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBrJyJTwj/xG7F7qY0HDFXbb8A+xNNH8eILQ8hlvKW7/` |
| fintan@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFsaRqAJ1r6bBFwlcWzJKN7DdjItQDumCNc0wqw6Dvk` |
| lorenz@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFhK7CqgIIbSthoNn8ea32krOnMzC807Z+PpBkR2YOVj` |
| rudolfs@osins.org | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPueml1FxzjvwbD7vRZfwoaoyuxLy0L+WLBwSNiVoJe5` |
| erik@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBrJyJTwj/xG7F7qY0HDFXbb8A+xNNH8eILQ8hlvKW7/` |
| fintan@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFsaRqAJ1r6bBFwlcWzJKN7DdjItQDumCNc0wqw6Dvk` |
| lorenz@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFhK7CqgIIbSthoNn8ea32krOnMzC807Z+PpBkR2YOVj` |
| rudolfs@osins.org | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPueml1FxzjvwbD7vRZfwoaoyuxLy0L+WLBwSNiVoJe5` |
### Checksum verification
+56 -25
View File
@@ -15,52 +15,83 @@ title: Glossary
<h1 class="txt-bold-32">Glossary. <span class="txt-color-tertiary">Definitions of Radicle-specific terms.</span></h1>
**Bootstrap node** — A well-known seed node that new nodes connect to on first startup to discover the rest of the network. Radicle ships pre-configured with `seed.radicle.garden` and `seed.radicle.xyz`.
**Bootstrap node** — A well-known seed node that new nodes connect to on first
startup to discover the rest of the network. Radicle ships pre-configured with
`seed.radicle.garden` and `seed.radicle.xyz`.
**Canonical JSON** — A standardized JSON encoding with sorted keys and no unnecessary whitespace. Used for identity documents so that hashing produces deterministic results.
**Canonical JSON** — A standardized JSON encoding with sorted keys and no
unnecessary whitespace. Used for identity documents so that hashing produces
deterministic results.
**Canonical reference** — A Git reference whose authoritative state is determined by delegate signatures meeting the threshold requirement.
**Canonical reference** — A Git reference whose authoritative state is
determined by delegate signatures meeting the threshold requirement.
**COB (Collaborative Object)** — A CRDT-based data structure stored as Git objects. Used to implement issues, patches, and other social features.
**COB (Collaborative Object)** — A CRDT-based data structure stored as Git
objects. Used to implement issues, patches, and other social features.
**`config.json`** — The node configuration file, typically at `~/.radicle/config.json`. Controls seeding policy, external addresses, bootstrap peers, and scope.
**`config.json`** — The node configuration file, typically at
`~/.radicle/config.json`. Controls seeding policy, external addresses, bootstrap
peers, and scope.
**CRDT (Conflict-Free Replicated Data Type)** — A data structure designed so that concurrent modifications by different participants always merge deterministically without conflicts. The foundation of COBs.
**CRDT (Conflict-Free Replicated Data Type)** — A data structure designed so
that concurrent modifications by different participants always merge
deterministically without conflicts. The foundation of COBs.
**Delegate** — A person (identified by their DID) authorized to make changes to a repository’s identity document and canonical branches.
**Delegate** — A person (identified by their DID) authorized to make changes to
a repository’s identity document and canonical branches.
**DID (Decentralized Identifier)** — A W3C standard identifier encoding a public key. In Radicle, DIDs use the `did:key` method with Ed25519 keys. Format: `did:key:z6Mk...`.
**DID (Decentralized Identifier)** — A W3C standard identifier encoding a public
key. In Radicle, DIDs use the `did:key` method with Ed25519 keys. Format:
`did:key:z6Mk...`.
**Gossip protocol** — The communication mechanism by which Radicle nodes discover peers and learn about repository updates.
**Gossip protocol** — The communication mechanism by which Radicle nodes
discover peers and learn about repository updates.
**Identity document** — A JSON document at `refs/rad/id` that defines a repository’s metadata, delegates, and threshold.
**Identity document** — A JSON document at `refs/rad/id` that defines a
repository’s metadata, delegates, and threshold.
**Magic ref** — A special Git reference (like `refs/patches`) that Radicle intercepts during push to trigger specific actions.
**Magic ref** — A special Git reference (like `refs/patches`) that Radicle
intercepts during push to trigger specific actions.
**Namespace** — Git’s mechanism for partitioning references. Radicle uses namespaces to store each peer’s data under their Node ID.
**Namespace** — Git’s mechanism for partitioning references. Radicle uses
namespaces to store each peer’s data under their Node ID.
**Node** — A participant in the Radicle network, running `radicle-node`. Identified by a public key (Node ID).
**Node** — A participant in the Radicle network, running `radicle-node`.
Identified by a public key (Node ID).
**Node ID (NID)** — The public key that identifies a node on the network. Retrieve yours with `rad self --nid`.
**Node ID (NID)** — The public key that identifies a node on the network.
Retrieve yours with `rad self --nid`.
**Patch** — A proposed code change, stored as a COB. Radicle’s equivalent of a pull request.
**Patch** — A proposed code change, stored as a COB. Radicle’s equivalent of a
pull request.
**`rad` remote** — The Git remote configured in Radicle working copies. Handled by `git-remote-rad`.
**`rad` remote** — The Git remote configured in Radicle working copies. Handled
by `git-remote-rad`.
**Radicle Explorer** — The web interface for browsing Radicle repositories, powered by `radicle-httpd`. Currently read-only.
**Radicle Explorer** — The web interface for browsing Radicle repositories,
powered by `radicle-httpd`. Currently read-only.
**Reference announcement** — A gossip message that informs peers a repository has been updated. Triggers Git fetches by interested nodes.
**Reference announcement** — A gossip message that informs peers a repository
has been updated. Triggers Git fetches by interested nodes.
**RID (Repository Identity)** — A globally unique, self-certifying identifier for a Radicle repository. Prefixed with `rad:`.
**RID (Repository Identity)** — A globally unique, self-certifying identifier
for a Radicle repository. Prefixed with `rad:`.
**Scope** — Controls which peers’ data your node synchronizes. Options are `followed` (only explicit follows) or `all`.
**Scope** — Controls which peers’ data your node synchronizes. Options are
`followed` (only explicit follows) or `all`.
**Seed / Seed node** — An always-on server node that provides high availability for repositories. Listens on TCP port 8776 by default.
**Seed / Seed node** — An always-on server node that provides high availability
for repositories. Listens on TCP port 8776 by default.
**Seeding** — Serving a repository to other nodes on the network. Distinct from tracking (storing locally for your own use).
**Seeding** — Serving a repository to other nodes on the network. Distinct from
tracking (storing locally for your own use).
**Seeding policy** — Configuration that determines which repositories a node will replicate. `allow` means replicate everything; `block` means only replicate explicitly allowed repos.
**Seeding policy** — Configuration that determines which repositories a node
will replicate. `allow` means replicate everything; `block` means only replicate
explicitly allowed repos.
**Threshold** — The number of delegates required to agree on a change for it to be considered canonical. Defined in the identity document.
**Threshold** — The number of delegates required to agree on a change for it to
be considered canonical. Defined in the identity document.
**Tracking** — Telling your node to store a local copy of a repository and fetch updates. Distinct from seeding (also serving it to others).
**Tracking** — Telling your node to store a local copy of a repository and fetch
updates. Distinct from seeding (also serving it to others).
@@ -122,7 +122,9 @@ test("math", async ({ page }) => {
waitUntil: "networkidle",
});
await expect(page.getByText("The Cauchy-Schwarz Inequality")).toBeVisible();
await expect(page.locator(".katex-display .katex-html").first()).toBeVisible();
await expect(
page.locator(".katex-display .katex-html").first(),
).toBeVisible();
await expect(page).toHaveScreenshot({ fullPage: true });
});
+12 -11
View File
@@ -1,14 +1,14 @@
# Open Graph
Worker on `open-graph.radicle.network` that serves OG meta tags and
preview card images for social media embeds.
Worker on `open-graph.radicle.network` that serves OG meta tags and preview card
images for social media embeds.
## How it works
`radicle.network` is a plain static SPA. A Cloudflare Redirect Rule
302s social media crawlers to `open-graph.radicle.network`, which
returns HTML with OG tags. The `og:image` points to `/cards/...` on
the same domain, where the worker renders PNG cards via Satori + resvg-wasm.
`radicle.network` is a plain static SPA. A Cloudflare Redirect Rule 302s social
media crawlers to `open-graph.radicle.network`, which returns HTML with OG tags.
The `og:image` points to `/cards/...` on the same domain, where the worker
renders PNG cards via Satori + resvg-wasm.
Browsers and search crawlers hit the SPA directly — no worker cost.
@@ -18,8 +18,8 @@ Browsers and search crawlers hit the SPA directly — no worker cost.
- `/cards/*` — PNG card images (cached 2 hours)
Card routes mirror app routes: `/cards/nodes/:host/:rid`,
`/cards/nodes/:host/:rid/issues/:id`, etc. When API fetches fail,
cards degrade: specific item -> repo -> node -> home.
`/cards/nodes/:host/:rid/issues/:id`, etc. When API fetches fail, cards degrade:
specific item -> repo -> node -> home.
## Cloudflare Redirect Rule
@@ -42,14 +42,15 @@ cards degrade: specific item -> repo -> node -> home.
)
```
Dynamic 302 to `concat("https://open-graph.radicle.network", http.request.uri.path)`
Dynamic 302 to
`concat("https://open-graph.radicle.network", http.request.uri.path)`
## Rate Limiting
**Security > WAF > Rate limiting rules** on the `radicle.network` zone:
Verified bots are rate limited to 50 requests per 10 seconds across
all of `radicle.network`, with a 1 minute block.
Verified bots are rate limited to 50 requests per 10 seconds across all of
`radicle.network`, with a 1 minute block.
## Deploy