Rename the bridge to the forge service; this repository is the add-on

- bridge/ → forge/, module and binary ipcr-forge, image ghcr.io/yundera/ipcr-forge
  (was ipcr-forge-bridge); releases are plain vX.Y.Z tags (workflow image.yml)
- data folders $AppID/bridge/{state,secrets} → $AppID/forge/..., in-container /forge
- docs link to the engine (Yundera/ipcr) instead of relative paths; README
This commit is contained in:
Worph
2026-10-05 13:21:11 +00:00
parent 291564bf1d
commit 98ff2f5783
14 changed files with 169 additions and 128 deletions
+33 -33
View File
@@ -10,15 +10,15 @@ name: ipcr-forge
# → ipcr-kubo / ipcr-registry
# docker pull ipcr.localhost:4767/ipns/<forge name>/<owner>/<repo>:1.2.3 (anyone)
#
# gitea ──system webhook──▶ ipcr-forge (bridge) ──rad init / git push rad──▶ radicle-node
# gitea ──system webhook──▶ ipcr-forge ──rad init / git push rad──▶ radicle-node
# every PUBLIC repository, one way: Radicle is a distribution mirror signed by this node.
#
# The tile opens Gitea. ipcr-forge-<domain> is the forge's own page, served by the bridge: how to
# The tile opens Gitea. ipcr-forge-<domain> is the forge's own page, served by the forge service: how to
# publish, the published images with the `image:` line to use, and the Radicle mirrors.
#
# Gitea, Radicle and IPCR services are copied from their standalone listings (Apps/Gitea,
# Apps/Radicle, Apps/IPCR), so each keeps its own comments and rationale.md; the differences are
# marked "Forge:". Data folders move, one subfolder per part: $AppID/{gitea,radicle,bridge,ci,ipcr}/.
# marked "Forge:". Data folders move, one subfolder per part: $AppID/{gitea,radicle,forge,ci,ipcr}/.
#
# Replaces the Gitea, Radicle and IPCR apps; it cannot sit beside them. It uses the same container
# names, domains (gitea-, radicle-, radicle-api-) and host ports (8776, 4768, 127.0.0.1:4767).
@@ -30,19 +30,19 @@ name: ipcr-forge
services:
# ═══ Bridge — the forge's page, and Gitea → Radicle ════════════════════════════
# ipcr-forge-bridge (bridge/ in the IPCR repository). Receives Gitea's system webhook on :8081
# ipcr-forge (forge/ in the ipcr-forge repository). Receives Gitea's system webhook on :8081
# (internal: no Caddy route) and mirrors every public repository to the Radicle node below,
# with the stock `rad` CLI against the node's own home. Serves the forge's page on :8080.
# Its install step (forge-setup, below) mints its Gitea token and registers the webhook.
ipcr-forge:
image: ghcr.io/yundera/ipcr-forge-bridge:0.4.0
image: ghcr.io/yundera/ipcr-forge:0.4.0
container_name: ipcr-forge
user: $PUID:$PGID
read_only: true
cap_drop: [ALL]
security_opt: ["no-new-privileges:true"]
restart: unless-stopped
command: ["ipcr-forge-bridge", "serve"]
command: ["ipcr-forge", "serve"]
cpu_shares: 30
deploy:
resources:
@@ -53,7 +53,7 @@ services:
GITEA_URL: http://gitea:3000
GITEA_OWNER: gitea_admin
SECRETS_DIR: /secrets
STATE_DIR: /bridge
STATE_DIR: /forge
RAD_HOME: /radicle-home
IPCR_PUBLISHED: /srv/ipcr-state/published.json
RECONCILE: 10m
@@ -77,9 +77,9 @@ services:
# radicle-api has.
- ${DATA_ROOT:-/DATA}/AppData/$AppID/radicle/home/:/radicle-home/
# repos.json (Gitea repository → Radicle ID) and one bare mirror per repository.
- ${DATA_ROOT:-/DATA}/AppData/$AppID/bridge/state:/bridge
- ${DATA_ROOT:-/DATA}/AppData/$AppID/forge/state:/forge
# The Gitea token and the webhook secret the install step writes. Read-only here.
- ${DATA_ROOT:-/DATA}/AppData/$AppID/bridge/secrets:/secrets:ro
- ${DATA_ROOT:-/DATA}/AppData/$AppID/forge/secrets:/secrets:ro
# published.json → /images.json, and the admin API's token. Read-only.
- ${DATA_ROOT:-/DATA}/AppData/$AppID/ipcr/state:/srv/ipcr-state:ro
# Kubo's keystore, read-only: the publisher key, for encrypted backups (Kubo exports keys
@@ -89,7 +89,7 @@ services:
- gitea
- radicle-node
healthcheck:
test: ["CMD", "ipcr-forge-bridge", "health"]
test: ["CMD", "ipcr-forge", "health"]
interval: 30s
timeout: 5s
retries: 3
@@ -160,7 +160,7 @@ services:
# Forge: off. Images go to the staging registry that IPCR follows; a second registry would
# be a second place an image could be published from.
GITEA__packages__ENABLED: "false"
# Forge: webhooks may reach the bridge (ipcr-forge, on this server's network) and, as by
# Forge: webhooks may reach the forge service (ipcr-forge, on this server's network) and, as by
# default, any public address. Not `private`: that would let any user's webhook reach every
# container on the shared network.
GITEA__webhook__ALLOWED_HOST_LIST: external,ipcr-forge
@@ -277,11 +277,11 @@ services:
# The staging registry's front door: `localhost:5000` to the daemon and to every job (act runs
# jobs on the host network, which is this namespace), forge-docker:5000 to ipcr-gateway. Plain
# HTTP, which Docker allows to localhost only. It checks a login on every request: a job logs in
# as its repository (<owner>/<repo>, the IPCR_PUSH_TOKEN secret the bridge sets on it) and may
# as its repository (<owner>/<repo>, the IPCR_PUSH_TOKEN secret the forge service sets on it) and may
# push that name only; IPCR logs in as the importer (state/staging-auth, which it generates).
# Same image as the bridge (`ipcr-forge-bridge gate`, bridge/gate.go). See rationale.md.
# Same image as the forge service (`ipcr-forge gate`, forge/gate.go). See rationale.md.
forge-gate:
image: ghcr.io/yundera/ipcr-forge-bridge:0.4.0
image: ghcr.io/yundera/ipcr-forge:0.4.0
container_name: forge-gate
user: $PUID:$PGID
read_only: true
@@ -289,7 +289,7 @@ services:
security_opt: ["no-new-privileges:true"]
restart: unless-stopped
network_mode: service:forge-docker
command: ["ipcr-forge-bridge", "gate"]
command: ["ipcr-forge", "gate"]
cpu_shares: 20
deploy:
resources:
@@ -301,8 +301,8 @@ services:
GATE_UPSTREAM: unix:///run/staging/registry.sock
GATE_CREDENTIALS: /gate/credentials.json
volumes:
# Hashes only, written by the bridge.
- ${DATA_ROOT:-/DATA}/AppData/$AppID/bridge/state/gate:/gate:ro
# Hashes only, written by the forge service.
- ${DATA_ROOT:-/DATA}/AppData/$AppID/forge/state/gate:/gate:ro
- ${DATA_ROOT:-/DATA}/AppData/$AppID/ci/registry-socket:/run/staging
depends_on:
- forge-docker
@@ -342,7 +342,7 @@ services:
- ${DATA_ROOT:-/DATA}/AppData/$AppID/ci/registry-socket:/run/staging
# ═══ Radicle — the public mirror ═══════════════════════════════════════════════
# Verbatim from Apps/Radicle; only its data folder moves to $AppID/radicle/. The bridge
# Verbatim from Apps/Radicle; only its data folder moves to $AppID/radicle/. The forge service
# creates and pushes repositories in this node's storage; the node replicates them.
# The web UI: the Radicle Explorer bundle served as static files by Caddy.
@@ -499,7 +499,7 @@ services:
# Forge: watch the CI's staging registry, over ci-internal. Internal, so no credentials.
IMPORT_WATCH: ${IPCR_IMPORT_WATCH:-http://forge-docker:5000}
IMPORT_AUTH: ${IPCR_IMPORT_AUTH:-}
# Forge: IPCR's login at the staging registry's gate, generated on first start; the bridge
# Forge: IPCR's login at the staging registry's gate, generated on first start; the forge service
# reads it (read-only ipcr/state) and gives it the importer's rights at the gate.
IMPORT_AUTH_FILE: /data/state/staging-auth
IMPORT_AUTH_GENERATE: "true"
@@ -508,8 +508,8 @@ services:
# /ipns/<forge key>/<repo>:<tag>. One name for the whole forge, which a DNSLink domain or an
# ENS name can point at: ipcr.localhost:4767/ipns/example.eth/<repo>:<tag>.
IMPORT_PUBLISHER: ${IPCR_IMPORT_PUBLISHER:-forge}
# Forge: publish only the repositories the bridge allows (public Gitea repositories, minus
# those switched off in the admin pages). Until the bridge sends its list, nothing is.
# Forge: publish only the repositories the forge service allows (public Gitea repositories, minus
# those switched off in the admin pages). Until the forge service sends its list, nothing is.
IMPORT_ALLOW_REQUIRED: "true"
# Forge: delete each tag from the staging registry once published (its blobs go at the
# registry's next start, see forge-registry).
@@ -518,7 +518,7 @@ services:
# its name; resolvers cache them 5 minutes, so a new tag shows up quickly.
IPNS_LIFETIME: ${IPCR_IPNS_LIFETIME:-168h}
IPNS_TTL: 5m
# Forge: the admin API, for the bridge, on ipcr-admin. Bearer token in state/admin-token.
# Forge: the admin API, for the forge service, on ipcr-admin. Bearer token in state/admin-token.
ADMIN_LISTEN: ":4769"
IMPORT_INTERVAL: 60s
STATE_DIR: /data/state
@@ -547,7 +547,7 @@ services:
- ipcr-internal
# Forge only: reaches the staging registry for the import poller above.
- ci-internal
# Forge only: the admin API, reached by the bridge.
# Forge only: the admin API, reached by the forge service.
- ipcr-admin
cpu_shares: 50
deploy:
@@ -841,15 +841,15 @@ x-compose-app:
schema_version: 2
folders:
# ── Bridge ──
- path: /DATA/AppData/$AppID/bridge/state
- path: /DATA/AppData/$AppID/forge/state
user: $PUID
group: $PGID
- path: /DATA/AppData/$AppID/bridge/secrets
- path: /DATA/AppData/$AppID/forge/secrets
user: $PUID
group: $PGID
mode: "0700"
# The gate's credentials (hashes), written by the bridge, read by the gate.
- path: /DATA/AppData/$AppID/bridge/state/gate
# The gate's credentials (hashes), written by the forge service, read by the gate.
- path: /DATA/AppData/$AppID/forge/state/gate
user: $PUID
group: $PGID
# ── Gitea ──
@@ -897,7 +897,7 @@ x-compose-app:
user: $PUID
group: $PGID
mode: "0700"
# Created ahead of Kubo's first start: the bridge mounts it, and Docker would otherwise create
# Created ahead of Kubo's first start: the forge service mounts it, and Docker would otherwise create
# it as root, where Kubo cannot write its keys.
- path: /DATA/AppData/$AppID/ipcr/kubo/keystore
user: $PUID
@@ -927,7 +927,7 @@ x-compose-app:
mode: "0755"
init:
# In order: Radicle's identity before its node starts, then Gitea's admin, then the runner
# token and the bridge's setup, which both need that admin to exist.
# token and the forge service's setup, which both need that admin to exist.
- name: radicle-identity
image: dirk1980/radicle-seed-node@sha256:78ccee526ad4b4f4d7c79c2323b032a7209858a48e1dc9c077272ff1660050e2
user: $PUID:$PGID
@@ -969,21 +969,21 @@ x-compose-app:
mv /runner/token.new /runner/token
chmod 600 /runner/token
when: absent:/DATA/AppData/$AppID/gitea/runner/.runner
# The bridge's Gitea tokens (public-only read; and write:repository for push secrets), the
# The forge service's Gitea tokens (public-only read; and write:repository for push secrets), the
# webhook secret, the system webhook, the admin pages' OAuth2 app, the root organisation and
# the example repository (in that organisation). Idempotent, so on every start: it repairs the webhook.
# The admin password is used here only, never stored.
- name: forge-setup
image: ghcr.io/yundera/ipcr-forge-bridge:0.4.0
image: ghcr.io/yundera/ipcr-forge:0.4.0
user: $PUID:$PGID
phase: post_up
network: $APP_NET
volumes:
- /DATA/AppData/$AppID/bridge/secrets:/secrets
- /DATA/AppData/$AppID/forge/secrets:/secrets
command:
- sh
- -c
- GITEA_PASSWORD="$APP_DEFAULT_PASSWORD" GITEA_URL=http://gitea:3000 IPCR_ROOT_ORG="$IPCR_ROOT_ORG" PUBLIC_HOSTS="ipcr-forge-$APP_DOMAIN,ipcr-forge-$APP_PUBLIC_IP_DASH.nip.io,ipcr-forge-$APP_PUBLIC_IP_DASH.sslip.io" ipcr-forge-bridge setup
- GITEA_PASSWORD="$APP_DEFAULT_PASSWORD" GITEA_URL=http://gitea:3000 IPCR_ROOT_ORG="$IPCR_ROOT_ORG" PUBLIC_HOSTS="ipcr-forge-$APP_DOMAIN,ipcr-forge-$APP_PUBLIC_IP_DASH.nip.io,ipcr-forge-$APP_PUBLIC_IP_DASH.sslip.io" ipcr-forge setup
when: always
webui-host: gitea-${domain}
webui-path: /