Add Forgejo T05 verify, operator bootstrap, and security hardening
Introduce forgejo-verify acceptance checks, idempotent operator bootstrap, runner registration SOPS capture helper, and session/security Helm values.
This commit is contained in:
parent
9b31f229e8
commit
3a9236148a
8 changed files with 436 additions and 1 deletions
5
Makefile
5
Makefile
|
|
@ -246,6 +246,9 @@ forgejo-status: check-railiance01-kubeconfig ## Show Forgejo pods, svc, ingress,
|
|||
forgejo-smoke: ## Verify Forgejo web and OCI registry challenge
|
||||
FORGEJO_BASE_URL="$(FORGEJO_BASE_URL)" tools/forgejo-smoke.sh
|
||||
|
||||
forgejo-npm-smoke: ## T07 npm install smoke (@coulomb/forgejo-npm-probe)
|
||||
tools/forgejo-npm-smoke.sh
|
||||
|
||||
forgejo-verify: check-railiance01-kubeconfig ## T05 acceptance: smoke, TLS, DB, mailer ESO, SSH operator, SOPS sentinels
|
||||
FORGEJO_BASE_URL="$(FORGEJO_BASE_URL)" KUBECONFIG="$(FORGEJO_KUBECONFIG)" tools/forgejo-verify.sh
|
||||
|
||||
|
|
@ -320,4 +323,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 eso-deploy forgejo-openbao-eso-token-apply forgejo-openbao-store-deploy forgejo-mailer-es-deploy forgejo-mailer-es-status forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-verify forgejo-operator-bootstrap forgejo-runner-registration-sops-bootstrap forgejo-secrets-check 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
|
||||
.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 eso-deploy forgejo-openbao-eso-token-apply forgejo-openbao-store-deploy forgejo-mailer-es-deploy forgejo-mailer-es-status forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-npm-smoke forgejo-verify forgejo-operator-bootstrap forgejo-runner-registration-sops-bootstrap forgejo-secrets-check 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
|
||||
|
|
|
|||
|
|
@ -79,6 +79,29 @@ make forgejo-runner-status
|
|||
make forgejo-status
|
||||
make forgejo-logs
|
||||
make forgejo-smoke
|
||||
make forgejo-verify # T05 acceptance (smoke, TLS, DB, mailer, SSH operator)
|
||||
make forgejo-secrets-check # SOPS sentinels present
|
||||
```
|
||||
|
||||
## Operator accounts (T05)
|
||||
|
||||
Bootstrap is automated and does not commit secrets:
|
||||
|
||||
```bash
|
||||
# Requires admin API token at /tmp/forgejo-tegwick-api-token (or FORGEJO_ADMIN_TOKEN)
|
||||
make forgejo-operator-bootstrap
|
||||
```
|
||||
|
||||
Default operator: `tegwick` (site admin, `coulomb` Owners team, SSH via
|
||||
`forgejo-remote` / `~/.ssh/id_gitea`). `forgejo_admin` remains the Helm chart
|
||||
bootstrap account; day-2 git+ssh should use the operator user.
|
||||
|
||||
Runner registration token: if the cluster secret was created with `kubectl create
|
||||
secret` (not SOPS), capture it once on a host with the age key:
|
||||
|
||||
```bash
|
||||
make forgejo-runner-registration-sops-bootstrap
|
||||
make check-sops SOPS_SENTINEL=helm/forgejo-runner-registration.sops.yaml
|
||||
```
|
||||
|
||||
## SMTP (IONOS) — password reset / notifications
|
||||
|
|
@ -135,6 +158,15 @@ Do not put the SMTP password in `helm/forgejo-secrets.sops.yaml`.
|
|||
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.
|
||||
|
||||
## Package registry (OCI, npm, generic)
|
||||
|
||||
See `docs/forgejo-package-registry.md`. Smoke targets:
|
||||
|
||||
```bash
|
||||
make forgejo-smoke
|
||||
make forgejo-npm-smoke
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- Gitea reference: `~/railiance-forge/Makefile` (`gitea-deploy`)
|
||||
|
|
|
|||
81
docs/forgejo-package-registry.md
Normal file
81
docs/forgejo-package-registry.md
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# Forgejo Package Registry (railiance01)
|
||||
|
||||
Workplan: `RAIL-HO-WP-0005` T07 · Decision: Option C (OCI + npm + generic)
|
||||
|
||||
Public host: `https://forgejo.coulomb.social`
|
||||
|
||||
Helm values: `helm/forgejo-registry-values.yaml` (`packages.ENABLED`, `ROOT_URL`).
|
||||
|
||||
## OCI (container images)
|
||||
|
||||
Registry API: `GET /v2/` (expect `401` + `Docker-Distribution-Api-Version`).
|
||||
|
||||
```bash
|
||||
docker login forgejo.coulomb.social -u tegwick
|
||||
docker tag IMAGE forgejo.coulomb.social/coulomb/REPO:TAG
|
||||
docker push forgejo.coulomb.social/coulomb/REPO:TAG
|
||||
```
|
||||
|
||||
Cluster pull (railiance01):
|
||||
|
||||
```bash
|
||||
KUBECONFIG=~/.kube/config-hosteurope crictl pull forgejo.coulomb.social/coulomb/REPO:TAG
|
||||
```
|
||||
|
||||
Smoke: `make forgejo-smoke`
|
||||
|
||||
## npm
|
||||
|
||||
Org registry: `https://forgejo.coulomb.social/api/packages/coulomb/npm/`
|
||||
|
||||
Workstation config (use a PAT with `read:package` / `write:package`; do not commit):
|
||||
|
||||
```bash
|
||||
npm config set @coulomb:registry https://forgejo.coulomb.social/api/packages/coulomb/npm/
|
||||
npm config set -- '//forgejo.coulomb.social/api/packages/coulomb/npm/:_authToken' "${FORGEJO_NPM_TOKEN}"
|
||||
```
|
||||
|
||||
Publish (scoped package under `@coulomb/`):
|
||||
|
||||
```bash
|
||||
npm publish --scope=@coulomb
|
||||
# or explicit:
|
||||
npm publish --scope=@coulomb --registry=https://forgejo.coulomb.social/api/packages/coulomb/npm/
|
||||
```
|
||||
|
||||
Install:
|
||||
|
||||
```bash
|
||||
npm install @coulomb/PACKAGE@VERSION
|
||||
```
|
||||
|
||||
Probe package (T07 evidence): `@coulomb/forgejo-npm-probe@0.0.1`
|
||||
|
||||
Smoke: `make forgejo-npm-smoke` (requires `FORGEJO_NPM_TOKEN` or `/tmp/forgejo-tegwick-api-token`)
|
||||
|
||||
CI template: `railiance-enablement/workflows/npm-publish.yaml`
|
||||
|
||||
## Generic packages
|
||||
|
||||
Upload release artifacts via Forgejo UI or API (`/api/packages/{owner}/generic/...`).
|
||||
Use for binaries, checksum files, and non-OCI release assets.
|
||||
|
||||
## Retention and backup
|
||||
|
||||
| Type | Storage | Backup | Cleanup |
|
||||
| --- | --- | --- | --- |
|
||||
| OCI | `/data/packages` in Forgejo PVC | `make forgejo-backup` (`forgejo dump`) | Delete unused tags in UI; no auto-prune yet |
|
||||
| npm | same | same | `npm unpublish @coulomb/pkg@version` or UI package settings |
|
||||
| generic | same | same | per-package UI delete |
|
||||
|
||||
Package blobs are included in daily `forgejo dump` (~686M with 14 repos, 2026-07-07).
|
||||
Restore drills must verify package metadata after isolated restore (T09).
|
||||
|
||||
RPO/RTO for package data matches Forgejo backup policy: 24h / 4h
|
||||
(`the-custodian/docs/forgejo-production-decisions.md` § Backup).
|
||||
|
||||
## Related
|
||||
|
||||
- `docs/forgejo-on-railiance01.md`
|
||||
- `railiance-enablement/docs/forgejo-actions-workflow-templates.md`
|
||||
- Gitea legacy reference: `~/railiance-forge/docs/gitea-package-registry.md`
|
||||
|
|
@ -56,6 +56,10 @@ gitea:
|
|||
DISABLE_SSH: false
|
||||
service:
|
||||
DISABLE_REGISTRATION: true
|
||||
security:
|
||||
INSTALL_LOCK: true
|
||||
session:
|
||||
COOKIE_SECURE: true
|
||||
mailer:
|
||||
ENABLED: true
|
||||
FROM: forgejo@coulomb.social
|
||||
|
|
|
|||
34
tools/forgejo-npm-smoke.sh
Executable file
34
tools/forgejo-npm-smoke.sh
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env bash
|
||||
# T07 npm registry smoke — install published @coulomb/forgejo-npm-probe (no publish).
|
||||
set -euo pipefail
|
||||
|
||||
REGISTRY="${FORGEJO_NPM_REGISTRY:-https://forgejo.coulomb.social/api/packages/coulomb/npm/}"
|
||||
TOKEN_FILE="${FORGEJO_NPM_TOKEN_FILE:-${FORGEJO_TOKEN_FILE:-/tmp/forgejo-tegwick-api-token}}"
|
||||
PROBE_PKG="${FORGEJO_NPM_PROBE_PKG:-@coulomb/forgejo-npm-probe@0.0.1}"
|
||||
|
||||
if [[ -n "${FORGEJO_NPM_TOKEN:-}" ]]; then
|
||||
TOKEN="${FORGEJO_NPM_TOKEN}"
|
||||
elif [[ -f "${TOKEN_FILE}" ]]; then
|
||||
TOKEN="$(cat "${TOKEN_FILE}")"
|
||||
else
|
||||
echo "Set FORGEJO_NPM_TOKEN or create ${TOKEN_FILE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
command -v npm >/dev/null 2>&1 || { echo "npm required" >&2; exit 1; }
|
||||
|
||||
work="$(mktemp -d)"
|
||||
trap 'rm -rf "${work}"' EXIT
|
||||
cd "${work}"
|
||||
|
||||
npm config set @coulomb:registry "${REGISTRY}"
|
||||
npm config set -- '//forgejo.coulomb.social/api/packages/coulomb/npm/:_authToken' "${TOKEN}"
|
||||
|
||||
npm install "${PROBE_PKG}" >/dev/null
|
||||
out="$(node -e "console.log(require('@coulomb/forgejo-npm-probe')())")"
|
||||
if [[ "${out}" == "forgejo-npm-probe-ok" ]]; then
|
||||
echo "npm smoke ok: ${PROBE_PKG}"
|
||||
else
|
||||
echo "npm smoke fail: unexpected output '${out}'" >&2
|
||||
exit 1
|
||||
fi
|
||||
104
tools/forgejo-operator-bootstrap.sh
Executable file
104
tools/forgejo-operator-bootstrap.sh
Executable file
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/env bash
|
||||
# Idempotent operator account bootstrap for Forgejo (RAIL-HO-WP-0005 T05).
|
||||
# Does not commit secrets — uses API token from file or env.
|
||||
set -euo pipefail
|
||||
|
||||
FORGEJO_API="${FORGEJO_API:-https://forgejo.coulomb.social/api/v1}"
|
||||
FORGEJO_ORG="${FORGEJO_ORG:-coulomb}"
|
||||
FORGEJO_OPERATOR_USER="${FORGEJO_OPERATOR_USER:-tegwick}"
|
||||
FORGEJO_OPERATOR_EMAIL="${FORGEJO_OPERATOR_EMAIL:-bernd.worsch@gmail.com}"
|
||||
FORGEJO_OPERATOR_SSH_KEY="${FORGEJO_OPERATOR_SSH_KEY:-$HOME/.ssh/id_gitea.pub}"
|
||||
FORGEJO_OPERATOR_SSH_TITLE="${FORGEJO_OPERATOR_SSH_TITLE:-workstation-automation}"
|
||||
TOKEN_FILE="${FORGEJO_TOKEN_FILE:-/tmp/forgejo-tegwick-api-token}"
|
||||
|
||||
if [[ -n "${FORGEJO_ADMIN_TOKEN:-}" ]]; then
|
||||
TOKEN="${FORGEJO_ADMIN_TOKEN}"
|
||||
elif [[ -f "${TOKEN_FILE}" ]]; then
|
||||
TOKEN="$(cat "${TOKEN_FILE}")"
|
||||
else
|
||||
echo "Set FORGEJO_ADMIN_TOKEN or create ${TOKEN_FILE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
AUTH=(-H "Authorization: token ${TOKEN}")
|
||||
|
||||
user_status() {
|
||||
curl -sS -o /dev/null -w '%{http_code}' "${AUTH[@]}" "${FORGEJO_API}/users/${FORGEJO_OPERATOR_USER}"
|
||||
}
|
||||
|
||||
echo "==> Forgejo operator bootstrap: ${FORGEJO_OPERATOR_USER}"
|
||||
|
||||
case "$(user_status)" in
|
||||
200)
|
||||
echo " user exists"
|
||||
;;
|
||||
404)
|
||||
echo " creating user ${FORGEJO_OPERATOR_USER}"
|
||||
FORGEJO_OPERATOR_USER="${FORGEJO_OPERATOR_USER}" \
|
||||
FORGEJO_OPERATOR_EMAIL="${FORGEJO_OPERATOR_EMAIL}" \
|
||||
curl -fsS -X POST "${FORGEJO_API}/admin/users" "${AUTH[@]}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(python3 -c "
|
||||
import json, os
|
||||
print(json.dumps({
|
||||
'username': os.environ['FORGEJO_OPERATOR_USER'],
|
||||
'email': os.environ['FORGEJO_OPERATOR_EMAIL'],
|
||||
'password': os.urandom(18).hex(),
|
||||
'must_change_password': False,
|
||||
'send_notify': False,
|
||||
}))
|
||||
")" >/dev/null
|
||||
;;
|
||||
*)
|
||||
echo "Unexpected user lookup status for ${FORGEJO_OPERATOR_USER}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Promote to site admin
|
||||
curl -fsS -X PATCH "${FORGEJO_API}/admin/users/${FORGEJO_OPERATOR_USER}" "${AUTH[@]}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"admin":true,"prohibit_login":false}' >/dev/null
|
||||
echo " admin=true"
|
||||
|
||||
# Org Owners team membership
|
||||
team_id="$(curl -fsS "${AUTH[@]}" "${FORGEJO_API}/orgs/${FORGEJO_ORG}/teams" \
|
||||
| python3 -c "import sys,json; teams=json.load(sys.stdin); print(next((t['id'] for t in teams if t.get('name')=='Owners'), ''))")"
|
||||
if [[ -n "${team_id}" ]]; then
|
||||
curl -fsS -X PUT "${AUTH[@]}" \
|
||||
"${FORGEJO_API}/teams/${team_id}/members/${FORGEJO_OPERATOR_USER}" >/dev/null 2>&1 \
|
||||
&& echo " org ${FORGEJO_ORG} Owners team" \
|
||||
|| echo " (already in Owners team or API skipped)"
|
||||
fi
|
||||
|
||||
# SSH key for git+ssh
|
||||
if [[ ! -f "${FORGEJO_OPERATOR_SSH_KEY}" ]]; then
|
||||
echo "Missing SSH public key: ${FORGEJO_OPERATOR_SSH_KEY}" >&2
|
||||
exit 1
|
||||
fi
|
||||
existing="$(curl -fsS "${AUTH[@]}" "${FORGEJO_API}/users/${FORGEJO_OPERATOR_USER}/keys" \
|
||||
| FORGEJO_OPERATOR_SSH_KEY="${FORGEJO_OPERATOR_SSH_KEY}" python3 -c "
|
||||
import json, os, sys
|
||||
want = open(os.environ['FORGEJO_OPERATOR_SSH_KEY']).read().strip()
|
||||
keys = json.load(sys.stdin)
|
||||
print(any((k.get('key') or '').strip() == want for k in keys))
|
||||
")"
|
||||
if [[ "${existing}" == "True" ]]; then
|
||||
echo " SSH key already present"
|
||||
else
|
||||
FORGEJO_OPERATOR_SSH_TITLE="${FORGEJO_OPERATOR_SSH_TITLE}" \
|
||||
FORGEJO_OPERATOR_SSH_KEY="${FORGEJO_OPERATOR_SSH_KEY}" \
|
||||
curl -fsS -X POST "${FORGEJO_API}/admin/users/${FORGEJO_OPERATOR_USER}/keys" "${AUTH[@]}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(python3 -c "
|
||||
import json, os
|
||||
print(json.dumps({
|
||||
'title': os.environ['FORGEJO_OPERATOR_SSH_TITLE'],
|
||||
'key': open(os.environ['FORGEJO_OPERATOR_SSH_KEY']).read().strip(),
|
||||
}))
|
||||
")" >/dev/null
|
||||
echo " SSH key added (${FORGEJO_OPERATOR_SSH_TITLE})"
|
||||
fi
|
||||
|
||||
echo "==> Operator bootstrap complete: ${FORGEJO_OPERATOR_USER}"
|
||||
echo " Test: ssh forgejo-remote # expect Hi there, ${FORGEJO_OPERATOR_USER}!"
|
||||
44
tools/forgejo-runner-registration-sops-bootstrap.sh
Executable file
44
tools/forgejo-runner-registration-sops-bootstrap.sh
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env bash
|
||||
# One-time: capture cluster runner registration token into SOPS (operator host with age key).
|
||||
# RAIL-HO-WP-0005 T05 — closes runner secret gap when bootstrap used kubectl create secret.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
OUT="${ROOT}/helm/forgejo-runner-registration.sops.yaml"
|
||||
KUBECONFIG="${KUBECONFIG:-$HOME/.kube/config-hosteurope}"
|
||||
FORGEJO_NAMESPACE="${FORGEJO_NAMESPACE:-forgejo}"
|
||||
|
||||
export KUBECONFIG
|
||||
|
||||
if ! command -v sops >/dev/null 2>&1; then
|
||||
echo "ERROR: sops required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -f "${OUT}" ]]; then
|
||||
echo "Already exists: ${OUT}" >&2
|
||||
echo "Remove it first if you intend to re-capture from cluster." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
token="$(kubectl get secret forgejo-runner-registration -n "${FORGEJO_NAMESPACE}" \
|
||||
-o jsonpath='{.data.token}' | base64 -d)"
|
||||
if [[ -z "${token}" ]]; then
|
||||
echo "ERROR: forgejo-runner-registration secret missing or empty in ${FORGEJO_NAMESPACE}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat > "${OUT}" <<EOF
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: forgejo-runner-registration
|
||||
namespace: ${FORGEJO_NAMESPACE}
|
||||
type: Opaque
|
||||
stringData:
|
||||
token: ${token}
|
||||
EOF
|
||||
|
||||
sops --encrypt --in-place "${OUT}"
|
||||
echo "Created ${OUT}"
|
||||
echo "Verify: SOPS_SENTINEL=${OUT} make check-sops"
|
||||
133
tools/forgejo-verify.sh
Executable file
133
tools/forgejo-verify.sh
Executable file
|
|
@ -0,0 +1,133 @@
|
|||
#!/usr/bin/env bash
|
||||
# T05 acceptance checks for production Forgejo on railiance01 (RAIL-HO-WP-0005).
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BASE_URL="${FORGEJO_BASE_URL:-https://forgejo.coulomb.social}"
|
||||
KUBECONFIG="${KUBECONFIG:-$HOME/.kube/config-hosteurope}"
|
||||
FORGEJO_NAMESPACE="${FORGEJO_NAMESPACE:-forgejo}"
|
||||
FORGEJO_DB_NAMESPACE="${FORGEJO_DB_NAMESPACE:-databases}"
|
||||
FORGEJO_DB_CLUSTER="${FORGEJO_DB_CLUSTER:-forgejo-db}"
|
||||
FORGEJO_OPERATOR_USER="${FORGEJO_OPERATOR_USER:-tegwick}"
|
||||
TOKEN_FILE="${FORGEJO_TOKEN_FILE:-/tmp/forgejo-tegwick-api-token}"
|
||||
|
||||
export KUBECONFIG
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
warn=0
|
||||
|
||||
ok() { echo " PASS $*"; pass=$((pass + 1)); }
|
||||
bad() { echo " FAIL $*" >&2; fail=$((fail + 1)); }
|
||||
note() { echo " WARN $*"; warn=$((warn + 1)); }
|
||||
|
||||
echo "Forgejo T05 verify — ${BASE_URL}"
|
||||
echo "kubeconfig: ${KUBECONFIG}"
|
||||
echo
|
||||
|
||||
# Web + OCI registry smoke
|
||||
if FORGEJO_BASE_URL="${BASE_URL}" "${ROOT}/tools/forgejo-smoke.sh" >/dev/null 2>&1; then
|
||||
ok "web + OCI registry smoke"
|
||||
else
|
||||
bad "web + OCI registry smoke"
|
||||
fi
|
||||
|
||||
# Kubernetes workload
|
||||
if kubectl get pod -n "${FORGEJO_NAMESPACE}" -l app.kubernetes.io/instance=forgejo \
|
||||
-o jsonpath='{.items[0].status.phase}' 2>/dev/null | grep -q Running; then
|
||||
ok "forgejo app pod Running"
|
||||
else
|
||||
bad "forgejo app pod not Running"
|
||||
fi
|
||||
|
||||
if kubectl get deploy forgejo-runner -n "${FORGEJO_NAMESPACE}" >/dev/null 2>&1; then
|
||||
ready="$(kubectl get deploy forgejo-runner -n "${FORGEJO_NAMESPACE}" \
|
||||
-o jsonpath='{.status.readyReplicas}/{.spec.replicas}' 2>/dev/null || echo 0/0)"
|
||||
if [[ "${ready}" == "1/1" ]]; then
|
||||
ok "forgejo-runner ready (${ready})"
|
||||
else
|
||||
note "forgejo-runner replicas ${ready}"
|
||||
fi
|
||||
else
|
||||
bad "forgejo-runner deployment missing"
|
||||
fi
|
||||
|
||||
# TLS ingress
|
||||
if kubectl get certificate forgejo-tls -n "${FORGEJO_NAMESPACE}" \
|
||||
-o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | grep -q True; then
|
||||
ok "TLS certificate Ready"
|
||||
else
|
||||
bad "TLS certificate not Ready"
|
||||
fi
|
||||
|
||||
# Database
|
||||
if kubectl get cluster "${FORGEJO_DB_CLUSTER}" -n "${FORGEJO_DB_NAMESPACE}" \
|
||||
-o jsonpath='{.status.phase}' 2>/dev/null | grep -qi healthy; then
|
||||
ok "forgejo-db cluster healthy"
|
||||
else
|
||||
bad "forgejo-db cluster not healthy"
|
||||
fi
|
||||
|
||||
# Mailer via OpenBao/ESO (not SOPS)
|
||||
if kubectl get externalsecret forgejo-mailer -n "${FORGEJO_NAMESPACE}" \
|
||||
-o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null | grep -q True; then
|
||||
ok "forgejo-mailer ExternalSecret Ready"
|
||||
else
|
||||
bad "forgejo-mailer ExternalSecret not Ready"
|
||||
fi
|
||||
|
||||
# SOPS secret files (encrypted sentinel — decrypt optional)
|
||||
for f in helm/forgejo-secrets.sops.yaml helm/forgejo-runner-registration.sops.yaml; do
|
||||
if [[ -f "${ROOT}/${f}" ]] && grep -q '^sops:' "${ROOT}/${f}"; then
|
||||
ok "SOPS file present: ${f}"
|
||||
if command -v sops >/dev/null 2>&1 && sops -d "${ROOT}/${f}" >/dev/null 2>&1; then
|
||||
ok "SOPS decrypt: ${f}"
|
||||
else
|
||||
note "SOPS decrypt skipped (no sops or age key): ${f}"
|
||||
fi
|
||||
elif [[ "${f}" == *runner-registration* ]]; then
|
||||
if kubectl get secret forgejo-runner-registration -n "${FORGEJO_NAMESPACE}" >/dev/null 2>&1; then
|
||||
note "runner registration in cluster only — encrypt helm/forgejo-runner-registration.sops.yaml on operator host with age key"
|
||||
else
|
||||
bad "missing runner registration secret"
|
||||
fi
|
||||
else
|
||||
bad "missing SOPS file: ${f}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Operator SSH (tegwick, not forgejo_admin)
|
||||
if ssh -o BatchMode=yes -o ConnectTimeout=8 forgejo-remote 2>&1 | grep -q "Hi there, ${FORGEJO_OPERATOR_USER}!"; then
|
||||
ok "SSH forgejo-remote authenticates as ${FORGEJO_OPERATOR_USER}"
|
||||
else
|
||||
bad "SSH forgejo-remote does not authenticate as ${FORGEJO_OPERATOR_USER}"
|
||||
fi
|
||||
|
||||
# Operator API account
|
||||
if [[ -f "${TOKEN_FILE}" ]]; then
|
||||
token="$(cat "${TOKEN_FILE}")"
|
||||
admin="$(curl -fsS -H "Authorization: token ${token}" \
|
||||
"${BASE_URL}/api/v1/users/${FORGEJO_OPERATOR_USER}" | python3 -c \
|
||||
"import sys,json; print(json.load(sys.stdin).get('is_admin', False))" 2>/dev/null || echo false)"
|
||||
if [[ "${admin}" == "True" ]]; then
|
||||
ok "operator user ${FORGEJO_OPERATOR_USER} is admin (API)"
|
||||
else
|
||||
bad "operator user ${FORGEJO_OPERATOR_USER} is not admin"
|
||||
fi
|
||||
else
|
||||
note "no API token at ${TOKEN_FILE} — skip operator admin API check"
|
||||
fi
|
||||
|
||||
# Git HTTPS reachability (no creds — expect 401/200 on repo HEAD)
|
||||
code="$(curl -sS -o /dev/null -w '%{http_code}' "${BASE_URL}/coulomb/glas-harness")"
|
||||
if [[ "${code}" == "200" ]]; then
|
||||
ok "public repo HTTPS (${code})"
|
||||
else
|
||||
bad "public repo HTTPS unexpected (${code})"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Summary: ${pass} pass, ${fail} fail, ${warn} warn"
|
||||
if [[ "${fail}" -gt 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue