Compare commits

...

10 commits

Author SHA1 Message Date
a74df4001b Add Forgejo CI smoke workflow (enablement template)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
2026-07-04 12:49:57 +02:00
28cbd3187c Fix in-cluster runner startup and readiness probes
Wait for DinD before starting forgejo-runner daemon; use TCP/_ping
probes so the Deployment reports available once both containers are up.
2026-07-03 22:32:34 +02:00
0f0b340754 Add in-cluster Forgejo Actions runner manifests (ADR-004)
DinD sidecar + forgejo-runner Deployment with PVC-backed registration
state. Makefile targets for registration secret, deploy, and status.
2026-07-03 22:29:27 +02:00
f49be83f7e Enable Forgejo Actions and Recreate deployment strategy
Actions are required for CI runners. Recreate avoids leveldb queue lock
contention on the shared PVC during Helm upgrades.
2026-07-03 21:44:57 +02:00
75698636c6 Deploy Forgejo on railiance01 using gitea-charts/gitea 12.5.0
Pin chart to 12.5.0 because 12.6+ calls `gitea config edit-ini`, which
Forgejo 11 lacks. Ingress targets forgejo-gitea-http (Helm release naming).
Smoke test uses GET for /v2/ registry challenge (HEAD returns 405).
2026-07-03 21:28:37 +02:00
6d00232865 Regenerate agent instructions: workstream -> workplan terminology
Registration guidance now prescribes file-first + fix-consistency (C-06)
instead of manual create_workplan/create_workstream calls; progress-event
examples use workplan_id; legacy field names annotated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 01:47:46 +02:00
8db47c8200 Archive closed workplans to workplans/archived/ (ADR-001)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 00:25:41 +02:00
0ef387b8e5 chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-07-02:
  - update .custodian-brief.md for railiance-apps
2026-07-02 00:23:25 +02:00
eedfb89c18 Repo hygiene: fill stack-and-commands, normalize workplan statuses
- Fill .claude/rules/stack-and-commands.md (was an empty TODO template)
- Normalize workplan frontmatter statuses to canonical vocabulary
  (completed/done -> finished) per ADR-001

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 00:21:48 +02:00
bf7ffa0df6 Normalize agent instructions and workplan frontmatter (STATE-WP-0067)
- Align agent files with on-disk workplan prefixes (infer from workplan ids)
- Set workplan domain to registered domain_slug; add topic_slug where applicable
- Repair frontmatter delimiter formatting; migrate legacy task status literals
- Regenerate AGENTS.md, CLAUDE.md, and .claude/rules from State Hub templates
2026-06-22 23:16:27 +02:00
28 changed files with 641 additions and 77 deletions

View file

@ -20,7 +20,7 @@ Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run wa
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=railiance-apps` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workplans; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table

View file

@ -1,37 +1,41 @@
## First Session Protocol
Triggered when `get_domain_summary("railiance")` shows **no workstreams**.
Triggered when `get_domain_summary("financials")` shows **no workplans**.
The project is registered but work has not yet been structured.
**Step 1 — Read, don't write**
- `~/the-custodian/canon/projects/railiance/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/railiance/roadmap_v0.1.md` — planned phases
- `~/the-custodian/canon/projects/financials/project_charter_v0.1.md` — purpose, scope
- `~/the-custodian/canon/projects/financials/roadmap_v0.1.md` — planned phases
- Scan repo root: README, directory structure, existing code or docs
**Step 2 — Survey in-progress work**
Look for TODOs, open branches, half-finished files. Note done vs. started but incomplete.
**Step 3 — Propose workstreams to Bernd**
Propose 13 workstreams — each a coherent strand, weeks to months, anchored to a
**Step 3 — Propose workplans to Bernd**
Propose 13 workplans — each a coherent strand, weeks to months, anchored to a
roadmap phase. **Wait for approval before creating.**
**Step 4 — Create workplan file first, then DB record (ADR-001)**
**Step 4 — Write the workplan file; fix-consistency registers it (ADR-001)**
```
workplans/railiance-apps-WP-NNNN-<slug>.md ← write this first
workplans/RAILIANCE-WP-NNNN-<slug>.md ← write this, commit it
```
Then register in the hub:
```
create_workstream(topic_id="ca369340-a64e-442e-98f1-a4fa7dc74a38", title="...", owner="...", description="...")
create_task(workstream_id="<id>", title="...", priority="high|medium|low")
Then register by running the consistency check — do **not** call
`create_workplan`/`create_task` (or legacy `create_workstream`) yourself;
manual registration duplicates what C-06 creates from the file:
```bash
statehub fix-consistency --repo railiance-apps
```
C-06 creates the hub workplan + tasks and writes `state_hub_workstream_id` /
`state_hub_task_id` back into the file (legacy field names, kept for
compatibility — they hold workplan/task IDs).
**Step 5 — Record the setup**
```
add_progress_event(
summary="First session: structured railiance into N workstreams, M tasks",
summary="First session: structured financials into N workplans, M tasks",
event_type="milestone",
topic_id="ca369340-a64e-442e-98f1-a4fa7dc74a38",
detail={"workstreams": [...], "tasks_created": M}
detail={"workplans": [...], "tasks_created": M}
)
```

