Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7efef1d4a | ||
|
|
36f713a809 | ||
|
|
bef30d8e20 | ||
|
|
17df335e3d | ||
|
|
b508bd267b |
@@ -0,0 +1,39 @@
|
|||||||
|
# IPCR Forge: builds the forge service's image (forge/Dockerfile) for every v* tag pushed to this repository on Gitea (gitea-yunderalabs.nsl.sh),
|
||||||
|
# pushed to the IPCR app on the same server; IPCR publishes it on IPFS within seconds, under its name
|
||||||
|
# (ipcrteam.eth): ipcr-yunderalabs.nsl.sh/ipns/ipcrteam.eth/<owner>/<repo>:<version>.
|
||||||
|
#
|
||||||
|
# runs-on: ubuntu-latest the Gitea app's own runner (Docker-in-Docker)
|
||||||
|
# ipcr-push-yunderalabs… IPCR's push endpoint; login <owner> with IPCR_PUSH_TOKEN, a namespace
|
||||||
|
# token from the IPCR dashboard (Push tokens), set as an Actions secret
|
||||||
|
# on the organisation
|
||||||
|
name: ipcr
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ['v*']
|
||||||
|
env:
|
||||||
|
IPCR_PUSH: ipcr-push-yunderalabs.nsl.sh
|
||||||
|
jobs:
|
||||||
|
image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.IPCR_PUSH }}
|
||||||
|
username: ${{ github.repository_owner }}
|
||||||
|
password: ${{ secrets.IPCR_PUSH_TOKEN }}
|
||||||
|
- uses: docker/metadata-action@v5
|
||||||
|
id: meta
|
||||||
|
with:
|
||||||
|
images: ${{ env.IPCR_PUSH }}/${{ github.repository }}
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=raw,value=latest
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: forge
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@@ -15,12 +15,10 @@ registry gateway, IPFS import, IPNS naming and admin API), used here as the imag
|
|||||||
`ghcr.io/yundera/ipcr`.
|
`ghcr.io/yundera/ipcr`.
|
||||||
|
|
||||||
- **[docs/forge.md](docs/forge.md)**: how it works, the trust model, findings, what was verified.
|
- **[docs/forge.md](docs/forge.md)**: how it works, the trust model, findings, what was verified.
|
||||||
- **[Apps/IPCR-Forge](https://github.com/Yundera/ipcr/tree/main/Apps/IPCR-Forge)** and
|
- **[Apps/IPCR-Forge-AIO](https://github.com/Yundera/ipcr/tree/main/Apps/IPCR-Forge-AIO)** (in the IPCR repository,
|
||||||
**[Apps/IPCR-Forge-AIO](https://github.com/Yundera/ipcr/tree/main/Apps/IPCR-Forge-AIO)** (in the IPCR repository,
|
with the rest of the IPCR app store): the Yundera AppStore listing (compose, rationale, seed). It
|
||||||
with the rest of the IPCR app store): the Yundera AppStore listings (compose, rationale, seed). The
|
bundles Gitea, Radicle and IPCR, and pins this repository's image and the engine's. See
|
||||||
first uses the store's Gitea app (and its Radicle app, if installed); the second bundles both.
|
[docs/forge.md](docs/forge.md#one-listing-all-in-one).
|
||||||
Both pin this repository's image and the engine's. See
|
|
||||||
[docs/forge.md](docs/forge.md#two-listings-split-and-all-in-one).
|
|
||||||
- **[forge/](forge/)**: the forge service, `ghcr.io/yundera/ipcr-forge` (Go, standard library only):
|
- **[forge/](forge/)**: the forge service, `ghcr.io/yundera/ipcr-forge` (Go, standard library only):
|
||||||
the Gitea → Radicle mirror, the forge's page and admin pages (Gitea login), the staging registry's
|
the Gitea → Radicle mirror, the forge's page and admin pages (Gitea login), the staging registry's
|
||||||
gate and per-repository push credentials, and the install step.
|
gate and per-repository push credentials, and the install step.
|
||||||
@@ -36,5 +34,5 @@ docker build -t ghcr.io/yundera/ipcr-forge:dev forge
|
|||||||
|
|
||||||
Releases: a `vX.Y.Z` tag publishes `ghcr.io/yundera/ipcr-forge:X.Y.Z`
|
Releases: a `vX.Y.Z` tag publishes `ghcr.io/yundera/ipcr-forge:X.Y.Z`
|
||||||
([.github/workflows/image.yml](.github/workflows/image.yml)). Then bump the version in the
|
([.github/workflows/image.yml](.github/workflows/image.yml)). Then bump the version in the
|
||||||
listings, `Apps/IPCR-Forge/docker-compose.yml` and `Apps/IPCR-Forge-AIO/docker-compose.yml` in
|
listing, `Apps/IPCR-Forge-AIO/docker-compose.yml` in the IPCR repository (the service, `forge-gate`
|
||||||
the IPCR repository (the service, `forge-gate` and the `forge-setup` step, in each).
|
and the `forge-setup` step).
|
||||||
|
|||||||
+17
-35
@@ -6,8 +6,7 @@ naming, the admin API). The engine knows nothing about Gitea, Radicle or this fo
|
|||||||
the engine's image (`ghcr.io/yundera/ipcr`) and the contract listed in its
|
the engine's image (`ghcr.io/yundera/ipcr`) and the contract listed in its
|
||||||
[design notes](https://github.com/Yundera/ipcr/blob/main/docs/design.md#contract-with-add-ons).
|
[design notes](https://github.com/Yundera/ipcr/blob/main/docs/design.md#contract-with-add-ons).
|
||||||
|
|
||||||
How the forge (store listings: [Apps/IPCR-Forge](https://github.com/Yundera/ipcr/tree/main/Apps/IPCR-Forge)
|
How the forge (store listing: [Apps/IPCR-Forge-AIO](https://github.com/Yundera/ipcr/tree/main/Apps/IPCR-Forge-AIO), in the IPCR repository)
|
||||||
and [Apps/IPCR-Forge-AIO](https://github.com/Yundera/ipcr/tree/main/Apps/IPCR-Forge-AIO), in the IPCR repository)
|
|
||||||
goes from `git push` to `docker pull`, why it is shaped this way, and what was learned building it.
|
goes from `git push` to `docker pull`, why it is shaped this way, and what was learned building it.
|
||||||
The listings' own security argument is in
|
The listings' own security argument is in
|
||||||
[rationale.md](https://github.com/Yundera/ipcr/blob/main/Apps/IPCR-Forge-AIO/rationale.md).
|
[rationale.md](https://github.com/Yundera/ipcr/blob/main/Apps/IPCR-Forge-AIO/rationale.md).
|
||||||
@@ -62,39 +61,22 @@ anyone: docker pull ipcr.localhost:4767/ipns/<forge k51… | example.eth>/<owner
|
|||||||
The two legs do not depend on each other: a repository can be built without being mirrored
|
The two legs do not depend on each other: a repository can be built without being mirrored
|
||||||
(private repositories), and mirrored without being built (no workflow).
|
(private repositories), and mirrored without being built (no workflow).
|
||||||
|
|
||||||
## Two listings: split and all in one
|
## One listing: all in one
|
||||||
|
|
||||||
The same image runs in both; only the compose differs.
|
The forge ships as one listing, **IPCR-Forge-AIO**: Gitea, Radicle and IPCR bundled, with the forge's
|
||||||
|
own runner (labels `ipcr` and `ubuntu-latest`/`-24.04`/`-22.04`). It cannot sit beside the store's
|
||||||
|
Gitea, Radicle or IPCR apps (same container names, domains, ports).
|
||||||
|
|
||||||
| | **IPCR-Forge** (split) | **IPCR-Forge-AIO** (all in one) |
|
A split listing (`Apps/IPCR-Forge`, the forge on top of the store's Gitea app and, optionally, its
|
||||||
| --- | --- | --- |
|
Radicle app) existed until IPCR 1.7.1. It was retired: beside a stock Gitea, what is wanted is a
|
||||||
| Gitea | the store's **Gitea** app, required, reached at `gitea:3000` on `pcs` | bundled |
|
registry, not a forge, and the IPCR app is now that registry, with its own push tokens, dashboard and
|
||||||
| Radicle | the store's **Radicle** app, optional, its home mounted from `/DATA/AppData/radicle/home` | bundled |
|
public pulls, and no Gitea or Radicle knowledge
|
||||||
| Runner labels | `ipcr` only | `ipcr` and `ubuntu-latest`/`-24.04`/`-22.04` |
|
([push.md](https://github.com/Yundera/ipcr/blob/main/docs/push.md)). What it cost, for the record: the
|
||||||
| Runner registration token | the setup step, through Gitea's API (`RUNNER_DIR`) | an init step on Gitea's volume |
|
Gitea admin password at install, a webhook allowance in the Gitea listing, a read-write mount of the
|
||||||
| Beside the Gitea/Radicle apps | yes, that is the point | no: same container names, domains, ports |
|
Radicle app's home, and `rad` versions kept in step across two listings. Its code paths
|
||||||
|
(`RUNNER_DIR`, the missing-Gitea and missing-Radicle states) are still here, unused by the AIO
|
||||||
|
listing, until a cleanup.
|
||||||
|
|
||||||
Neither sits beside the IPCR app (the engine is bundled in both: its configuration is the forge's).
|
|
||||||
|
|
||||||
What the split one relies on, and how each part fails when missing:
|
|
||||||
|
|
||||||
- **Gitea**: the forge service calls `http://gitea:3000` and Gitea calls the forge's webhook at
|
|
||||||
`http://ipcr-forge:8081`, which the Gitea listing allows (`webhook.ALLOWED_HOST_LIST:
|
|
||||||
external,ipcr-forge`). The setup step signs in once as `FORGE_GITEA_ADMIN` / `FORGE_GITEA_PASSWORD`
|
|
||||||
(default `gitea_admin` and the server's default password). Gitea mints tokens only for a password,
|
|
||||||
never for another token, so these are what to change if the password was changed. Setup leaves
|
|
||||||
`secrets/setup-status` (`ok`, `unreachable`, `bad-credentials`), and the page shows a banner
|
|
||||||
for the last two. Setup exits cleanly in either case, so the install never hangs on a missing Gitea.
|
|
||||||
- **The runner**: the Gitea app has its own runner, `gitea-runner`, with the usual labels. Its jobs
|
|
||||||
run in its own daemon, where `localhost:5000` is nothing. So the forge's runner takes `ipcr` only,
|
|
||||||
and publishing workflows say `runs-on: ipcr` (the example does; it works in both listings).
|
|
||||||
- **Radicle**: the forge service needs the node's home itself (its key, its storage, its control
|
|
||||||
socket), not an API: Radicle's HTTP API is read-only. A repository is mirrored only when
|
|
||||||
`keys/radicle` and `node/control.sock` are there; otherwise it shows *waiting* and the page hides
|
|
||||||
the Radicle parts. The reconcile loop looks again every pass, so installing Radicle later starts
|
|
||||||
the mirror. `RADICLE=off` turns it off for good. The split listing creates
|
|
||||||
`/DATA/AppData/radicle/home` itself, owned by `$PUID`: left to Docker it would be created as
|
|
||||||
root, and the Radicle app's `rad auth` (as `$PUID`) could not write its key there later.
|
|
||||||
- **Versions**: the forge image carries the `rad` CLI at the version the Radicle listing pins
|
- **Versions**: the forge image carries the `rad` CLI at the version the Radicle listing pins
|
||||||
(`RADICLE_VERSION` in [forge/Dockerfile](../forge/Dockerfile)). Bump both together.
|
(`RADICLE_VERSION` in [forge/Dockerfile](../forge/Dockerfile)). Bump both together.
|
||||||
|
|
||||||
@@ -287,7 +269,7 @@ A repository opts in by committing a workflow under `.gitea/workflows/`. The tem
|
|||||||
registry, the only one its token may push. `docker/metadata-action` lowercases it.
|
registry, the only one its token may push. `docker/metadata-action` lowercases it.
|
||||||
- `context: .` and `driver-opts: network=host`: see findings 2 and 3.
|
- `context: .` and `driver-opts: network=host`: see findings 2 and 3.
|
||||||
- `runs-on: ipcr`: the forge's own runner, the only one whose jobs reach the staging registry (see
|
- `runs-on: ipcr`: the forge's own runner, the only one whose jobs reach the staging registry (see
|
||||||
[Two listings](#two-listings-split-and-all-in-one)).
|
[One listing](#one-listing-all-in-one)).
|
||||||
- The image is published as `/ipns/<forge>/<owner>/<repo>:<tag>`.
|
- The image is published as `/ipns/<forge>/<owner>/<repo>:<tag>`.
|
||||||
|
|
||||||
## Findings
|
## Findings
|
||||||
@@ -448,5 +430,5 @@ New in v3 (tested locally with Gitea 1.27.3 and rad 1.10.1, 2026-10-04):
|
|||||||
- **act_runner** is pinned at 0.6.1, proven with Gitea 1.27.3. Upstream has moved on.
|
- **act_runner** is pinned at 0.6.1, proven with Gitea 1.27.3. Upstream has moved on.
|
||||||
- **Radicle → Gitea:** patches opened on Radicle could become pull requests. Not built.
|
- **Radicle → Gitea:** patches opened on Radicle could become pull requests. Not built.
|
||||||
- **Store install:** test through the store, not only as a hand install.
|
- **Store install:** test through the store, not only as a hand install.
|
||||||
- **Split listing:** the store has no dependencies between apps; removing the Gitea app leaves the
|
- **Split-listing code:** `RUNNER_DIR` and the missing-Gitea / missing-Radicle states served only the
|
||||||
forge reporting `unreachable` on its page, nothing more.
|
retired split listing; remove them in a cleanup.
|
||||||
|
|||||||
@@ -332,7 +332,7 @@
|
|||||||
st8.textContent = "";
|
st8.textContent = "";
|
||||||
publisher = s.key.id || "";
|
publisher = s.key.id || "";
|
||||||
$("key-name").textContent = s.key.name || "—";
|
$("key-name").textContent = s.key.name || "—";
|
||||||
$("key-id").textContent = publisher || "not created yet (the first import creates it)";
|
$("key-id").textContent = publisher || "not created yet (IPCR creates it when it starts: reload in a minute)";
|
||||||
$("ens-value").textContent = "ipns://" + (publisher || "k51…");
|
$("ens-value").textContent = "ipns://" + (publisher || "k51…");
|
||||||
$("dns-value").textContent = "dnslink=/ipns/" + (publisher || "k51…");
|
$("dns-value").textContent = "dnslink=/ipns/" + (publisher || "k51…");
|
||||||
pubName = s.name && s.name.ok ? s.name.name : "";
|
pubName = s.name && s.name.ok ? s.name.name : "";
|
||||||
|
|||||||
Reference in New Issue
Block a user