IPCR Forge: fixes from the holyhorse run
- forge-runner: 1G (act_runner was OOM-killed mid-job at 256M) - bridge: log rad's stdout on failure; page links Gitea from its own host (html_url is the internal address); description fallback without it - docs/forge.md: holyhorse verification and findings 11-14
This commit is contained in:
@@ -200,7 +200,9 @@ services:
|
|||||||
deploy:
|
deploy:
|
||||||
resources:
|
resources:
|
||||||
limits:
|
limits:
|
||||||
memory: 256M
|
# act_runner holds a job's checkout and actions in memory while it copies them into the
|
||||||
|
# job container: 256M was OOM-killed mid-job on the first build (holyhorse, 2026-10-04).
|
||||||
|
memory: 1G
|
||||||
environment:
|
environment:
|
||||||
TZ: $TZ
|
TZ: $TZ
|
||||||
HOME: /data
|
HOME: /data
|
||||||
|
|||||||
+24
-7
@@ -148,11 +148,20 @@ New in v3 (tested locally with Gitea 1.27.3 and rad 1.10.1, 2026-10-04):
|
|||||||
10. **`rad sync` blocks for its timeout when the node has no peers.** The bridge pushes with
|
10. **`rad sync` blocks for its timeout when the node has no peers.** The bridge pushes with
|
||||||
`-o no-sync` and announces separately with a 30-second bound, so a push is never held up by the
|
`-o no-sync` and announces separately with a 30-second bound, so a push is never held up by the
|
||||||
network.
|
network.
|
||||||
|
11. **`act_runner` needs about 1 GB.** It holds a job's checkout and actions in memory while it
|
||||||
|
copies them into the job container. At 256 MB it was OOM-killed mid-job. The job container
|
||||||
|
then ran on, orphaned, and Gitea showed the run as "running" until it timed out.
|
||||||
|
12. **The first `rad init` can fail if the node is still starting.** The bridge's retry adopts the
|
||||||
|
RID that `rad init` already wrote into the mirror's `rad` remote, so no second repository is
|
||||||
|
created (seen on holyhorse). `rad` prints its errors on stdout, which the bridge now logs.
|
||||||
|
13. **Gitea's API returns `html_url` built from the internal address** (`http://gitea:3000/…`) when
|
||||||
|
called through it. The page builds Gitea links from its own host name instead.
|
||||||
|
14. **Right after `up`, new domains answer with the gateway's SSO redirect** until Caddy picks up the
|
||||||
|
labels (about a minute). The runner's registration retries through that window.
|
||||||
|
|
||||||
## Verified
|
## Verified
|
||||||
|
|
||||||
Local integration (Gitea 1.27.3-rootless, `radicle-seed-node` 1.10.1, the bridge image, one Docker
|
**Locally** (Gitea 1.27.3-rootless, `radicle-seed-node` 1.10.1, the bridge image, one Docker network):
|
||||||
network):
|
|
||||||
|
|
||||||
- `setup` run twice leaves exactly one system hook.
|
- `setup` run twice leaves exactly one system hook.
|
||||||
- `ipcr-hello` was created and mirrored with the crefs rule in its identity.
|
- `ipcr-hello` was created and mirrored with the crefs rule in its identity.
|
||||||
@@ -160,14 +169,22 @@ network):
|
|||||||
- A private repository never appeared.
|
- A private repository never appeared.
|
||||||
- A public repository made private was frozen.
|
- A public repository made private was frozen.
|
||||||
|
|
||||||
The full chain on a PCS (runner → staging → IPCR → pull from a second server) is in
|
**On holyhorse** (a test PCS, hand install that mimics Maison, 2026-10-04):
|
||||||
[Open items](#open-items) until it is run on a test box.
|
|
||||||
|
| Step | Result |
|
||||||
|
| --- | --- |
|
||||||
|
| install steps: Gitea admin, runner token, `forge-setup` | token, system hook, `ipcr-hello` created; runner registered |
|
||||||
|
| `ipcr-hello` mirrored to Radicle | `rad:z4Nc3jTG2q2rfQbio2gfuA7tWouee`, crefs rule in place |
|
||||||
|
| tag `v1.0.1` created through Gitea's API (as the web UI does) | Actions run started on `push`; tag canonical on Radicle in seconds |
|
||||||
|
| build (job image pulled cold) | success after about 4 min; `localhost:5000/gitea_admin/ipcr-hello:1.0.1` and `latest` |
|
||||||
|
| IPCR import | about a minute later: `/ipns/k51qzi5uqu5dg7urd3olssfzho23ze73n0jd9rwlqkivjlz3gspzyajk8cqfmd/gitea_admin/ipcr-hello:1.0.1` |
|
||||||
|
| `docker pull` of that name, then `docker run` | page served (`Hello from IPFS`) |
|
||||||
|
| forge page `ipcr-forge-holyhorse.nsl.sh` | 200; `/images.json` and `/repos.json` filled |
|
||||||
|
|
||||||
## Open items
|
## Open items
|
||||||
|
|
||||||
- **Run the full chain on a test PCS.** Check that the runner registers, that jobs see the daemon
|
- **Pull from a second server.** Verified on holyhorse itself only; v2's images pulled across
|
||||||
(`docker_host: ""` mounts its socket into each job), that a `v*` tag on `ipcr-hello` builds and
|
servers the same way.
|
||||||
imports, and that the image pulls from a second server.
|
|
||||||
- **Publish the images**: `ghcr.io/yundera/ipcr:1.2.0` and `ghcr.io/yundera/ipcr-forge-bridge:0.1.0`
|
- **Publish the images**: `ghcr.io/yundera/ipcr:1.2.0` and `ghcr.io/yundera/ipcr-forge-bridge:0.1.0`
|
||||||
(workflows `image.yml` and `bridge.yml`, tag `bridge-v0.1.0`).
|
(workflows `image.yml` and `bridge.yml`, tag `bridge-v0.1.0`).
|
||||||
- **Per-repository image names:** the bridge as a registry token server (see Trust).
|
- **Per-repository image names:** the bridge as a registry token server (see Trust).
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ type repo struct {
|
|||||||
Mirror bool `json:"mirror"`
|
Mirror bool `json:"mirror"`
|
||||||
Empty bool `json:"empty"`
|
Empty bool `json:"empty"`
|
||||||
DefaultBranch string `json:"default_branch"`
|
DefaultBranch string `json:"default_branch"`
|
||||||
HTMLURL string `json:"html_url"`
|
|
||||||
Owner struct {
|
Owner struct {
|
||||||
Login string `json:"login"`
|
Login string `json:"login"`
|
||||||
// public | limited | private. Only a public owner's repositories are public.
|
// public | limited | private. Only a public owner's repositories are public.
|
||||||
|
|||||||
+1
-1
@@ -46,7 +46,7 @@ func (m *mirror) sync(id int64) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
cur := prev
|
cur := prev
|
||||||
cur.FullName, cur.HTMLURL = r.FullName, r.HTMLURL
|
cur.FullName = r.FullName
|
||||||
if r.Empty {
|
if r.Empty {
|
||||||
if cur.RID == "" {
|
if cur.RID == "" {
|
||||||
cur.State, cur.Error = stWaiting, ""
|
cur.State, cur.Error = stWaiting, ""
|
||||||
|
|||||||
+5
-1
@@ -43,6 +43,10 @@ func (m *mirror) run(timeout time.Duration, dir, name string, args ...string) (s
|
|||||||
cmd.Stdout, cmd.Stderr = &stdout, &stderr
|
cmd.Stdout, cmd.Stderr = &stdout, &stderr
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
msg := strings.TrimSpace(stderr.String())
|
msg := strings.TrimSpace(stderr.String())
|
||||||
|
if msg == "" {
|
||||||
|
// rad reports its errors on stdout.
|
||||||
|
msg = strings.TrimSpace(stdout.String())
|
||||||
|
}
|
||||||
if len(msg) > 500 {
|
if len(msg) > 500 {
|
||||||
msg = "…" + msg[len(msg)-500:]
|
msg = "…" + msg[len(msg)-500:]
|
||||||
}
|
}
|
||||||
@@ -66,7 +70,7 @@ func (m *mirror) rad(dir string, args ...string) (string, error) {
|
|||||||
func (m *mirror) radInit(dir string, r *repo) (string, error) {
|
func (m *mirror) radInit(dir string, r *repo) (string, error) {
|
||||||
desc := r.Description
|
desc := r.Description
|
||||||
if desc == "" {
|
if desc == "" {
|
||||||
desc = "Mirrored from " + r.HTMLURL
|
desc = "Mirrored from Gitea: " + r.FullName
|
||||||
}
|
}
|
||||||
out, err := m.rad(dir, "init", dir, "--name", r.Name, "--description", desc,
|
out, err := m.rad(dir, "init", dir, "--name", r.Name, "--description", desc,
|
||||||
"--default-branch", r.DefaultBranch, "--public", "--no-confirm")
|
"--default-branch", r.DefaultBranch, "--public", "--no-confirm")
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ const (
|
|||||||
type repoState struct {
|
type repoState struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
FullName string `json:"full_name"`
|
FullName string `json:"full_name"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
|
||||||
RID string `json:"rid,omitempty"`
|
RID string `json:"rid,omitempty"`
|
||||||
DefaultBranch string `json:"default_branch,omitempty"`
|
DefaultBranch string `json:"default_branch,omitempty"`
|
||||||
State string `json:"state"`
|
State string `json:"state"`
|
||||||
|
|||||||
@@ -347,7 +347,8 @@ for IPFS peers. Details: <a href="https://github.com/Yundera/ipcr" target="_blan
|
|||||||
repos.forEach(function (r) {
|
repos.forEach(function (r) {
|
||||||
var tr = el("tr");
|
var tr = el("tr");
|
||||||
var td1 = el("td");
|
var td1 = el("td");
|
||||||
td1.appendChild(r.html_url ? link(r.html_url, r.full_name) : el("span", r.full_name));
|
// Not r.html_url: the bridge talks to Gitea's internal address, which is what Gitea puts there.
|
||||||
|
td1.appendChild(base ? link(url("gitea") + "/" + r.full_name, r.full_name) : el("span", r.full_name));
|
||||||
var td2 = el("td");
|
var td2 = el("td");
|
||||||
if (r.rid) {
|
if (r.rid) {
|
||||||
td2.appendChild(base ? link(url("radicle") + "/nodes/radicle-api-" + base + "/" + r.rid, r.rid) : el("code", r.rid));
|
td2.appendChild(base ? link(url("radicle") + "/nodes/radicle-api-" + base + "/" + r.rid, r.rid) : el("code", r.rid));
|
||||||
|
|||||||
Reference in New Issue
Block a user