View file

@ -1,5 +1,5 @@
**Purpose:** OAS S5 Workloads & Experience Endpoints — application Helm releases, Gitea, coulomb services
**Domain:** railiance
**Domain:** financials
**Repo slug:** railiance-apps
**Topic ID:** ca369340-a64e-442e-98f1-a4fa7dc74a38

View file

@ -1,6 +1,7 @@
## Session Protocol
State Hub: http://127.0.0.1:8000
Dev Hub (State Hub API): http://127.0.0.1:8000
MCP server name in `~/.claude.json`: `dev-hub`
**Step 1 — Orient**
@ -10,7 +11,7 @@ cat .custodian-brief.md
```
Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
```
get_domain_summary("railiance")
get_domain_summary("financials")
```
If MCP tools are unavailable in the current agent session, use the REST API:
```bash
@ -39,11 +40,11 @@ curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
ls workplans/
```
For each file with `status: ready`, `active`, or `blocked`, note pending
`todo`/`in_progress` tasks.
`wait`/`todo`/`progress` tasks.
**Step 4 — Present brief**
1. **Active workstreams** for `railiance` — title, task counts, blocking decisions
1. **Active workplans** for `financials` — title, task counts, blocking decisions
2. **Pending tasks** from `workplans/` + any `[repo:railiance-apps]` hub tasks
3. **Goal guidance** — if `goal_guidance` in summary:
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
@ -51,33 +52,42 @@ For each file with `status: ready`, `active`, or `blocked`, note pending
4. **Suggested next action** — highest-priority open item
5. **SBOM status** — flag if `last_sbom_at` is unset for this repo
If no workstreams: follow First Session Protocol (`first-session.md`).
If no workplans: follow First Session Protocol (`first-session.md`).
**During work:** `record_decision()` · `add_progress_event()` · `resolve_decision()`
> State Hub is a *read model*. Bootstrap tools (`create_workstream`, `create_task`)
> are First Session Protocol only. Work structure belongs in repo files (ADR-001).
> State Hub is a *read model*. **Never register workplans or tasks by hand**
> (`create_workplan`, `create_task`, or the legacy `create_workstream`) — write
> the workplan file in `workplans/` and run `fix-consistency`; its C-06 check
> registers the workplan and its tasks in the hub and writes the IDs back into
> the file. Manual registration creates duplicates the moment fix-consistency
> runs. Work structure belongs in repo files (ADR-001).
>
> Terminology: "workstream" is the legacy name for workplan. Some API/frontmatter
> field names keep it for compatibility (`state_hub_workstream_id`,
> `workstream_id` params) — treat them as workplan IDs.
**Session close:**
With MCP tools:
```
add_progress_event(summary="...", topic_id="ca369340-a64e-442e-98f1-a4fa7dc74a38", workstream_id="<uuid>")
add_progress_event(summary="...", topic_id="ca369340-a64e-442e-98f1-a4fa7dc74a38", workplan_id="<uuid>")
```
Without MCP tools:
```bash
curl -s -X POST http://127.0.0.1:8000/progress/ \
-H "Content-Type: application/json" \
-d '{"topic_id":"ca369340-a64e-442e-98f1-a4fa7dc74a38","workstream_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
-d '{"topic_id":"ca369340-a64e-442e-98f1-a4fa7dc74a38","workplan_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
```
If workplan files were modified, ensure the local copy is up to date first:
If workplan files were modified, ensure the local copy is up to date first,
then sync from the repo checkout:
```bash
git -C <repo_path> pull --ff-only
cd ~/state-hub && make fix-consistency REPO=railiance-apps
git pull --ff-only
statehub fix-consistency
```
For repos where implementation runs on a remote machine (e.g. CoulombCore),
use the combined target which pulls before fixing:
use the pull-before-fix mode from any shell with the State Hub CLI:
```bash
cd ~/state-hub && make fix-consistency-remote REPO=railiance-apps
statehub fix-consistency --repo railiance-apps --remote
```
**C-15** (DB task ahead of file) is normal in multi-machine workflows — writeback
will sync the file to match DB. **C-16** (repo behind remote) blocks all writes

View file

@ -1,19 +1,20 @@
## Stack
<!-- TODO: Fill in language, frameworks, and key dependencies -->
- **Language:**
- **Key deps:**
- **Language:** Helm values + Kubernetes manifests, Bash Make targets
- **Key deps:** vergabe-teilnahme (Django) and inter-hub releases, shared apps-pg cnpg cluster, SOPS/age; runs against the S2 cluster on railiance01
## Dev Commands
```bash
# TODO: Fill in the standard commands for this repo
# Install dependencies
# Run tests
# Lint / type check
# Build / package (if applicable)
make help # list all targets
make check-tools && make check-sops # operator preflight (read-only)
make apps-pg-status # shared DB health (read-only)
make vergabe-dry-run # helm template render, no apply
make vergabe-status # pod / svc / ingress / cert state (read-only)
make vergabe-deploy # production-touching
make vergabe-migrate | vergabe-seed # production-touching (Django management)
make check-inter-hub-image-tag # gate: explicit image tag for prod deploys
```
`*-deploy` / `*-migrate` / `*-seed` change production workloads — operator
approval required. Dry-run/status/check targets are safe.

View file

@ -1,28 +1,45 @@
## Workplan Convention (ADR-001)
File location: `workplans/railiance-apps-WP-NNNN-<slug>.md`
ID prefix: `RAILIANCE-WP`
File location: `workplans/RAILIANCE-WP-NNNN-<slug>.md`
ID prefix: `RAILIANCE-WP-`
Work items originate as files in this repo **before** being registered in the hub.
Canonical workplan/workstream frontmatter statuses are:
Canonical workplan frontmatter statuses are:
`proposed`, `ready`, `active`, `blocked`, `backlog`, `finished`, `archived`.
Use `proposed` for a newly drafted plan, `ready` after review against current
repo state, and `finished` when implementation is complete. `stalled` and
`needs_review` are derived health labels, not stored statuses.
Closed workplans may be moved to `workplans/archived/` with a completion-date
prefix: `YYMMDD-railiance-apps-WP-NNNN-<slug>.md`. The frontmatter id remains
prefix: `YYMMDD-RAILIANCE-WP-NNNN-<slug>.md`. The frontmatter id remains
unchanged; the prefix is only for quick visual reference.
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
`workplans/ADHOC-YYYY-MM-DD.md`, workstream slug `adhoc-YYYY-MM-DD`, and task ids
`workplans/ADHOC-YYYY-MM-DD.md`, workplan slug `adhoc-YYYY-MM-DD`, and task ids
`ADHOC-YYYY-MM-DD-T01`, `T02`, etc. Use adhocs only for low-risk work completed
directly. Promote anything requiring analysis, design, approval, dependencies, or
multiple planned phases into a normal workplan.
Ecosystem todos from other agents arrive as `[repo:railiance-apps]` hub tasks —
visible at session start. Pick one up by creating the workplan file, then registering
the workstream.
visible at session start. Pick one up by creating the workplan file, committing,
and running `statehub fix-consistency` — C-06 registers the workplan in the hub.
Never register by hand with `create_workplan`/`create_workstream`.
Task blocks use this shape:
```task
id: RAILIANCE-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
```
Status progression is `todo``progress``done`; use `wait` for waiting or
blocked work and `cancel` for stopped work.
Workplan frontmatter carries `state_hub_workstream_id` — a legacy field name
kept for compatibility ("workstream" is the old term for workplan); it holds
the hub workplan id and is written by fix-consistency. Do not edit or rename it.
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->

View file

@ -1,8 +1,8 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — railiance-apps
**Domain:** railiance
**Last synced:** 2026-06-15 13:58 UTC
**Domain:** financials
**Last synced:** 2026-07-01 22:23 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
@ -13,6 +13,6 @@
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("railiance")`
`get_domain_summary("financials")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

View file

@ -0,0 +1,29 @@
# Canonical CI smoke template (tier 1 routing drill).
# Copy to: .forgejo/workflows/ci-smoke.yaml in consumer repos.
name: CI Smoke
on:
push:
branches:
- main
workflow_dispatch:
jobs:
host-smoke:
runs-on: self-hosted
steps:
- name: Routing probe (host runner)
run: |
set -eu
echo "repository=${GITHUB_REPOSITORY:-unknown}"
echo "sha=${GITHUB_SHA:-unknown}"
echo "runner=${RUNNER_NAME:-unknown}"
uname -a
container-smoke:
runs-on: ubuntu-latest
steps:
- name: Routing probe (container label)
run: |
set -eu
echo "container-smoke ok for ${GITHUB_REPOSITORY:-unknown}"

View file

@ -2,9 +2,9 @@
## Repo Identity
**Purpose:** OAS S5 Workloads & Experience Endpoints — application Helm releases, app runbooks, and deployment guardrails
**Purpose:** OAS S5 Workloads & Experience Endpoints — application Helm releases, Gitea, coulomb services
**Domain:** railiance
**Domain:** financials
**Repo slug:** railiance-apps
**Topic ID:** `ca369340-a64e-442e-98f1-a4fa7dc74a38`
**Workplan prefix:** `RAILIANCE-WP-`
@ -20,6 +20,12 @@ there is no MCP server for Codex agents.
|---------|-----|
| Local workstation | `http://127.0.0.1:8000` |
| Remote via tunnel | `http://127.0.0.1:18000` |
| Optional local edge relay | http://127.0.0.1:18080 |
When an operator has enabled the edge relay, set API_BASE to the relay URL.
Queueable writes return an explicit queued receipt if the central hub is
unreachable. Treat that as pending local evidence, then ask the operator to run
statehub outbox status/replay after connectivity returns.
### Orient at session start
@ -27,8 +33,8 @@ there is no MCP server for Codex agents.
# Offline brief — works without hub connection
cat .custodian-brief.md
# Active workstreams for this domain
curl -s "http://127.0.0.1:8000/workstreams/?topic_id=ca369340-a64e-442e-98f1-a4fa7dc74a38&status=active" \
# Active workplans for this domain
curl -s "http://127.0.0.1:8000/workplans/?topic_id=ca369340-a64e-442e-98f1-a4fa7dc74a38&status=active" \
| python3 -m json.tool
# Check inbox
@ -51,12 +57,12 @@ curl -s -X POST http://127.0.0.1:8000/progress/ \
"summary": "what was done",
"event_type": "note",
"author": "codex",
"workstream_id": "<uuid>",
"workplan_id": "<uuid>",
"task_id": "<uuid>"
}'
```
Omit `workstream_id` / `task_id` when not applicable.
Omit `workplan_id` / `task_id` when not applicable.
### Update task status
@ -80,10 +86,10 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
## Session Protocol
**Start:**
1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe)
1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe)
2. Check inbox: `GET /messages/?to_agent=railiance-apps&unread_only=true`; mark read
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
4. Check blocked tasks: `GET /tasks/?needs_human=true`
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
**During work:**
- Update task statuses in workplan files as tasks progress
@ -92,12 +98,12 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
**Close:**
1. Update workplan file task statuses to reflect progress
2. Log: `POST /progress/` with a summary of what changed
3. Note for the custodian operator: after workplan file changes, run from
`~/state-hub`:
3. After workplan file changes, run:
```bash
make fix-consistency REPO=railiance-apps
statehub fix-consistency
```
This syncs task status from files into the hub DB.
Coding agents should run this directly; ask the operator only if the CLI or
State Hub API is unavailable. This syncs task status from files into the hub DB.
---
@ -123,7 +129,7 @@ Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run wa
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=railiance-apps` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workplans; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
@ -151,6 +157,11 @@ every repo's agent instructions because it is high-frequency, high-risk, and eas
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
## Workplan Convention (ADR-001)
@ -176,7 +187,7 @@ anything needing analysis, design, approval, dependencies, or multiple phases.
id: RAILIANCE-WP-NNNN
type: workplan
title: "..."
domain: railiance
domain: financials
repo: railiance-apps
status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex
@ -206,7 +217,7 @@ state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
Task description text.
```
Status progression: `wait` → `todo` → `progress``done` (or `cancel`)
Status progression: `todo` → `progress``done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
To create a new workplan:
1. Write the file following the format above

View file

@ -32,10 +32,26 @@ REUSE_CERTIFICATE ?= reuse-surface-tls
RAILIANCE01_KUBECONFIG ?= $(HOME)/.kube/config-hosteurope
INTER_HUB_KUBECONFIG ?= $(RAILIANCE01_KUBECONFIG)
REUSE_KUBECONFIG ?= $(RAILIANCE01_KUBECONFIG)
FORGEJO_KUBECONFIG ?= $(RAILIANCE01_KUBECONFIG)
FORGEJO_RELEASE ?= forgejo
FORGEJO_NAMESPACE ?= forgejo
FORGEJO_CHART ?= gitea-charts/gitea
# Forgejo 11.x (Gitea 1.22) lacks `gitea config edit-ini`; chart 12.6+ requires Gitea 1.26+.
FORGEJO_CHART_VERSION ?= 12.5.0
FORGEJO_VALUES ?= helm/forgejo-values.yaml
FORGEJO_REGISTRY_VALUES ?= helm/forgejo-registry-values.yaml
FORGEJO_SECRETS ?= helm/forgejo-secrets.sops.yaml
FORGEJO_INGRESS ?= manifests/forgejo-ingress.yaml
FORGEJO_SSH_NODEPORT ?= manifests/forgejo-ssh-nodeport.yaml
FORGEJO_BASE_URL ?= https://forgejo.coulomb.social
FORGEJO_DB_CLUSTER ?= forgejo-db
FORGEJO_DB_NAMESPACE ?= databases
FORGEJO_RUNNER_MANIFEST ?= manifests/forgejo-runner.yaml
FORGEJO_RUNNER_REGISTRATION_SECRET ?= helm/forgejo-runner-registration.sops.yaml
INTER_HUB_IMAGE_REF = $(INTER_HUB_IMAGE_REPOSITORY):$(INTER_HUB_IMAGE_TAG)
INTER_HUB_IMAGE_SET_ARG = $(if $(strip $(INTER_HUB_IMAGE_TAG)),--set image.tag=$(INTER_HUB_IMAGE_TAG),)
SOPS_SENTINEL ?=
SOPS_SENTINEL ?= $(FORGEJO_SECRETS)
DRY_RUN_CREATE_NAMESPACES ?= false
##@ Operator checks
@ -153,6 +169,75 @@ inter-hub-smoke: ## Verify public inter-hub v2 route and OpenAPI surface after r
inter-hub-logs: check-railiance01-kubeconfig ## Tail inter-hub app logs from Railiance01
KUBECONFIG="$(INTER_HUB_KUBECONFIG)" kubectl logs -n $(INTER_HUB_NAMESPACE) -l app=$(INTER_HUB_RELEASE) -f --tail=50
##@ Forgejo (forgejo.coulomb.social)
forgejo-dry-run: check-railiance01-kubeconfig check-sops ## helm template render for Forgejo
KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm template $(FORGEJO_RELEASE) $(FORGEJO_CHART) \
--version $(FORGEJO_CHART_VERSION) \
--namespace $(FORGEJO_NAMESPACE) \
-f $(FORGEJO_VALUES) \
-f $(FORGEJO_REGISTRY_VALUES) \
-f <(sops -d $(FORGEJO_SECRETS))
forgejo-server-dry-run: check-railiance01-kubeconfig check-sops ## Helm server dry-run Forgejo upgrade
KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm upgrade --install $(FORGEJO_RELEASE) $(FORGEJO_CHART) \
--version $(FORGEJO_CHART_VERSION) \
--namespace $(FORGEJO_NAMESPACE) --create-namespace \
-f $(FORGEJO_VALUES) \
-f $(FORGEJO_REGISTRY_VALUES) \
-f <(sops -d $(FORGEJO_SECRETS)) \
--dry-run=server --timeout 5m
forgejo-deploy: check-railiance01-kubeconfig check-sops ## Deploy / upgrade Forgejo on railiance01
KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm repo add gitea-charts https://dl.gitea.com/charts/ --force-update
KUBECONFIG="$(FORGEJO_KUBECONFIG)" helm upgrade --install $(FORGEJO_RELEASE) $(FORGEJO_CHART) \
--version $(FORGEJO_CHART_VERSION) \
--namespace $(FORGEJO_NAMESPACE) --create-namespace \
-f $(FORGEJO_VALUES) \
-f $(FORGEJO_REGISTRY_VALUES) \
-f <(sops -d $(FORGEJO_SECRETS)) \
--wait --timeout 10m
forgejo-ingress-deploy: check-railiance01-kubeconfig ## Apply Forgejo HTTPS ingress
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl apply -f $(FORGEJO_INGRESS)
forgejo-ssh-nodeport-deploy: check-railiance01-kubeconfig ## Apply Forgejo SSH NodePort (30022)
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl apply -f $(FORGEJO_SSH_NODEPORT)
forgejo-status: check-railiance01-kubeconfig ## Show Forgejo pods, svc, ingress, cert, database
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl get pods,svc,ingress,certificate,pvc -n $(FORGEJO_NAMESPACE) --ignore-not-found
@if KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl cnpg status $(FORGEJO_DB_CLUSTER) -n $(FORGEJO_DB_NAMESPACE) >/dev/null 2>&1; then \
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl cnpg status $(FORGEJO_DB_CLUSTER) -n $(FORGEJO_DB_NAMESPACE); \
else \
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl get cluster $(FORGEJO_DB_CLUSTER) -n $(FORGEJO_DB_NAMESPACE) --ignore-not-found; \
fi
forgejo-smoke: ## Verify Forgejo web and OCI registry challenge
FORGEJO_BASE_URL="$(FORGEJO_BASE_URL)" tools/forgejo-smoke.sh
forgejo-logs: check-railiance01-kubeconfig ## Tail Forgejo application logs
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl logs -n $(FORGEJO_NAMESPACE) -l app.kubernetes.io/instance=$(FORGEJO_RELEASE) -f --tail=50
forgejo-runner-registration-deploy: check-railiance01-kubeconfig check-sops ## Apply SOPS runner registration secret
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl apply -f <(sops -d $(FORGEJO_RUNNER_REGISTRATION_SECRET))
forgejo-runner-deploy: check-railiance01-kubeconfig ## Deploy in-cluster Forgejo Actions runner (ADR-004)
@test -n "$$(KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl get secret forgejo-runner-registration -n $(FORGEJO_NAMESPACE) -o name 2>/dev/null)" || { \
echo "Missing secret forgejo-runner-registration in $(FORGEJO_NAMESPACE)." >&2; \
echo "Apply helm/forgejo-runner-registration.sops.yaml or bootstrap once:" >&2; \
echo " kubectl create secret generic forgejo-runner-registration -n forgejo --from-literal=token=<token>" >&2; \
exit 1; \
}
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl apply -f $(FORGEJO_RUNNER_MANIFEST)
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl rollout status deployment/forgejo-runner -n $(FORGEJO_NAMESPACE) --timeout=5m
forgejo-runner-status: check-railiance01-kubeconfig ## Show in-cluster runner pod and PVC state
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl get deploy,pods,pvc -n $(FORGEJO_NAMESPACE) -l app.kubernetes.io/name=forgejo-runner --ignore-not-found
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl logs -n $(FORGEJO_NAMESPACE) deploy/forgejo-runner -c runner --tail=20 2>/dev/null || true
forgejo-runner-logs: check-railiance01-kubeconfig ## Tail in-cluster runner logs
KUBECONFIG="$(FORGEJO_KUBECONFIG)" kubectl logs -n $(FORGEJO_NAMESPACE) deploy/forgejo-runner -c runner -f --tail=50
##@ reuse-surface (reuse.coulomb.social)
reuse-dry-run: check-railiance01-kubeconfig ## helm template render (no apply) for reuse-surface
@ -183,4 +268,4 @@ help: ## Show this help
/^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } \
/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs help
.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs help

