diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..a3857fc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,65 @@ +# railiance-apps — Codex Instructions + +**OAS Stack Level:** S5 Workloads & Experience Endpoints +**Scope:** Application Helm releases and Kubernetes manifests for +user-facing services — Gitea, coulomb services, APIs, web frontends. + +**Pre-condition:** The full stack below must be operational: +`railiance-infra` → `railiance-cluster` → `railiance-platform` → `railiance-enablement` + +## Custodian State Hub Integration + +Domain: **railiance** — topic ID: `ca369340-a64e-442e-98f1-a4fa7dc74a38` +State Hub: http://127.0.0.1:8000 + +### Session Protocol + +**Step 1 — Orient** +``` +get_domain_summary("railiance") +``` + +**Step 2 — Scan workplans** +``` +ls workplans/ # read all active workplans; note todo/in_progress tasks +``` + +**Step 3 — Present brief** +1. Active workstreams for railiance with `[repo:railiance-apps]` tasks +2. Pending tasks from local workplans +3. Goal guidance from summary (needs_workplan / alignment_warnings) +4. Suggested next action + +**During work:** use `record_decision()`, `add_progress_event()`, `resolve_decision()`. + +**Session close:** `add_progress_event()` with topic_id and workstream_id. + +> Design boundary: hub is read model. Bootstrap tools are First Session +> Protocol only. Work originates as files per ADR-001. + +### Repo Boundary Rule (ADR-003) + +This repo owns **S5 Workloads & Experience Endpoints only**. Do not manage: +- OS-level concerns → `railiance-infra` (S1) +- Kubernetes runtime → `railiance-cluster` (S2) +- Platform services → `railiance-platform` (S3) +- Developer tooling → `railiance-enablement` (S4) + +Reference: `railiance-infra/docs/adr/ADR-003-railiance-5repo-stack-architecture.md` + +### Workplan Convention (ADR-001) + +File location: `workplans/RAIL-AP-WP-NNNN-.md` +Prefix: `RAIL-AP` + +### SBOM + +After updating dependencies: +```bash +cd ~/the-custodian/state-hub +make ingest-sbom REPO=railiance-apps SCAN=1 REPO_PATH=/home/worsch/railiance-apps +``` + +### Quick Reference + +`~/the-custodian/state-hub/mcp_server/TOOLS.md` diff --git a/Makefile b/Makefile index d218039..935b9c6 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,26 @@ SHELL := /usr/bin/env bash .DEFAULT_GOAL := help +GITEA_RELEASE ?= gitea +GITEA_NAMESPACE ?= default +GITEA_CHART ?= gitea-charts/gitea +GITEA_VALUES ?= helm/gitea-values.sops.yaml +GITEA_INGRESS ?= manifests/gitea-ingress.yaml + ##@ Gitea gitea-deploy: ## Deploy / upgrade Gitea (S5 workload) - helm upgrade --install gitea gitea-charts/gitea \ - -f <(sops -d helm/gitea-values.sops.yaml) \ - --namespace gitea --create-namespace + helm upgrade --install $(GITEA_RELEASE) $(GITEA_CHART) \ + -f <(sops -d $(GITEA_VALUES)) \ + --namespace $(GITEA_NAMESPACE) --create-namespace + +gitea-ingress-deploy: ## Apply the Gitea OCI registry ingress + kubectl apply -f $(GITEA_INGRESS) gitea-status: ## Check Gitea health - kubectl get pods -n gitea + kubectl get pods -n $(GITEA_NAMESPACE) -l app.kubernetes.io/instance=$(GITEA_RELEASE) + kubectl get svc -n $(GITEA_NAMESPACE) $(GITEA_RELEASE) + kubectl get ingress -n $(GITEA_NAMESPACE) $(GITEA_RELEASE) --ignore-not-found kubectl cnpg status gitea-db -n databases ##@ Help @@ -19,4 +30,4 @@ help: ## Show this help /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } \ /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST) -.PHONY: gitea-deploy gitea-status help +.PHONY: gitea-deploy gitea-ingress-deploy gitea-status help diff --git a/docs/gitea-container-registry.md b/docs/gitea-container-registry.md new file mode 100644 index 0000000..72aaf7e --- /dev/null +++ b/docs/gitea-container-registry.md @@ -0,0 +1,50 @@ +# Gitea Container Registry + +## Registry Target + +Use `gitea.coulomb.social` as the approved registry host. The `/v2` ingress is +live as of 2026-05-15 and returns the OCI registry authentication challenge over +HTTPS. + +The encrypted Helm values still need an explicit package-registry stanza once +the SOPS age identity is available in the operator session. + +Image names should use the Gitea owner and package path: + +```bash +gitea.coulomb.social/coulomb/state-hub: +``` + +The State Hub handoff from `CUST-WP-0011` should publish the locally verified +`state-hub:local` image under that name. + +## Operator Smoke Test + +Use a Gitea personal access token with package read/write permission: + +```bash +docker login gitea.coulomb.social +docker tag state-hub:local gitea.coulomb.social/coulomb/state-hub: +docker push gitea.coulomb.social/coulomb/state-hub: +docker pull gitea.coulomb.social/coulomb/state-hub: +``` + +For private packages, create an image pull secret in each consuming namespace: + +```bash +kubectl create secret docker-registry gitea-registry \ + --docker-server=gitea.coulomb.social \ + --docker-username= \ + --docker-password= \ + --namespace= +``` + +Reference it from workloads as `imagePullSecrets: [{name: gitea-registry}]`. + +## Current Storage Notes + +The live Gitea pod mounts `gitea-shared-storage` at `/data`; package blobs are +expected to land on that existing PVC unless a separate package storage backend +is configured. The live cluster did not show Kubernetes `CronJob` backups for +the namespace during the 2026-05-15 inventory, so package backup coverage needs +operator confirmation before publishing many tags. diff --git a/manifests/gitea-ingress.yaml b/manifests/gitea-ingress.yaml new file mode 100644 index 0000000..ac76e63 --- /dev/null +++ b/manifests/gitea-ingress.yaml @@ -0,0 +1,29 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: gitea + namespace: default + labels: + app.kubernetes.io/name: gitea + app.kubernetes.io/instance: gitea + app.kubernetes.io/part-of: railiance-apps + railiance/component: gitea-registry + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod +spec: + ingressClassName: traefik + rules: + - host: gitea.coulomb.social + http: + paths: + - path: /v2 + pathType: Prefix + backend: + service: + name: gitea + port: + number: 3000 + tls: + - hosts: + - gitea.coulomb.social + secretName: gitea-tls diff --git a/workplans/RAIL-AP-WP-0001-gitea-container-registry.md b/workplans/RAIL-AP-WP-0001-gitea-container-registry.md new file mode 100644 index 0000000..31167dd --- /dev/null +++ b/workplans/RAIL-AP-WP-0001-gitea-container-registry.md @@ -0,0 +1,356 @@ +--- +id: RAIL-AP-WP-0001 +type: workplan +title: "Enable Gitea Container Registry for Cluster Image Publishing" +domain: railiance +repo: railiance-apps +status: active +owner: railiance +topic_slug: railiance +created: "2026-05-15" +updated: "2026-05-15" +planning_priority: high +planning_order: 1 +state_hub_workstream_id: "abd268e6-5af9-45ec-93e0-5ffca0211dd0" +--- + +# Enable Gitea Container Registry for Cluster Image Publishing + +## Goal + +Enable the existing Railiance-managed Gitea deployment to serve as an OCI +container registry so cluster workloads can publish and pull images through the +same source-forge boundary already used for Git hosting. + +The immediate forcing function is `CUST-WP-0011`: the State Hub image +`state-hub:local` builds and smoke-tests locally, but cannot be published until +Docker can authenticate against the Gitea registry `/v2/` endpoint. + +## Placement in the Railiance Tooling Set + +This workplan lives in `railiance-apps` because Gitea is an S5 application +workload. The active deployment surface is: + +- `helm/gitea-values.sops.yaml` — SOPS-encrypted Gitea Helm values. +- `Makefile` target `gitea-deploy` — applies the Gitea Helm release. +- `Makefile` target `gitea-status` — checks Gitea pod and database health. + +Lower-layer ownership remains unchanged: + +| Concern | Owner repo | Notes | +|---------|------------|-------| +| Gitea application config and Helm release | `railiance-apps` | This workplan | +| Gitea database and CNPG health | `railiance-platform` | Existing `gitea-db` | +| Ingress controller / cluster routing primitives | `railiance-cluster` | Only if `/v2/` requires lower-layer route changes | +| Forgejo replacement roadmap | `railiance-infra` | `RAIL-HO-WP-0005` remains the umbrella plan | + +This is not a replacement for the Forgejo production migration. It is a +pragmatic enablement step for current Gitea so images needed before Forgejo +cutover have a governed registry target. + +## Current Evidence + +- `railiance-apps/SCOPE.md` states that Gitea Helm values are owned here and + that Gitea is the active git hosting platform for Railiance and Custodian + repos. +- `railiance-apps/Makefile` deploys Gitea via `helm/gitea-values.sops.yaml` into + the `gitea` namespace. +- `helm/gitea-values.sops.yaml` currently contains Gitea app config sections + such as `server`, `database`, `cache`, `session`, and `queue`, but no visible + unencrypted package-registry section. +- `CUST-WP-0011` recorded Docker login/push receiving HTTP 404 from `/v2/`. + Runtime inspection found no `[packages]` section in the live Gitea `app.ini`. + +## Safety Contract + +- Do not commit decrypted Helm values or secrets. +- Take a Gitea backup or verify the most recent backup before changing the live + Helm release. +- Preserve Git hosting behavior: clone, push, login, and repository browsing + must remain healthy after any registry change. +- Do not broaden public exposure beyond the current Gitea exposure model. +- If package storage/backups require platform changes, pause and create or link + a `railiance-platform` task before changing S3 resources. +- If `/v2/` routing requires ingress-controller or NodePort changes, pause and + create or link a `railiance-cluster` task before changing S2 resources. + +## Target State + +- Gitea package registry is globally enabled. +- Gitea container package uploads are allowed with an explicit size policy. +- `/v2/` reaches Gitea and returns an OCI registry authentication challenge + rather than a generic 404. +- Docker can log in with a package-capable personal access token. +- The State Hub image can be tagged, pushed, pulled, and referenced by a future + cluster deployment. +- The chosen registry URL and image naming convention are documented for + downstream workplans. + +## Tasks + +### T01 — Inventory current registry and routing state + +```task +id: RAIL-AP-WP-0001-T01 +status: done +priority: high +state_hub_task_id: "30075930-6585-465d-9b8f-1c5f2304632d" +``` + +Confirm the live Gitea registry state before changing Helm values. + +Checks: + +- Confirm the active Helm release, chart version, namespace, and values source. +- Inspect live Gitea `app.ini` for `[packages]`, `[repository]`, `ROOT_URL`, and + any package or repository-unit settings. +- Check `GET /v2/` through each expected access path: service, NodePort or + tunnel, and any ingress hostname. +- Record the current response codes and headers for `/v2/`. +- Identify the registry hostname that Docker and Kubernetes should use. + +**Done when:** the workplan records whether the blocker is app config, route +config, TLS/trust, authentication, or a combination. + +--- + +### T02 — Enable Gitea package and container registry config + +```task +id: RAIL-AP-WP-0001-T02 +status: blocked +priority: high +state_hub_task_id: "e4136a4a-7730-47fe-bf64-315a513a3d8b" +``` + +Update `helm/gitea-values.sops.yaml` through `sops` so the generated Gitea +`app.ini` enables packages and permits container uploads. + +Expected app configuration: + +```ini +[packages] +ENABLED = true +LIMIT_SIZE_CONTAINER = -1 +``` + +Also verify repository package units are not globally disabled: + +```ini +[repository] +DISABLED_REPO_UNITS = +``` + +If the chart values require YAML nesting, express those settings under the +existing `gitea.config` tree without exposing decrypted secrets in Git. + +**Done when:** a dry-rendered or live-inspected `app.ini` includes the package +registry settings and no decrypted secret material was committed. + +--- + +### T03 — Ensure `/v2/` reaches the Gitea registry handler + +```task +id: RAIL-AP-WP-0001-T03 +status: done +priority: high +state_hub_task_id: "21c503be-12c7-411c-a82c-f738536cc114" +``` + +Make the OCI registry endpoint reachable at the root `/v2/` path for the chosen +registry host. + +Validation: + +- `curl -i https:///v2/` should return the expected registry auth + challenge or an auth-related response, not a generic 404. +- Docker must not be pointed at a sub-path registry URL; the registry name is + the host, and `/v2/` is fixed by the OCI distribution API. +- If Gitea is served through a sub-path, route root-level `/v2/` to the Gitea + service as required by Docker-compatible registries. + +Boundary note: Gitea Helm ingress/service settings belong here. Ingress +controller or cluster network changes belong in `railiance-cluster`. + +**Done when:** `/v2/` is routed correctly through the intended operator and +cluster access paths. + +--- + +### T04 — Prove Docker login, push, and pull + +```task +id: RAIL-AP-WP-0001-T04 +status: blocked +priority: high +state_hub_task_id: "5ffd7515-384b-4a11-9b5e-141197d1b985" +``` + +Use a Gitea user or bot personal access token with package read/write +permissions to prove the registry workflow. + +Smoke sequence: + +```bash +docker login +docker tag state-hub:local /coulomb/state-hub: +docker push /coulomb/state-hub: +docker pull /coulomb/state-hub: +``` + +Then verify pull behavior from the cluster node runtime or a disposable +Kubernetes pod, including TLS trust and private-registry credentials if the +package is private. + +**Done when:** the State Hub image can be pushed and pulled by both the +operator workstation and the Railiance cluster runtime. + +--- + +### T05 — Document registry handoff for State Hub deployment + +```task +id: RAIL-AP-WP-0001-T05 +status: done +priority: medium +state_hub_task_id: "55c2fd0c-ee6b-4524-8022-f21d6e9e046f" +``` + +Record the approved registry target and downstream handoff details. + +Expected output: + +- Registry host and owner/image naming convention. +- State Hub image tag used for the successful smoke test. +- Whether images are public or private. +- Required Kubernetes `imagePullSecret` name or creation command if private. +- Link back to `CUST-WP-0011` and its container image provenance. + +**Done when:** State Hub cluster deployment work can consume the image without +rediscovering registry naming, auth, or TLS requirements. + +--- + +### T06 — Capture backup and retention implications + +```task +id: RAIL-AP-WP-0001-T06 +status: blocked +priority: medium +state_hub_task_id: "d5734ef1-d710-458c-b569-034f03a50bd8" +``` + +Confirm how Gitea package data is stored and backed up once container images +are published. + +Checks: + +- Identify whether package blobs live on the existing Gitea persistent volume or + another configured storage backend. +- Confirm the current backup process includes package data. +- Decide whether image retention or cleanup policy is needed before publishing + many tags. +- If storage or backups need S3/platform changes, create a follow-up + `railiance-platform` workplan or task. + +**Done when:** package data durability is understood and no hidden storage gap +is introduced by enabling the registry. + +## Implementation Log + +### 2026-05-15 — Inventory and S5 routing update + +T01 findings: + +- Active Kubernetes context: `default`. +- Live Helm release metadata is stored in namespace `default` as revisions + `sh.helm.release.v1.gitea.v1` through `v6`. +- Live deployment labels report chart `gitea-12.5.0`, app version `1.25.4`, + image `docker.gitea.com/gitea:1.25.4-rootless`. +- Live Gitea service is `default/gitea`, type `NodePort`, port + `3000:32166/TCP`. +- `default/gitea` pod app.ini has server `ROOT_URL = + http://gitea.coulomb.social`, `SSH_DOMAIN = gitea.coulomb.social`, and + `DOMAIN = gitea.coulomb.social`. +- No `[packages]` section was found in the inspected live app.ini output, but + the application handler is active: pod-local `/v2/` and + `http://92.205.130.254:32166/v2/` both returned OCI registry + `401 Unauthorized` with `Docker-Distribution-Api-Version: registry/2.0`. +- `http://gitea.coulomb.social/v2/` and + `https://gitea.coulomb.social/v2/` returned generic `404`, so the immediate + blocker is public hostname routing. A secondary cleanup is updating + `ROOT_URL` to `https://gitea.coulomb.social/` so future auth challenges use + the TLS endpoint. + +T02 status: + +- SOPS editing was attempted with `sops 3.9.0`, matching the file metadata. +- The local age identity required by `helm/gitea-values.sops.yaml` was not + available at the default path, so encrypted Helm values were not changed. +- Once the age identity is available, apply these non-secret app config values: + +```bash +sops set helm/gitea-values.sops.yaml '["gitea"]["config"]["packages"]["ENABLED"]' 'true' +sops set helm/gitea-values.sops.yaml '["gitea"]["config"]["packages"]["LIMIT_SIZE_CONTAINER"]' '-1' +sops set helm/gitea-values.sops.yaml '["gitea"]["config"]["repository"]["DISABLED_REPO_UNITS"]' '""' +sops set helm/gitea-values.sops.yaml '["gitea"]["config"]["server"]["ROOT_URL"]' '"https://gitea.coulomb.social/"' +``` + +T03 implementation: + +- Added `manifests/gitea-ingress.yaml`, a Traefik/cert-manager ingress for + only `gitea.coulomb.social/v2*` to the existing `default/gitea` service. +- Added `make gitea-ingress-deploy`. +- Updated Makefile variables so the default Gitea namespace matches the live + release namespace `default`; this avoids accidentally deploying a parallel + `gitea` namespace release while the live release remains in `default`. +- Applied the ingress to the live cluster. +- Cert-manager issued `default/gitea-tls`. +- `http://gitea.coulomb.social/v2/` now returns `401 Unauthorized` with + `Docker-Distribution-Api-Version: registry/2.0`. +- `https://gitea.coulomb.social/v2/` now returns `401 Unauthorized` with + `Docker-Distribution-Api-Version: registry/2.0` and a TLS token realm. + +T04 blocker: + +- Docker is available locally, but no Gitea personal access token was present + in this session. Login, push, pull, and cluster runtime pull remain blocked + on a package-capable token and the T02/T03 deployment. + +T05 handoff: + +- Registry host: `gitea.coulomb.social`. +- Image naming convention: `gitea.coulomb.social/coulomb/state-hub:`. +- Handoff notes and `imagePullSecret` command are documented in + `docs/gitea-container-registry.md`. +- This links back to `CUST-WP-0011`, whose local image provenance is + `state-hub:local`. + +T06 findings: + +- Live Gitea package data is expected to use the existing `/data` mount backed + by PVC `default/gitea-shared-storage` (`10Gi`, `local-path`) unless package + storage is separately configured later. +- No Kubernetes `CronJob` backup resources were present in the live cluster + inventory. Backup coverage for `gitea-shared-storage` needs operator + confirmation or a `railiance-platform` follow-up before publishing many tags. + +## Completion Criteria + +This workplan is complete when: + +1. Gitea's container registry is enabled through governed Helm values. +2. `/v2/` is reachable at the chosen registry host. +3. `state-hub:local` has been pushed as + `/coulomb/state-hub:`. +4. The pushed image can be pulled from the Railiance cluster runtime. +5. Registry auth, TLS, naming, and backup/retention notes are documented. + +## Notes + +The future Forgejo migration should inherit the lessons from this workplan: +registry package scope, `/v2/` routing, package data backups, and cluster image +pull credentials. If Forgejo lands before this plan starts, close this workplan +as superseded and move the tasks under `RAIL-HO-WP-0005` / the appropriate S5 +Forgejo workplan.