ci-flows
Purpose: the workflow artefacts that exercise the platform, plus the guardrail that stops devs picking a label no runner serves.
Holds (flows/ + scripts/):
- flows/hello.yml — smoke workflow (runs-on: con-ci); proves poll + run end-to-end. Copy into a
repo's .forgejo/workflows/.
- flows/README.md — outlines the Nextcloud sign+push and OCI build+push flows.
- scripts/check-forgejo-runs-on.sh — the label guardrail (also exported as the
forgejo-runs-on pre-commit hook via .pre-commit-hooks.yaml).
The guardrail rules:
- runs-on must be con-ci or con-ci-oci (generic docker/ubuntu-latest are not served →
jobs would sit queued forever).
- A job with container: or services: MUST use con-ci-oci (the host backend can't run images).
Heuristic for humans (revised 2026-07-04): plain host job (scripts, lint, tooling, the sign
step) → con-ci; ANYTHING involving container images — running them (container:, Hydra) AND
building them — → con-ci-oci. Image builds were originally intended for con-ci (host
buildah), but the stock runner image ships no buildah and host jobs are non-root without a
package manager; meanwhile con-ci-oci proved podman build works rootless. Builds live there.
Use it:
- Manually: scripts/check-forgejo-runs-on.sh path/to/wf.yml (needs yq).
- As a pre-commit hook in a consuming repo:
- repo: https://codeberg.org/Conduction/talos
rev: <tag-or-sha>
hooks: [{ id: forgejo-runs-on }]
Status (2026-07-04): smoke proven on con-ci; container: job proven end-to-end on
con-ci-oci (test-con-ci-oci green: claim → pull via own egress-proxy → run inside image).
test-con-ci.yml now tests what con-ci is for (host shell + proxy enforcement) instead of a
buildah build that the class can't and shouldn't do. OCI build+push and Hydra flows validate on
con-ci-oci; Nextcloud sign stays a con-ci concern (see below). A CI-side gate (running the
guardrail on con-ci) needs yq in the job — add when convenient.
Image builds — the kaniko pattern (bridge until BuildKit lands)
How a repo builds + pushes a container image on this platform today. kaniko builds fully
unprivileged (no daemon, no userns/seccomp relaxations — a plain container: job), which is
why it works on con-ci-oci with zero extra platform surface. It is a bridge, not an end
state: upstream is archived (2025-06-03, no security patches) — accepted with review date
2026-10-01 in docs/risk-acceptance-ci-on-prod.md, tied to the BuildKit enablement
(docs/components/runner-oci.md, job-container seccomp section).
The four things every image-build workflow needs:
- Builder image — pin the self-hosted mirror by digest (never gcr.io, never a tag):
yaml runs-on: con-ci-oci container: image: codeberg.org/conduction/kaniko@sha256:c3109d5926a997b100c4343944e06c6b30a6804b2f9abe0994d3de6ef92b028eThat digest is the multi-arch index ofgcr.io/kaniko-project/executor:v1.23.2-debug, mirrored byte-identically. Keep the-debugvariant — Forgejo Actions container jobs need a shell in the image. - Registry push needs a token secret. The automatic Actions token (
GITHUB_TOKEN/FORGEJO_TOKEN) has no package write on Codeberg (Forgejo v15 platform fact, not an org setting). Create an access token with scopewrite:packageonly, store it as a repo or org Actions secret (e.g.REGISTRY_TOKEN), and authenticate kaniko's push with it. Pulling the builder image needs no credentials (public org package). - Proxy build-args for network access inside
RUNsteps. The runner injectsHTTPS_PROXYetc. into the job container — kaniko's own base-image pulls and the final push honor those automatically. ButRUNsteps (npm/pip/apt) execute with the image-under-construction's env, so pass the proxy explicitly:--build-arg HTTP_PROXY=http://egress-proxy.con-ci-oci.svc.cluster.local:3128 \ --build-arg HTTPS_PROXY=http://egress-proxy.con-ci-oci.svc.cluster.local:3128 \ --build-arg NO_PROXY=.svc.cluster.local,.cluster.local,localhost,127.0.0.1(Predefined proxy build-args — noARGdeclarations needed in the Dockerfile.) - Egress allowlist covers the build's hosts. Allowed today: codeberg.org (clone, builder
pull, image push), Docker Hub (base images),
registry.npmjs.org,pypi.org+files.pythonhosted.org. Anything else 403s at the proxy — additions are a one-line reviewed change tomanifests/overlays/con-ci-oci/squid.conf.
Mirror maintenance (one-time done 2026-07-07; repeat only for a new version): an org
member with a write:package token runs
skopeo copy --all --preserve-digests docker://gcr.io/kaniko-project/executor@sha256:<digest> docker://codeberg.org/conduction/kaniko:<tag>
(--all keeps the multi-arch index, --preserve-digests fails rather than rewrite bytes),
then verifies with an anonymous skopeo inspect of the digest. Revoke ad-hoc tokens after
use. Note: last upstream release is v1.24.0 (2025-05-23); gcr.io availability for an archived
project is not guaranteed, so mirror before you need it.
Nextcloud sign+push — class decision + the crown-jewel guardrail
Design intent: runs-on: con-ci (plain/host class) — see flows/README.md. The signing key
(nextcloud-signing-key, crown jewel) is mounted at runtime into the con-ci signing job, from
the cluster-side SOPS Secret; it must NEVER enter the con-ci-oci build class, and
apps.nextcloud.com is therefore deliberately absent from the con-ci-oci egress allowlist
(it lives on con-ci only).
Unresolved tension for whoever authors the actual flow (it's still a template — task 7.2 open):
- occ integrity:sign-app needs a Nextcloud/PHP environment. The stock con-ci runner image is
minimal (no occ, and no buildah either — see troubleshooting.md). In practice that pushes
toward a container: job → which the guardrail forces onto con-ci-oci.
- If the flow ends up needing a container, do NOT "fix" it by adding apps.nextcloud.com +
mounting the signing key in con-ci-oci. That drags the crown jewel into the build class and
breaks the isolation the two-namespace split exists to protect.
The correct pattern if it becomes a container job — build/sign split:
- con-ci-oci builds the app artifact only. No signing key, no apps.nextcloud.com. It hands
the unsigned artifact off (Codeberg registry, or a shared artifact location).
- A separate sign+push step in the namespace that holds the key — con-ci (host, with the
nextcloud-signing-key mount + apps.nextcloud.com egress), or a dedicated con-ci-sign
namespace — runs occ integrity:sign-app and pushes to apps.nextcloud.com.
- The key stays exclusively in the sign namespace; the build class never sees it. Two runners,
two allowlists, key co-located only with the sign step.
Until the flow is actually authored, the design stands: Nextcloud = con-ci, and the con-ci-oci
allowlist stays as delivered (no apps.nextcloud.com).