View file

@ -0,0 +1,93 @@
# Forgejo on railiance01
Production source forge at **`https://forgejo.coulomb.social`**.
Mirrors the coulombcore Gitea pattern (`railiance-forge`) but targets
**railiance01** using the same OAS split as other S5 apps (`inter-hub`,
`reuse-surface`).
## Layer ownership
| Layer | Repo | Concern |
| --- | --- | --- |
| S3 | `railiance-platform` | `forgejo-db` CNPG cluster + network policies |
| S5 | `railiance-apps` | Helm release, ingress, operator Makefile |
| S2 | `railiance-cluster` | Traefik, cert-manager, cnpg operator |
Hostname decision: `the-custodian/docs/forgejo-production-decisions.md`.
## Hosts
| Item | Value |
| --- | --- |
| Server | railiance01 `92.205.62.239` |
| Namespace | `forgejo` |
| Helm release | `forgejo` |
| HTTP service | `forgejo-gitea-http` (chart naming; ingress must target this) |
| Chart | `gitea-charts/gitea` **12.5.0** (Forgejo-compatible; 12.6+ needs Gitea 1.26 `config edit-ini`) |
| Image | `code.forgejo.org/forgejo/forgejo:11.0.3` |
| Database | `forgejo-db-rw.databases.svc.cluster.local:5432` |
| Kubeconfig | `~/.kube/config-hosteurope` |
## Bootstrap (first deploy)
### 1. Database credentials (platform)
```bash
cd ~/railiance-platform
# One-time: create and SOPS-encrypt helm/forgejo-db-secret.sops.yaml from template
KUBECONFIG=~/.kube/config-hosteurope make forgejo-db-deploy
KUBECONFIG=~/.kube/config-hosteurope make forgejo-db-status
```
### 2. Application secrets (apps)
```bash
cd ~/railiance-apps
# Encrypt helm/forgejo-secrets.sops.yaml from template (DB PASSWD must match platform secret)
make check-sops
```
### 3. Deploy Forgejo
```bash
cd ~/railiance-apps
make forgejo-dry-run
make forgejo-deploy
make forgejo-ingress-deploy
make forgejo-ssh-nodeport-deploy # optional; git+ssh via nodePort 30022
# In-cluster Actions runner (ADR-004, railiance-infra/docs/adr/ADR-004-...)
# One-time: encrypt helm/forgejo-runner-registration.sops.yaml from template
make forgejo-runner-registration-deploy
make forgejo-runner-deploy
make forgejo-status
make forgejo-smoke
make forgejo-runner-status
```
## Upgrade notes
- Pin `FORGEJO_CHART_VERSION=12.5.0` — chart 12.6+ requires Gitea 1.26 `config edit-ini`.
- `strategy.type: Recreate` in `helm/forgejo-values.yaml` — avoids leveldb queue lock
on the shared RWO PVC during rolling updates.
- Actions enabled via `gitea.config.actions.ENABLED: true`.
- Ingress backend service name is `forgejo-gitea-http` (Helm release naming).
## Day-2 operator targets
```bash
make forgejo-status
make forgejo-logs
make forgejo-smoke
```
## Coexistence with Gitea
Gitea on coulombcore remains canonical until `RAIL-HO-WP-0005` migration drills
and cutover pass. Do not repoint repo remotes until Wave 1 cutover is approved.
## Related
- Gitea reference: `~/railiance-forge/Makefile` (`gitea-deploy`)
- Drain plan: `the-custodian/docs/coulombcore-drain-placement-plan.md` Wave 1
- Onboarding checklist: `docs/s5-app-onboarding-checklist.md`

