335 lines
16 KiB
YAML
335 lines
16 KiB
YAML
name: radicle
|
|
|
|
services:
|
|
# The web UI: the Radicle Explorer bundle served as static files by Caddy.
|
|
# It is a pure client-side app — every request it makes goes from the visitor's
|
|
# browser straight to the API below, which is why that one needs its own domain.
|
|
radicle:
|
|
image: dirk1980/radicle-explorer-container@sha256:0e31485c38a765e83a10b1a4bf71b12d4f9fec630881d171bba736a4a56a16ce
|
|
container_name: radicle
|
|
user: $PUID:$PGID
|
|
restart: unless-stopped
|
|
cpu_shares: 80
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 128M
|
|
environment:
|
|
TZ: $TZ
|
|
# Caddy writes its autosaved config and its (unused here) certificate store
|
|
# under these two paths. The image points them at /config and /data, which are
|
|
# root-owned, so as $PUID the process cannot create them and refuses to start.
|
|
XDG_CONFIG_HOME: /tmp/caddy-config
|
|
XDG_DATA_HOME: /tmp/caddy-data
|
|
volumes:
|
|
# Replaces the image's own :80 Caddyfile — see the comments in that file.
|
|
- ${DATA_ROOT:-/DATA}/AppData/$AppID/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8080/"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 15s
|
|
expose:
|
|
- 8080
|
|
labels:
|
|
caddy_0: radicle-${APP_DOMAIN}
|
|
caddy_0.import: gateway_tls
|
|
caddy_0.reverse_proxy: "{{upstreams 8080}}"
|
|
caddy_1: radicle-${APP_PUBLIC_IP_DASH}.nip.io
|
|
caddy_1.import: gateway_tls
|
|
caddy_1.reverse_proxy: "{{upstreams 8080}}"
|
|
caddy_2: radicle-${APP_PUBLIC_IP_DASH}.sslip.io
|
|
caddy_2.reverse_proxy: "{{upstreams 8080}}"
|
|
networks:
|
|
- pcs
|
|
|
|
# radicle-httpd — the node's read-only JSON API, on its own domain.
|
|
#
|
|
# This domain is deliberately NOT behind the SSO gate: the explorer above is a
|
|
# static bundle running in the visitor's browser, so it is the browser that calls
|
|
# this API, cross-origin. The daemon serves public repositories only (private ones
|
|
# are filtered out upstream) and has no write endpoints at all. See rationale.md.
|
|
radicle-api:
|
|
image: dirk1980/radicle-httpd@sha256:5e95be30e4bde55b509a65fd56f8b825cafd0f5cd752c1a13e79aad75b958895
|
|
container_name: radicle-api
|
|
user: $PUID:$PGID
|
|
restart: unless-stopped
|
|
cpu_shares: 70
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512M
|
|
command: ["--listen", "0.0.0.0:8080"]
|
|
environment:
|
|
TZ: $TZ
|
|
volumes:
|
|
# Same Radicle home as the node: the daemon reads the repository storage
|
|
# directly and talks to the node over the control socket inside it.
|
|
- ${DATA_ROOT:-/DATA}/AppData/$AppID/home/:/radicle-home/
|
|
healthcheck:
|
|
# curl, not wget: this image carries curl and no busybox.
|
|
test: ["CMD", "curl", "-fsS", "-o", "/dev/null", "http://127.0.0.1:8080/api/v1"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 15s
|
|
expose:
|
|
- 8080
|
|
labels:
|
|
caddy_0: radicle-api-${APP_DOMAIN}
|
|
caddy_0.import: gateway_tls
|
|
caddy_0.reverse_proxy: "{{upstreams 8080}}"
|
|
caddy_1: radicle-api-${APP_PUBLIC_IP_DASH}.nip.io
|
|
caddy_1.import: gateway_tls
|
|
caddy_1.reverse_proxy: "{{upstreams 8080}}"
|
|
caddy_2: radicle-api-${APP_PUBLIC_IP_DASH}.sslip.io
|
|
caddy_2.reverse_proxy: "{{upstreams 8080}}"
|
|
networks:
|
|
- pcs
|
|
|
|
# The node itself: speaks the Radicle peer-to-peer protocol (gossip, fetch,
|
|
# announce) on TCP 8776. No HTTP, so no Caddy route and no place on the shared
|
|
# network — it only needs egress, which the app-internal bridge provides.
|
|
radicle-node:
|
|
image: dirk1980/radicle-seed-node@sha256:78ccee526ad4b4f4d7c79c2323b032a7209858a48e1dc9c077272ff1660050e2
|
|
container_name: radicle-node
|
|
user: $PUID:$PGID
|
|
restart: unless-stopped
|
|
cpu_shares: 50
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 1G
|
|
environment:
|
|
TZ: $TZ
|
|
volumes:
|
|
- ${DATA_ROOT:-/DATA}/AppData/$AppID/home/:/radicle-home/
|
|
ports:
|
|
# Radicle peer-to-peer protocol. Other nodes dial in here to fetch the
|
|
# repositories this node seeds; without it the node still syncs outbound,
|
|
# but nobody can fetch from it. Hence the needs-public-ip tag.
|
|
- "8776:8776"
|
|
healthcheck:
|
|
# `rad node status` round-trips the node's control socket, which is the only
|
|
# liveness signal this container has — but it also renders the peer table, and
|
|
# that takes 3-8s on an idle node and longer while the node is syncing. Hence
|
|
# the unusually generous timeout: a 10s one fails during the first sync and
|
|
# flaps the container between healthy and unhealthy.
|
|
test: ["CMD", "rad", "node", "status"]
|
|
interval: 60s
|
|
timeout: 30s
|
|
retries: 3
|
|
start_period: 90s
|
|
networks:
|
|
- radicle-internal
|
|
|
|
networks:
|
|
pcs:
|
|
name: ${APP_NET:-pcs}
|
|
external: true
|
|
# Egress-only network for the node. Nothing else joins it — the node and the
|
|
# API daemon share state through the Radicle home directory, not over TCP.
|
|
radicle-internal:
|
|
driver: bridge
|
|
|
|
x-casaos:
|
|
architectures:
|
|
- amd64
|
|
main: radicle
|
|
developer: Radicle
|
|
category: Developer
|
|
tags: ["developer", "personal", "needs-public-ip"]
|
|
description:
|
|
en_us: |
|
|
Radicle — your own peer-to-peer forge
|
|
|
|
Radicle is a sovereign alternative to GitHub and GitLab. Instead of a company's server holding the canonical copy of your code, every participant keeps a full replica and gossips changes directly to peers. Repositories, issues and patches are all Git objects, signed by their author and replicated across the network — there is no central service to go down, change its terms, or lock you out.
|
|
|
|
This app runs a complete Radicle node on your server, plus the official Radicle Explorer web interface to browse it:
|
|
|
|
• Your node stays online and keeps your repositories available even when your laptop is not
|
|
• Anything you push from your machine is replicated here and served to the network
|
|
• Issues, patches and code are browsable in your own web UI, on your own domain
|
|
• Anyone can clone your repositories over plain HTTPS, no account required
|
|
|
|
The web interface is for browsing. Creating repositories, opening issues and proposing patches happen with the `rad` command line on your own machine — the node here is what keeps them alive and reachable.
|
|
|
|
Repositories are only replicated once you seed them. A fresh node starts empty and stores nothing until you tell it what to follow.
|
|
fr_fr: |
|
|
Radicle — votre forge pair-à-pair
|
|
|
|
Radicle est une alternative souveraine à GitHub et GitLab. Plutôt qu'un serveur d'entreprise détenant la copie de référence de votre code, chaque participant conserve un réplica complet et propage ses modifications directement à ses pairs. Dépôts, tickets et patches sont tous des objets Git, signés par leur auteur et répliqués sur le réseau — aucun service central ne peut tomber, changer ses conditions ou vous verrouiller dehors.
|
|
|
|
Cette application fait tourner un nœud Radicle complet sur votre serveur, ainsi que l'interface web officielle Radicle Explorer pour le parcourir :
|
|
|
|
• Votre nœud reste en ligne et garde vos dépôts disponibles même quand votre portable ne l'est pas
|
|
• Tout ce que vous poussez depuis votre machine est répliqué ici et servi au réseau
|
|
• Tickets, patches et code se consultent dans votre propre interface web, sur votre domaine
|
|
• N'importe qui peut cloner vos dépôts en HTTPS, sans compte
|
|
|
|
L'interface web sert à consulter. Créer des dépôts, ouvrir des tickets et proposer des patches se fait avec la ligne de commande `rad` sur votre machine — le nœud installé ici est ce qui les maintient vivants et accessibles.
|
|
|
|
Les dépôts ne sont répliqués qu'une fois que vous les suivez. Un nœud neuf démarre vide et ne stocke rien tant que vous ne lui avez rien indiqué.
|
|
zh_cn: |
|
|
Radicle — 属于你自己的点对点代码托管
|
|
|
|
Radicle 是 GitHub 与 GitLab 的自主替代方案。代码的权威副本不再由某家公司的服务器保管,而是每个参与者都持有完整副本,并直接向对等节点传播更改。仓库、议题和补丁都是 Git 对象,由作者签名并在网络中复制——没有中心服务会宕机、修改条款或将你拒之门外。
|
|
|
|
此应用在你的服务器上运行一个完整的 Radicle 节点,并附带官方的 Radicle Explorer 网页界面:
|
|
|
|
• 节点持续在线,即使你的笔记本关机,仓库依然可用
|
|
• 你从本机推送的内容会复制到这里,并向网络提供服务
|
|
• 议题、补丁和代码都可以在你自己的域名下浏览
|
|
• 任何人都可以通过 HTTPS 克隆你的仓库,无需账户
|
|
|
|
网页界面用于浏览。创建仓库、提交议题和提出补丁请在本机使用 `rad` 命令行完成——这里的节点负责让它们保持在线、可被获取。
|
|
|
|
仓库只有在你选择跟随(seed)之后才会被复制。全新节点启动时是空的,在你指定之前不会存储任何内容。
|
|
icon: icon.png
|
|
screenshot_link:
|
|
- screenshot-1.png
|
|
- screenshot-2.png
|
|
- screenshot-3.png
|
|
tagline:
|
|
en_us: Your own peer-to-peer Git forge — a Radicle node and web explorer on your domain.
|
|
fr_fr: Votre forge Git pair-à-pair — un nœud Radicle et son explorateur web sur votre domaine.
|
|
zh_cn: 你自己的点对点 Git 托管——运行在你域名下的 Radicle 节点与网页浏览器。
|
|
thumbnail: thumbnail.png
|
|
tips:
|
|
before_install:
|
|
en_us: |
|
|
This app is public on purpose
|
|
|
|
A Radicle node is a participant in a public peer-to-peer network. Both of
|
|
its addresses are served directly to the public, bypassing the server's
|
|
single sign-on:
|
|
|
|
| Address | What it serves |
|
|
| ------- | -------------- |
|
|
| `https://radicle-$APP_DOMAIN` | The web interface |
|
|
| `https://radicle-api-$APP_DOMAIN/api/v1` | The read-only API the interface calls |
|
|
|
|
Only **public** repositories are served, the API has no write endpoints, and
|
|
a fresh node holds nothing until you seed something. See `rationale.md`.
|
|
|
|
Connecting your machine
|
|
|
|
Install the `rad` CLI from [radicle.dev](https://radicle.dev), then push a
|
|
repository and tell this node to seed it. Your node's identity (its Node ID)
|
|
is printed at `https://radicle-api-$APP_DOMAIN/api/v1` under `nid`.
|
|
|
|
Peer-to-peer port
|
|
|
|
The node publishes TCP **8776** so other peers can fetch from it. On a server
|
|
without a directly reachable public IP the app still works — your repositories
|
|
replicate outward to public seeds — but nobody fetches from you directly.
|
|
fr_fr: |
|
|
Cette application est publique, volontairement
|
|
|
|
Un nœud Radicle participe à un réseau pair-à-pair public. Ses deux adresses
|
|
sont servies directement au public, en contournant l'authentification
|
|
unique du serveur :
|
|
|
|
| Adresse | Ce qu'elle sert |
|
|
| ------- | --------------- |
|
|
| `https://radicle-$APP_DOMAIN` | L'interface web |
|
|
| `https://radicle-api-$APP_DOMAIN/api/v1` | L'API en lecture seule appelée par l'interface |
|
|
|
|
Seuls les dépôts **publics** sont servis, l'API n'a aucun point d'écriture, et
|
|
un nœud neuf ne contient rien tant que vous n'avez rien suivi. Voir
|
|
`rationale.md`.
|
|
|
|
Connecter votre machine
|
|
|
|
Installez la CLI `rad` depuis [radicle.dev](https://radicle.dev), puis poussez
|
|
un dépôt et demandez à ce nœud de le suivre. L'identité de votre nœud (Node ID)
|
|
est affichée sur `https://radicle-api-$APP_DOMAIN/api/v1`, champ `nid`.
|
|
|
|
Port pair-à-pair
|
|
|
|
Le nœud publie le port TCP **8776** pour que les autres pairs puissent récupérer
|
|
ses dépôts. Sur un serveur sans IP publique directement joignable, l'application
|
|
fonctionne quand même — vos dépôts se répliquent vers les seeds publics — mais
|
|
personne ne récupère directement chez vous.
|
|
zh_cn: |
|
|
此应用有意保持公开
|
|
|
|
Radicle 节点是公开点对点网络的参与者。它的两个地址都直接向公众开放,
|
|
绕过服务器的单点登录:
|
|
|
|
| 地址 | 提供的内容 |
|
|
| ---- | ---------- |
|
|
| `https://radicle-$APP_DOMAIN` | 网页界面 |
|
|
| `https://radicle-api-$APP_DOMAIN/api/v1` | 界面调用的只读 API |
|
|
|
|
仅提供**公开**仓库,API 没有任何写入接口,全新节点在你指定跟随之前不含任何内容。
|
|
详见 `rationale.md`。
|
|
|
|
连接你的机器
|
|
|
|
从 [radicle.dev](https://radicle.dev) 安装 `rad` 命令行,然后推送仓库并让此节点
|
|
跟随它。节点身份(Node ID)显示在 `https://radicle-api-$APP_DOMAIN/api/v1` 的
|
|
`nid` 字段。
|
|
|
|
点对点端口
|
|
|
|
节点公开 TCP **8776**,供其他对等节点抓取。若服务器没有可直接访问的公网 IP,应用
|
|
仍可使用——你的仓库会向公共 seed 节点复制——但别人无法直接从你这里抓取。
|
|
title:
|
|
en_us: Radicle
|
|
index: /
|
|
webui_port: 8080
|
|
store_app_id: radicle
|
|
|
|
x-compose-app:
|
|
schema_version: 2
|
|
folders:
|
|
- path: /DATA/AppData/$AppID/home
|
|
user: $PUID
|
|
group: $PGID
|
|
- path: /DATA/AppData/$AppID/caddy
|
|
user: $PUID
|
|
group: $PGID
|
|
init:
|
|
# Creates the node's Ed25519 identity. Radicle has no first-run wizard: without
|
|
# a key pair in the Radicle home, radicle-node exits immediately. `rad auth`
|
|
# prompts for an optional passphrase, hence the empty line on stdin — a seed
|
|
# node's key secures peer connections and signs nothing permanent, so upstream's
|
|
# own guidance is to leave it unencrypted (a passphrase would have to be typed
|
|
# in at every start).
|
|
- name: init-identity
|
|
image: dirk1980/radicle-seed-node@sha256:78ccee526ad4b4f4d7c79c2323b032a7209858a48e1dc9c077272ff1660050e2
|
|
user: $PUID:$PGID
|
|
volumes:
|
|
- /DATA/AppData/$AppID/home:/radicle-home
|
|
command: ["sh", "-c", "yes '' | rad auth --alias radicle-${APP_PUBLIC_IP_DASH} --stdin"]
|
|
when: absent:/DATA/AppData/$AppID/home/keys/radicle
|
|
files:
|
|
# The node's config carries the deployment's own addresses, so it is re-rendered
|
|
# on every up rather than frozen at install. `rad auth` above writes a default
|
|
# config.json first; this replaces it with ours and keeps it in step if the PCS
|
|
# domain or public IP ever changes.
|
|
#
|
|
# It also carries the bootstrap seeds, and those are NOT the compiled-in defaults.
|
|
# radicle 1.10 ships `iris.radicle.network` / `rosa.radicle.network`, whose IPv4
|
|
# addresses refuse connections on 8776 (verified from two networks, 2026-09-04) —
|
|
# a node configured with them never finds a peer. The same two nodes answer under
|
|
# their previous hostnames, which is what this file uses; radicle logs a rename
|
|
# warning for them and keeps working. Revisit when upstream's new addresses accept
|
|
# peer traffic again.
|
|
- path: /DATA/AppData/$AppID/home/config.json
|
|
from: home/config.json.tmpl
|
|
ensure: always
|
|
mode: "0644"
|
|
# Carries this deployment's API domain in a redirect, so it is re-rendered too.
|
|
- path: /DATA/AppData/$AppID/caddy/Caddyfile
|
|
from: caddy/Caddyfile.tmpl
|
|
ensure: always
|
|
mode: "0644"
|
|
# The landing path is handled by the Caddy redirect above, not here: neither
|
|
# `webui-path` nor `x-casaos.index` is interpolated, so a ${...} written in
|
|
# either reaches the browser as a literal.
|
|
webui-host: radicle-${domain}
|
|
webui-path: /
|