Split listing support: Gitea and Radicle as separate apps
image / vet (push) Successful in 1m51s
image / image (push) Failing after 29s

- setup: with RUNNER_DIR, fetch the runner registration token from Gitea's
  admin API (POST /admin/actions/runners/registration-token) instead of the
  gitea CLI on Gitea's volume; record secrets/setup-status (ok, unreachable,
  bad-credentials) and exit cleanly when Gitea cannot be used.
- mirror: Radicle is optional. Repositories wait until a node is there (key and
  control socket in RAD_HOME), re-checked every reconcile; RADICLE=off.
- page: /status.json; hide the Radicle parts without a node, banner when Gitea
  is unreachable or refuses the login.
- example workflow: runs-on: ipcr, the forge's own runner label.
- docs: two listings, split (IPCR-Forge) and all in one (IPCR-Forge-AIO).
This commit is contained in:
Worph
2026-10-05 16:11:24 +00:00
parent eb80042f13
commit ca8f87050b
10 changed files with 317 additions and 27 deletions
+27
View File
@@ -21,6 +21,8 @@ type mirror struct {
state *state
repoDir string // one bare repository per Gitea repository: <id>.git
radHome string
// RADICLE=off: never mirror, whatever is mounted.
radicleOff bool
// Called after each reconcile pass (the admin pages keep IPCR's allowlist in step).
onReconcile func()
}
@@ -56,6 +58,15 @@ func (m *mirror) sync(id int64) {
}
return
}
if !m.radicleReady() {
// No node (the split listing without the Radicle app, or the node not started yet). Nothing
// is fetched; the reconcile loop looks again, so installing Radicle later starts the mirror.
if cur.State != stWaiting || cur.Error != errNoRadicle {
cur.State, cur.Error = stWaiting, errNoRadicle
m.save(cur)
}
return
}
if err := m.publish(r, &cur); err != nil {
cur.State, cur.Error = stError, err.Error()
log.Printf("sync %s: %v", r.FullName, err)
@@ -65,6 +76,22 @@ func (m *mirror) sync(id int64) {
m.save(cur)
}
const errNoRadicle = "no Radicle node: mirrored once the Radicle app is installed and running"
// radicleReady reports whether a Radicle node is there to mirror into: its key (rad auth ran) and
// its control socket (the node runs) in RAD_HOME.
func (m *mirror) radicleReady() bool {
if m.radicleOff {
return false
}
for _, p := range []string{"keys/radicle", "node/control.sock"} {
if _, err := os.Stat(filepath.Join(m.radHome, p)); err != nil {
return false
}
}
return true
}
func (m *mirror) save(r repoState) {
if err := m.state.put(r); err != nil {
log.Printf("state: %v", err)