View file

@ -0,0 +1,14 @@
# Public endpoint and package registry settings for Forgejo on railiance01.
service:
http:
type: ClusterIP
gitea:
config:
packages:
ENABLED: true
LIMIT_SIZE_CONTAINER: -1
repository:
DISABLED_REPO_UNITS: ""
server:
ROOT_URL: "https://forgejo.coulomb.social/"

View file

@ -0,0 +1,15 @@
# Encrypt as helm/forgejo-runner-registration.sops.yaml, then:
# kubectl apply -f <(sops -d helm/forgejo-runner-registration.sops.yaml)
#
# Generate token (one-time, do not commit plaintext):
# kubectl exec -n forgejo deploy/forgejo-gitea -- \
# forgejo actions generate-runner-token --scope coulomb
---
apiVersion: v1
kind: Secret
metadata:
name: forgejo-runner-registration
namespace: forgejo
type: Opaque
stringData:
token: REPLACE_WITH_RUNNER_REGISTRATION_TOKEN

View file

@ -0,0 +1,15 @@
# Encrypt as helm/forgejo-secrets.sops.yaml before production deploy.
# Contains database PASSWD and admin password consumed by the gitea chart.
#
# gitea:
# admin:
# password: <generated>
# config:
# database:
# PASSWD: <same as forgejo-db-credentials>
gitea:
admin:
password: REPLACE_WITH_ADMIN_PASSWORD
config:
database:
PASSWD: REPLACE_WITH_DB_PASSWORD

53
helm/forgejo-values.yaml Normal file
View file

@ -0,0 +1,53 @@
# Non-secret Forgejo Helm values (gitea-charts/gitea chart + Forgejo image).
# Layer helm/forgejo-secrets.sops.yaml for database password and admin credentials.
# Recreate avoids leveldb queue lock fights on the shared RWO PVC during upgrades.
strategy:
type: Recreate
service:
http:
type: ClusterIP
image:
registry: code.forgejo.org
repository: forgejo/forgejo
tag: "11.0.3"
rootless: true
persistence:
enabled: true
size: 10Gi
postgresql:
enabled: false
postgresql-ha:
enabled: false
redis-cluster:
enabled: false
valkey-cluster:
enabled: false
gitea:
admin:
username: forgejo_admin
email: admin@coulomb.social
passwordMode: initialOnlyRequireReset
config:
database:
DB_TYPE: postgres
HOST: forgejo-db-rw.databases.svc.cluster.local:5432
NAME: forgejo
USER: forgejo
server:
DOMAIN: forgejo.coulomb.social
SSH_DOMAIN: forgejo.coulomb.social
SSH_PORT: 22
SSH_LISTEN_PORT: 2222
DISABLE_SSH: false
service:
DISABLE_REGISTRATION: true
actions:
ENABLED: true

View file

@ -0,0 +1,45 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: forgejo
namespace: forgejo
labels:
app.kubernetes.io/name: forgejo
app.kubernetes.io/instance: forgejo
app.kubernetes.io/part-of: railiance-apps
railiance/component: forgejo-public-endpoint
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
ingressClassName: traefik
rules:
- host: forgejo.coulomb.social
http:
paths:
- path: /api/packages
pathType: Prefix
backend:
service:
name: forgejo-gitea-http
port:
number: 3000
- path: /v2
pathType: Prefix
backend:
service:
name: forgejo-gitea-http
port:
number: 3000
- path: /
pathType: Prefix
backend:
service:
name: forgejo-gitea-http
port:
number: 3000
tls:
- hosts:
- forgejo.coulomb.social
secretName: forgejo-tls

View file

@ -0,0 +1,142 @@
# In-cluster Forgejo Actions runner (ADR-004).
# DinD sidecar + forgejo-runner; registration via init container on first boot.
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: forgejo-runner-data
namespace: forgejo
labels:
app.kubernetes.io/name: forgejo-runner
app.kubernetes.io/part-of: railiance-apps
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: local-path
---
apiVersion: v1
kind: ConfigMap
metadata:
name: forgejo-runner-init
namespace: forgejo
labels:
app.kubernetes.io/name: forgejo-runner
data:
register.sh: |
#!/bin/sh
set -eu
cd /data
if [ ! -f config.yaml ]; then
forgejo-runner generate-config > config.yaml
fi
if [ ! -f .runner ]; then
forgejo-runner register --no-interactive \
--config /data/config.yaml \
--instance "${FORGEJO_INSTANCE}" \
--token "${REGISTRATION_TOKEN}" \
--name "${RUNNER_NAME}" \
--labels "${RUNNER_LABELS}"
fi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: forgejo-runner
namespace: forgejo
labels:
app.kubernetes.io/name: forgejo-runner
app.kubernetes.io/part-of: railiance-apps
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: forgejo-runner
template:
metadata:
labels:
app.kubernetes.io/name: forgejo-runner
spec:
securityContext:
fsGroup: 1000
initContainers:
- name: register
image: code.forgejo.org/forgejo/runner:6.3.1
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "/scripts/register.sh"]
env:
- name: FORGEJO_INSTANCE
value: "https://forgejo.coulomb.social/"
- name: RUNNER_NAME
value: "railiance01-build-01"
- name: RUNNER_LABELS
value: "self-hosted:host,linux:host,linux_amd64:host,container-build:host,registry-publish:host,railiance01:host,ubuntu-latest:docker://node:20-bookworm,docker:docker://node:20-bookworm"
- name: REGISTRATION_TOKEN
valueFrom:
secretKeyRef:
name: forgejo-runner-registration
key: token
volumeMounts:
- name: data
mountPath: /data
- name: init-scripts
mountPath: /scripts
readOnly: true
containers:
- name: dind
image: docker:27-dind
imagePullPolicy: IfNotPresent
securityContext:
privileged: true
env:
- name: DOCKER_TLS_CERTDIR
value: ""
args:
- dockerd
- -H
- tcp://0.0.0.0:2375
- --tls=false
readinessProbe:
tcpSocket:
port: 2375
initialDelaySeconds: 10
periodSeconds: 10
- name: runner
image: code.forgejo.org/forgejo/runner:6.3.1
imagePullPolicy: IfNotPresent
env:
- name: DOCKER_HOST
value: tcp://127.0.0.1:2375
command: ["/bin/sh", "-c"]
args:
- |
set -eu
echo "waiting for DinD..."
for i in $(seq 1 90); do
if wget -q -O- http://127.0.0.1:2375/_ping 2>/dev/null | grep -q OK; then
echo "DinD ready"
break
fi
sleep 2
done
exec forgejo-runner daemon --config /data/config.yaml
volumeMounts:
- name: data
mountPath: /data
readinessProbe:
exec:
command: ["/bin/sh", "-c", "wget -q -O- http://127.0.0.1:2375/_ping | grep -q OK"]
initialDelaySeconds: 15
periodSeconds: 30
volumes:
- name: data
persistentVolumeClaim:
claimName: forgejo-runner-data
- name: init-scripts
configMap:
name: forgejo-runner-init
defaultMode: 0555

View file

@ -0,0 +1,21 @@
# Git/SSH access for Forgejo (NodePort pattern mirrors coulombcore Gitea).
apiVersion: v1
kind: Service
metadata:
name: forgejo-ssh-nodeport
namespace: forgejo
labels:
app.kubernetes.io/name: forgejo
app.kubernetes.io/instance: forgejo
app.kubernetes.io/part-of: railiance-apps
spec:
type: NodePort
selector:
app.kubernetes.io/instance: forgejo
app.kubernetes.io/name: gitea
ports:
- name: ssh
port: 22
targetPort: 2222
nodePort: 30022
protocol: TCP

9
tools/forgejo-smoke.sh Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${FORGEJO_BASE_URL:-https://forgejo.coulomb.social}"
curl -fsS "${BASE_URL}/" -o /dev/null -w "root_http=%{http_code}\n"
# HEAD returns 405 on Forgejo/Gitea; GET returns 401 + Docker-Distribution-Api-Version.
curl -sSI -X GET "${BASE_URL}/v2/" | awk 'BEGIN{code=""; ok=0} /^HTTP/{code=$2} /^[Dd]ocker-[Dd]istribution-[Aa]pi-[Vv]ersion:/{ok=1; print "registry_challenge=ok"} END{if (code=="" || !ok) exit 1; if (code!="401" && code!="200") {print "registry_http=" code; exit 1} else {print "registry_http=" code}}'
echo "forgejo smoke ok: ${BASE_URL}"

View file

@ -2,7 +2,7 @@
id: RAILIANCE-WP-0005
type: workplan
title: "S5 app release readiness and scope alignment"
domain: railiance
domain: financials
repo: railiance-apps
status: finished
owner: codex

View file

@ -2,7 +2,7 @@
id: RAILIANCE-WP-0006
type: workplan
title: "Extract railiance-forge and formalize forge-layer contracts"
domain: railiance
domain: financials
repo: railiance-apps
status: finished
owner: codex

View file

@ -2,7 +2,7 @@
id: RAILIANCE-WP-0007
type: workplan
title: "Deploy reuse-surface federation service on railiance01"
domain: railiance
domain: financials
repo: railiance-apps
status: finished
owner: codex

View file

@ -2,7 +2,7 @@
id: RAILIANCE-WP-0008
type: workplan
title: "Add friendly landing pages for S5 service endpoints"
domain: railiance
domain: financials
repo: railiance-apps
status: finished
owner: codex

View file

@ -2,7 +2,7 @@
id: RAILIANCE-WP-0009
type: workplan
title: "Inter-hub chart handoff"
domain: railiance
domain: financials
repo: railiance-apps
status: finished
owner: codex

View file

@ -2,7 +2,7 @@
id: RAILIANCE-WP-0010
type: workplan
title: "Inbox suggestion guardrails"
domain: railiance
domain: financials
repo: railiance-apps
status: finished
owner: codex

View file

@ -2,7 +2,7 @@
id: RAILIANCE-WP-0011
type: workplan
title: "Inter-Hub production trigger hardening"
domain: railiance
domain: financials
repo: railiance-apps
status: finished
owner: codex

View file

@ -2,7 +2,7 @@
id: RAILIANCE-WP-0002
type: workplan
title: "Establish vergabe-teilnahme as an Application on railiance01"
domain: railiance
domain: financials
repo: railiance-apps
status: finished
owner: railiance

View file

@ -2,7 +2,7 @@
id: RAILIANCE-WP-0004
type: workplan
title: "App deployment improvements (lessons from RAILIANCE-WP-0002)"
domain: railiance
domain: financials
repo: railiance-apps
status: finished
owner: railiance