#!/bin/sh set -u tmp="$(mktemp -d)" export tmp trap 'rm -rf "$tmp"' EXIT if ! unidesk_gitea_materialize_payloads "$tmp"; then printf '%s\n' '{"ok":false,"error":"gitea-stdin-payload-materialization-failed","valuesPrinted":false}' exit 1 fi json_tail() { name="$1" limit="${2:-2000}" python3 - "$tmp/$name" "$limit" <<'PY' import json, sys path, limit = sys.argv[1], int(sys.argv[2]) try: data = open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: data = "" print(json.dumps(data)) PY } capture_json() { name="$1" shift "$@" -o json >"$tmp/$name.json" 2>"$tmp/$name.err" rc=$? printf '%s' "$rc" >"$tmp/$name.rc" } run_apply() { manifest="$tmp/gitea.k8s.yaml" if [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then kubectl create namespace "$UNIDESK_GITEA_NAMESPACE" --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager="$UNIDESK_GITEA_FIELD_MANAGER" -f - >"$tmp/namespace.out" 2>"$tmp/namespace.err" fi if [ "$UNIDESK_GITEA_FRPC_ENABLED" = "1" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then kubectl -n "$UNIDESK_GITEA_NAMESPACE" create secret generic "$UNIDESK_GITEA_FRPC_SECRET_NAME" \ --from-file="$UNIDESK_GITEA_FRPC_SECRET_KEY=$tmp/frpc.toml" \ --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager="$UNIDESK_GITEA_FIELD_MANAGER" -f - >"$tmp/frpc-secret.out" 2>"$tmp/frpc-secret.err" frpc_rc=$? else frpc_rc=0 : >"$tmp/frpc-secret.out" if [ "$UNIDESK_GITEA_FRPC_ENABLED" = "1" ]; then printf '%s\n' "dry-run: frpc secret apply skipped" >"$tmp/frpc-secret.err" else printf '%s\n' "frpc disabled" >"$tmp/frpc-secret.err" fi fi if [ "$UNIDESK_GITEA_WEBHOOK_SYNC_ENABLED" = "1" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then kubectl -n "$UNIDESK_GITEA_NAMESPACE" create secret generic "$UNIDESK_GITEA_WEBHOOK_SECRET_NAME" \ --from-literal=github-token="$UNIDESK_GITEA_GITHUB_TOKEN" \ --from-literal=gitea-username="$UNIDESK_GITEA_ADMIN_USERNAME" \ --from-literal=gitea-password="$UNIDESK_GITEA_ADMIN_PASSWORD" \ --from-literal=github-webhook-secret="$UNIDESK_GITEA_GITHUB_WEBHOOK_SECRET" \ --dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager="$UNIDESK_GITEA_FIELD_MANAGER" -f - >"$tmp/webhook-secret.out" 2>"$tmp/webhook-secret.err" webhook_secret_rc=$? else webhook_secret_rc=0 : >"$tmp/webhook-secret.out" if [ "$UNIDESK_GITEA_WEBHOOK_SYNC_ENABLED" = "1" ]; then printf '%s\n' "dry-run: webhook sync secret apply skipped" >"$tmp/webhook-secret.err" else printf '%s\n' "webhook sync disabled" >"$tmp/webhook-secret.err" fi fi timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl apply --server-side --force-conflicts --dry-run=server \ --field-manager="$UNIDESK_GITEA_FIELD_MANAGER" -f "$manifest" >"$tmp/server-dry-run.out" 2>"$tmp/server-dry-run.err" server_dry_run_rc=$? if [ "$frpc_rc" -eq 0 ] && [ "$webhook_secret_rc" -eq 0 ]; then if [ "$server_dry_run_rc" -ne 0 ]; then apply_rc=1 : >"$tmp/apply.out" printf '%s\n' "target server dry-run failed; manifest apply skipped" >"$tmp/apply.err" elif [ "$UNIDESK_GITEA_DRY_RUN" = "1" ]; then apply_rc=0 cp "$tmp/server-dry-run.out" "$tmp/apply.out" : >"$tmp/apply.err" else timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl apply --server-side --force-conflicts \ --field-manager="$UNIDESK_GITEA_FIELD_MANAGER" -f "$manifest" >"$tmp/apply.out" 2>"$tmp/apply.err" apply_rc=$? fi else apply_rc=1 : >"$tmp/apply.out" printf '%s\n' "frpc or webhook secret apply failed; manifest apply skipped" >"$tmp/apply.err" fi if [ "$apply_rc" -eq 0 ] && [ "$UNIDESK_GITEA_WAIT" = "1" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl -n "$UNIDESK_GITEA_NAMESPACE" rollout status "statefulset/$UNIDESK_GITEA_STATEFULSET_NAME" --timeout="${UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS}s" >"$tmp/rollout.out" 2>"$tmp/rollout.err" rollout_rc=$? else rollout_rc=0 : >"$tmp/rollout.out" if [ "$UNIDESK_GITEA_DRY_RUN" = "1" ]; then printf '%s\n' "dry-run: rollout wait skipped" >"$tmp/rollout.err" else printf '%s\n' "rollout wait not requested" >"$tmp/rollout.err" fi fi if [ "$apply_rc" -eq 0 ] && [ "$UNIDESK_GITEA_FRPC_ENABLED" = "1" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then kubectl -n "$UNIDESK_GITEA_NAMESPACE" rollout restart "deployment/$UNIDESK_GITEA_FRPC_DEPLOYMENT_NAME" >"$tmp/frpc-rollout.out" 2>"$tmp/frpc-rollout.err" frpc_rollout_rc=$? if [ "$frpc_rollout_rc" -eq 0 ] && [ "$UNIDESK_GITEA_WAIT" = "1" ]; then timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl -n "$UNIDESK_GITEA_NAMESPACE" rollout status "deployment/$UNIDESK_GITEA_FRPC_DEPLOYMENT_NAME" --timeout="${UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS}s" >>"$tmp/frpc-rollout.out" 2>>"$tmp/frpc-rollout.err" frpc_rollout_rc=$? fi else frpc_rollout_rc=0 : >"$tmp/frpc-rollout.out" if [ "$UNIDESK_GITEA_DRY_RUN" = "1" ]; then printf '%s\n' "dry-run: frpc rollout skipped" >"$tmp/frpc-rollout.err" elif [ "$UNIDESK_GITEA_FRPC_ENABLED" = "1" ]; then printf '%s\n' "manifest apply failed; frpc rollout skipped" >"$tmp/frpc-rollout.err" else printf '%s\n' "frpc disabled" >"$tmp/frpc-rollout.err" fi fi if [ "$UNIDESK_GITEA_FRPC_ENABLED" = "0" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then kubectl -n "$UNIDESK_GITEA_NAMESPACE" delete deployment "$UNIDESK_GITEA_FRPC_DEPLOYMENT_NAME" --ignore-not-found >"$tmp/frpc-cleanup.out" 2>"$tmp/frpc-cleanup.err" frpc_cleanup_deploy_rc=$? kubectl -n "$UNIDESK_GITEA_NAMESPACE" delete secret "$UNIDESK_GITEA_FRPC_SECRET_NAME" --ignore-not-found >>"$tmp/frpc-cleanup.out" 2>>"$tmp/frpc-cleanup.err" frpc_cleanup_secret_rc=$? if [ "$frpc_cleanup_deploy_rc" -eq 0 ] && [ "$frpc_cleanup_secret_rc" -eq 0 ]; then frpc_cleanup_rc=0 else frpc_cleanup_rc=1 fi else frpc_cleanup_rc=0 : >"$tmp/frpc-cleanup.out" if [ "$UNIDESK_GITEA_DRY_RUN" = "1" ]; then printf '%s\n' "dry-run: frpc cleanup skipped" >"$tmp/frpc-cleanup.err" elif [ "$UNIDESK_GITEA_FRPC_ENABLED" = "1" ]; then printf '%s\n' "frpc enabled" >"$tmp/frpc-cleanup.err" else printf '%s\n' "frpc cleanup skipped" >"$tmp/frpc-cleanup.err" fi fi if [ "$apply_rc" -eq 0 ] && [ "$UNIDESK_GITEA_WEBHOOK_SYNC_ENABLED" = "1" ] && [ "$UNIDESK_GITEA_DRY_RUN" != "1" ]; then kubectl -n "$UNIDESK_GITEA_NAMESPACE" rollout restart "deployment/$UNIDESK_GITEA_WEBHOOK_DEPLOYMENT" >"$tmp/webhook-rollout.out" 2>"$tmp/webhook-rollout.err" webhook_rollout_rc=$? if [ "$webhook_rollout_rc" -eq 0 ] && [ "$UNIDESK_GITEA_WAIT" = "1" ]; then timeout "$UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS" kubectl -n "$UNIDESK_GITEA_NAMESPACE" rollout status "deployment/$UNIDESK_GITEA_WEBHOOK_DEPLOYMENT" --timeout="${UNIDESK_GITEA_WAIT_TIMEOUT_SECONDS}s" >>"$tmp/webhook-rollout.out" 2>>"$tmp/webhook-rollout.err" webhook_rollout_rc=$? fi else webhook_rollout_rc=0 : >"$tmp/webhook-rollout.out" if [ "$UNIDESK_GITEA_DRY_RUN" = "1" ]; then printf '%s\n' "dry-run: webhook sync rollout skipped" >"$tmp/webhook-rollout.err" elif [ "$UNIDESK_GITEA_WEBHOOK_SYNC_ENABLED" = "1" ]; then printf '%s\n' "manifest apply failed; webhook sync rollout skipped" >"$tmp/webhook-rollout.err" else printf '%s\n' "webhook sync disabled" >"$tmp/webhook-rollout.err" fi fi python3 - "$frpc_rc" "$webhook_secret_rc" "$server_dry_run_rc" "$apply_rc" "$rollout_rc" "$frpc_rollout_rc" "$webhook_rollout_rc" "$frpc_cleanup_rc" "$tmp/frpc-secret.out" "$tmp/frpc-secret.err" "$tmp/frpc-cleanup.out" "$tmp/frpc-cleanup.err" "$tmp/webhook-secret.out" "$tmp/webhook-secret.err" "$tmp/server-dry-run.out" "$tmp/server-dry-run.err" "$tmp/apply.out" "$tmp/apply.err" "$tmp/rollout.out" "$tmp/rollout.err" "$tmp/frpc-rollout.out" "$tmp/frpc-rollout.err" "$tmp/webhook-rollout.out" "$tmp/webhook-rollout.err" <<'PY' import json, os, sys frpc_rc, webhook_secret_rc, server_dry_run_rc, apply_rc, rollout_rc, frpc_rollout_rc, webhook_rollout_rc, frpc_cleanup_rc = [int(value) for value in sys.argv[1:9]] def text(path, limit=3000): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" dry = os.environ.get("UNIDESK_GITEA_DRY_RUN") == "1" payload = { "ok": all(value == 0 for value in [frpc_rc, webhook_secret_rc, server_dry_run_rc, apply_rc, rollout_rc, frpc_rollout_rc, webhook_rollout_rc, frpc_cleanup_rc]), "target": os.environ.get("UNIDESK_GITEA_TARGET_ID"), "route": os.environ.get("UNIDESK_GITEA_ROUTE"), "namespace": os.environ.get("UNIDESK_GITEA_NAMESPACE"), "mode": "dry-run" if dry else "confirmed", "mutation": not dry, "image": os.environ.get("UNIDESK_GITEA_IMAGE"), "objects": { "statefulSet": os.environ.get("UNIDESK_GITEA_STATEFULSET_NAME"), "service": os.environ.get("UNIDESK_GITEA_SERVICE_NAME"), "networkPolicy": "allow-all", }, "steps": { "frpcSecret": {"exitCode": frpc_rc, "stdoutTail": text(sys.argv[9]), "stderrTail": text(sys.argv[10])}, "frpcCleanup": {"exitCode": frpc_cleanup_rc, "stdoutTail": text(sys.argv[11]), "stderrTail": text(sys.argv[12])}, "webhookSyncSecret": {"exitCode": webhook_secret_rc, "stdoutTail": text(sys.argv[13]), "stderrTail": text(sys.argv[14])}, "serverDryRun": {"exitCode": server_dry_run_rc, "stdoutTail": text(sys.argv[15]), "stderrTail": text(sys.argv[16])}, "apply": {"exitCode": apply_rc, "stdoutTail": text(sys.argv[17]), "stderrTail": text(sys.argv[18])}, "rollout": {"exitCode": rollout_rc, "stdoutTail": text(sys.argv[19]), "stderrTail": text(sys.argv[20])}, "frpcRollout": {"exitCode": frpc_rollout_rc, "stdoutTail": text(sys.argv[21]), "stderrTail": text(sys.argv[22])}, "webhookSyncRollout": {"exitCode": webhook_rollout_rc, "stdoutTail": text(sys.argv[23]), "stderrTail": text(sys.argv[24])}, }, "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY } run_status() { selector="app.kubernetes.io/name=$UNIDESK_GITEA_APP_NAME,app.kubernetes.io/component=gitea" capture_json namespace kubectl get namespace "$UNIDESK_GITEA_NAMESPACE" capture_json networkpolicy kubectl -n "$UNIDESK_GITEA_NAMESPACE" get networkpolicy allow-all capture_json statefulset kubectl -n "$UNIDESK_GITEA_NAMESPACE" get statefulset "$UNIDESK_GITEA_STATEFULSET_NAME" capture_json service kubectl -n "$UNIDESK_GITEA_NAMESPACE" get service "$UNIDESK_GITEA_SERVICE_NAME" capture_json endpoints kubectl -n "$UNIDESK_GITEA_NAMESPACE" get endpoints "$UNIDESK_GITEA_SERVICE_NAME" capture_json pods kubectl -n "$UNIDESK_GITEA_NAMESPACE" get pods -l "$selector" capture_json pvc kubectl -n "$UNIDESK_GITEA_NAMESPACE" get pvc -l "$selector" capture_json events kubectl -n "$UNIDESK_GITEA_NAMESPACE" get events --sort-by=.lastTimestamp python3 - "$tmp" <<'PY' import json, os, sys tmp = sys.argv[1] def rc(name): try: return int(open(f"{tmp}/{name}.rc", encoding="utf-8").read() or "1") except FileNotFoundError: return 1 def load(name): try: return json.load(open(f"{tmp}/{name}.json", encoding="utf-8")) except Exception: return None def items(name): data = load(name) if isinstance(data, dict) and isinstance(data.get("items"), list): return data["items"] if isinstance(data, dict) and data.get("kind") != "Status": return [data] return [] def meta_name(item): return ((item or {}).get("metadata") or {}).get("name") def pod_ready(item): status = (item or {}).get("status") or {} return any(c.get("type") == "Ready" and c.get("status") == "True" for c in status.get("conditions", [])) def pod_summary(item): status = (item or {}).get("status") or {} containers = status.get("containerStatuses") or [] return { "name": meta_name(item), "phase": status.get("phase"), "ready": pod_ready(item), "restarts": sum(int(c.get("restartCount") or 0) for c in containers if isinstance(c, dict)), } def service_summary(item): spec = (item or {}).get("spec") or {} return { "name": meta_name(item), "type": spec.get("type"), "clusterIP": spec.get("clusterIP"), "ports": [{"name": p.get("name"), "port": p.get("port"), "targetPort": p.get("targetPort")} for p in spec.get("ports", [])], } def endpoint_ready(item): subsets = ((item or {}).get("subsets") or []) return any(s.get("addresses") for s in subsets if isinstance(s, dict)) def pvc_summary(item): spec = (item or {}).get("spec") or {} status = (item or {}).get("status") or {} return { "name": meta_name(item), "phase": status.get("phase"), "storageClassName": spec.get("storageClassName"), "capacity": (status.get("capacity") or {}).get("storage"), } def event_tail(limit=8): result = [] for item in items("events")[-limit:]: result.append({ "type": item.get("type"), "reason": item.get("reason"), "object": f"{((item.get('involvedObject') or {}).get('kind') or '-')}/{((item.get('involvedObject') or {}).get('name') or '-')}", "message": (item.get("message") or "")[:300], "lastTimestamp": item.get("lastTimestamp") or item.get("eventTime"), }) return result sts = load("statefulset") or {} sts_spec = sts.get("spec") or {} sts_status = sts.get("status") or {} desired = int(sts_spec.get("replicas") or 0) ready_replicas = int(sts_status.get("readyReplicas") or 0) pods = [pod_summary(item) for item in items("pods")] service = service_summary(load("service") or {}) endpoint_ok = endpoint_ready(load("endpoints") or {}) ready = rc("namespace") == 0 and rc("networkpolicy") == 0 and rc("service") == 0 and desired > 0 and ready_replicas >= desired and endpoint_ok payload = { "ok": True, "ready": ready, "target": os.environ.get("UNIDESK_GITEA_TARGET_ID"), "route": os.environ.get("UNIDESK_GITEA_ROUTE"), "namespace": os.environ.get("UNIDESK_GITEA_NAMESPACE"), "image": os.environ.get("UNIDESK_GITEA_IMAGE"), "serviceDns": f"{os.environ.get('UNIDESK_GITEA_SERVICE_NAME')}.{os.environ.get('UNIDESK_GITEA_NAMESPACE')}.svc.cluster.local:{os.environ.get('UNIDESK_GITEA_HTTP_PORT')}", "networkPolicy": {"allowAllPresent": rc("networkpolicy") == 0}, "statefulSet": {"name": meta_name(sts), "desired": desired, "readyReplicas": ready_replicas, "updatedReplicas": sts_status.get("updatedReplicas")}, "service": service, "endpointsReady": endpoint_ok, "pods": pods, "pvcs": [pvc_summary(item) for item in items("pvc")], "eventsTail": event_tail(), "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0) PY } run_validate() { proxy_path="/api/v1/namespaces/$UNIDESK_GITEA_NAMESPACE/services/http:$UNIDESK_GITEA_SERVICE_NAME:$UNIDESK_GITEA_HTTP_PORT/proxy$UNIDESK_GITEA_HEALTH_PATH" kubectl get --raw "$proxy_path" >"$tmp/health.out" 2>"$tmp/health.err" health_rc=$? capture_json endpoints kubectl -n "$UNIDESK_GITEA_NAMESPACE" get endpoints "$UNIDESK_GITEA_SERVICE_NAME" python3 - "$health_rc" "$tmp/health.out" "$tmp/health.err" "$tmp/endpoints.json" "$tmp/endpoints.rc" <<'PY' import json, os, sys health_rc = int(sys.argv[1]) def text(path, limit=2000): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" body = text(sys.argv[2], 4000) try: parsed = json.loads(body) except Exception: parsed = None try: endpoint_rc = int(open(sys.argv[5], encoding="utf-8").read() or "1") except Exception: endpoint_rc = 1 try: endpoints = json.load(open(sys.argv[4], encoding="utf-8")) except Exception: endpoints = {} subsets = endpoints.get("subsets") if isinstance(endpoints, dict) else [] endpoints_ready = any(isinstance(s, dict) and s.get("addresses") for s in (subsets or [])) health_status = parsed.get("status") if isinstance(parsed, dict) else None ok = health_rc == 0 and endpoint_rc == 0 and endpoints_ready and health_status == "pass" payload = { "ok": ok, "target": os.environ.get("UNIDESK_GITEA_TARGET_ID"), "route": os.environ.get("UNIDESK_GITEA_ROUTE"), "namespace": os.environ.get("UNIDESK_GITEA_NAMESPACE"), "service": os.environ.get("UNIDESK_GITEA_SERVICE_NAME"), "serviceProxyPath": f"/api/v1/namespaces/{os.environ.get('UNIDESK_GITEA_NAMESPACE')}/services/http:{os.environ.get('UNIDESK_GITEA_SERVICE_NAME')}:{os.environ.get('UNIDESK_GITEA_HTTP_PORT')}/proxy{os.environ.get('UNIDESK_GITEA_HEALTH_PATH')}", "health": { "exitCode": health_rc, "status": health_status, "description": parsed.get("description") if isinstance(parsed, dict) else None, "bodyBytes": len(body.encode("utf-8")), "stderrTail": text(sys.argv[3]), }, "endpointsReady": endpoints_ready, "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if ok else 1) PY } service_base_url() { kubectl -n "$UNIDESK_GITEA_NAMESPACE" get svc "$UNIDESK_GITEA_SERVICE_NAME" -o json >"$tmp/gitea-svc.json" 2>"$tmp/gitea-svc.err" python3 - "$tmp/gitea-svc.json" "$UNIDESK_GITEA_HTTP_PORT" <<'PY' import json, sys data = json.load(open(sys.argv[1], encoding="utf-8")) print(f"http://{data['spec']['clusterIP']}:{sys.argv[2]}") PY } mirror_repos_json() { test -f "$tmp/repos.json" } admin_basic_b64() { python3 - <<'PY' import base64, os login = os.environ["UNIDESK_GITEA_ADMIN_USERNAME"] username = login.split("@", 1)[0] if "@" in login else login raw = f"{username}:{os.environ['UNIDESK_GITEA_ADMIN_PASSWORD']}".encode() print(base64.b64encode(raw).decode()) PY } admin_cli_username() { python3 - <<'PY' import os login = os.environ["UNIDESK_GITEA_ADMIN_USERNAME"] print(login.split("@", 1)[0] if "@" in login else login) PY } run_mirror_bootstrap() { mirror_repos_json pod="$(kubectl -n "$UNIDESK_GITEA_NAMESPACE" get pod -l "app.kubernetes.io/name=$UNIDESK_GITEA_APP_NAME,app.kubernetes.io/component=gitea" -o jsonpath='{.items[0].metadata.name}' 2>"$tmp/pod.err")" create_rc=1 change_rc=1 unset_rc=1 if [ -n "$pod" ]; then admin_username="$(admin_cli_username)" admin_email="$UNIDESK_GITEA_ADMIN_USERNAME" case "$admin_email" in *@*) ;; *) admin_email="$admin_username@local" ;; esac kubectl -n "$UNIDESK_GITEA_NAMESPACE" exec "$pod" -- gitea --work-path /var/lib/gitea --config /etc/gitea/app.ini admin user create --username "$admin_username" --password "$UNIDESK_GITEA_ADMIN_PASSWORD" --email "$admin_email" --admin --must-change-password=false >"$tmp/admin-create.out" 2>"$tmp/admin-create.err" create_rc=$? if [ "$create_rc" -ne 0 ] && grep -qi 'user already exists' "$tmp/admin-create.err"; then create_rc=0 fi kubectl -n "$UNIDESK_GITEA_NAMESPACE" exec "$pod" -- gitea --work-path /var/lib/gitea --config /etc/gitea/app.ini admin user change-password --username "$admin_username" --password "$UNIDESK_GITEA_ADMIN_PASSWORD" --must-change-password=false >"$tmp/admin-pass.out" 2>"$tmp/admin-pass.err" change_rc=$? kubectl -n "$UNIDESK_GITEA_NAMESPACE" exec "$pod" -- gitea --work-path /var/lib/gitea --config /etc/gitea/app.ini admin user must-change-password --unset "$admin_username" >"$tmp/admin-unset.out" 2>"$tmp/admin-unset.err" unset_rc=$? else printf '%s\n' "gitea pod not found" >"$tmp/admin-create.err" : >"$tmp/admin-create.out" : >"$tmp/admin-pass.out" : >"$tmp/admin-pass.err" : >"$tmp/admin-unset.out" : >"$tmp/admin-unset.err" fi base_url="$(service_base_url)" basic="$(admin_basic_b64)" python3 - "$base_url" "$basic" "$tmp/repos.json" "$tmp/api.json" <<'PY' import json, sys, traceback, urllib.error, urllib.request base_url, basic, repos_path, out_path = sys.argv[1:5] def request(method, path, payload=None, expected=(200, 201, 204), tolerate=()): data = None if payload is None else json.dumps(payload).encode() req = urllib.request.Request(base_url + path, data=data, method=method) req.add_header("Authorization", "Basic " + basic) if payload is not None: req.add_header("Content-Type", "application/json") try: with urllib.request.urlopen(req, timeout=20) as resp: body = resp.read().decode("utf-8", errors="replace") return {"ok": resp.status in expected, "status": resp.status, "body": body[:1000]} except urllib.error.HTTPError as exc: body = exc.read().decode("utf-8", errors="replace") return {"ok": exc.code in tolerate, "status": exc.code, "body": body[:1000]} try: repos = json.load(open(repos_path, encoding="utf-8")) orgs = {} repositories = [] auth = request("GET", "/api/v1/user") for repo in repos: owner = repo["gitea"]["owner"] if owner not in orgs: orgs[owner] = { "create": request("POST", "/api/v1/orgs", {"username": owner, "full_name": "UniDesk internal mirrors", "visibility": "public"}, expected=(201,), tolerate=(409, 422)), "patch": request("PATCH", f"/api/v1/orgs/{owner}", {"full_name": "UniDesk internal mirrors", "visibility": "public"}, expected=(200,), tolerate=()), } repositories.append({ "key": repo["key"], "owner": owner, "name": repo["gitea"]["name"], "create": request("POST", f"/api/v1/orgs/{owner}/repos", {"name": repo["gitea"]["name"], "private": not bool(repo["gitea"].get("publicRead")), "auto_init": False, "description": f"UniDesk controlled mirror for {repo['upstream']['repository']}"}, expected=(201,), tolerate=(409, 422)), "patch": request("PATCH", f"/api/v1/repos/{owner}/{repo['gitea']['name']}", {"private": not bool(repo["gitea"].get("publicRead")), "description": f"UniDesk controlled mirror for {repo['upstream']['repository']}"}, expected=(200,), tolerate=()), }) payload = {"auth": auth, "orgs": orgs, "repositories": repositories} ok = auth.get("ok") and all(v["create"].get("ok") and v["patch"].get("ok") for v in orgs.values()) and all(item["create"].get("ok") and item["patch"].get("ok") for item in repositories) except Exception as exc: payload = { "auth": {"ok": False}, "orgs": {}, "repositories": [], "error": str(exc), "traceTail": traceback.format_exc()[-1600:], } ok = False json.dump(payload, open(out_path, "w", encoding="utf-8"), ensure_ascii=False, indent=2) sys.exit(0 if ok else 1) PY api_rc=$? python3 - "$create_rc" "$change_rc" "$unset_rc" "$api_rc" "$tmp/admin-create.out" "$tmp/admin-create.err" "$tmp/admin-pass.out" "$tmp/admin-pass.err" "$tmp/admin-unset.out" "$tmp/admin-unset.err" "$tmp/api.json" <<'PY' import json, sys def text(path, limit=1600): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" create_rc, change_rc, unset_rc, api_rc = map(int, sys.argv[1:5]) try: api = json.load(open(sys.argv[11], encoding="utf-8")) except Exception: api = {} payload = { "ok": create_rc == 0 and change_rc == 0 and unset_rc == 0 and api_rc == 0, "target": __import__("os").environ.get("UNIDESK_GITEA_TARGET_ID"), "namespace": __import__("os").environ.get("UNIDESK_GITEA_NAMESPACE"), "steps": { "adminCreate": {"exitCode": create_rc, "stdoutTail": text(sys.argv[5]), "stderrTail": text(sys.argv[6])}, "adminPassword": {"exitCode": change_rc, "stdoutTail": text(sys.argv[7]), "stderrTail": text(sys.argv[8])}, "adminMustChangePasswordUnset": {"exitCode": unset_rc, "stdoutTail": text(sys.argv[9]), "stderrTail": text(sys.argv[10])}, "apiBootstrap": {"exitCode": api_rc}, }, "api": api, "repositories": api.get("repositories", []), "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY } run_mirror_sync() { mirror_repos_json base_url="$(service_base_url)" basic="$(admin_basic_b64)" python3 - "$tmp/repos.json" "$tmp/sync-plan.sh" "$tmp/repo-meta.json" "$base_url" <<'PY' import json, shlex, sys, urllib.parse repos = json.load(open(sys.argv[1], encoding="utf-8")) base_url = sys.argv[4].rstrip("/") script = [] meta = [] for i, repo in enumerate(repos): key = repo["key"] source_branch = repo["upstream"]["branch"] gitops_branch = repo["gitops"]["branch"] branches = [source_branch] + ([] if gitops_branch == source_branch or gitops_branch == "not-a-gitops-branch" else [gitops_branch]) work = f"$tmp/repo-{i}.git" gitea_write_url = base_url + urllib.parse.urlparse(repo["gitea"]["readUrl"]).path script += [ f"mkdir -p {shlex.quote(work)}", f"git init --bare {shlex.quote(work)} >$tmp/{key}.init.out 2>$tmp/{key}.init.err; printf '%s' \"$?\" >$tmp/{key}.init.rc", f"git -C {shlex.quote(work)} -c http.extraHeader=\"$GITHUB_AUTH_HEADER\" fetch --prune {shlex.quote(repo['upstream']['cloneUrl'])} " + " ".join(shlex.quote(f"+refs/heads/{b}:refs/remotes/github/{b}") for b in branches) + f" >$tmp/{key}.fetch.out 2>$tmp/{key}.fetch.err; fetch_rc=$?; printf '%s' \"$fetch_rc\" >$tmp/{key}.fetch.rc", f"if [ \"$fetch_rc\" -eq 0 ]; then sha=$(git -C {shlex.quote(work)} rev-parse refs/remotes/github/{shlex.quote(source_branch)} 2>$tmp/{key}.revparse.err); revparse_rc=$?; printf '%s\\n' \"$sha\" >$tmp/{key}.revparse.out; else sha=''; revparse_rc=1; : >$tmp/{key}.revparse.out; printf '%s\\n' 'fetch failed; revparse skipped' >$tmp/{key}.revparse.err; fi; printf '%s' \"$revparse_rc\" >$tmp/{key}.revparse.rc", f"if [ \"$revparse_rc\" -eq 0 ]; then snapshot_ref={shlex.quote(repo['snapshot']['prefix'])}/$sha; git -C {shlex.quote(work)} -c http.extraHeader=\"$GITEA_AUTH_HEADER\" push --force {shlex.quote(gitea_write_url)} $sha:refs/heads/{shlex.quote(source_branch)} $sha:$snapshot_ref >$tmp/{key}.push.out 2>$tmp/{key}.push.err; push_rc=$?; else snapshot_ref=''; push_rc=1; : >$tmp/{key}.push.out; printf '%s\\n' 'revparse failed; push skipped' >$tmp/{key}.push.err; fi; printf '%s' \"$push_rc\" >$tmp/{key}.push.rc", ] if gitops_branch != source_branch and gitops_branch != "not-a-gitops-branch": script.append(f"if [ \"$fetch_rc\" -eq 0 ]; then gitops_sha=$(git -C {shlex.quote(work)} rev-parse refs/remotes/github/{shlex.quote(gitops_branch)} 2>/dev/null || true); else gitops_sha=''; fi") script.append(f"if [ -n \"$gitops_sha\" ] && [ \"$push_rc\" -eq 0 ]; then git -C {shlex.quote(work)} -c http.extraHeader=\"$GITEA_AUTH_HEADER\" push --force {shlex.quote(gitea_write_url)} $gitops_sha:refs/heads/{shlex.quote(gitops_branch)} >>$tmp/{key}.push.out 2>>$tmp/{key}.push.err; gitops_push_rc=$?; else gitops_push_rc=0; fi; printf '%s' \"$gitops_push_rc\" >$tmp/{key}.gitops-push.rc") else: script.append(f"printf '%s' '0' >$tmp/{key}.gitops-push.rc") script.append(f"if [ \"$push_rc\" -eq 0 ]; then printf '%s %s\\n' \"$sha\" \"$snapshot_ref\" >$tmp/{key}.bundle; fi") meta.append({"key": key, "sourceBranch": source_branch, "gitopsBranch": gitops_branch, "readUrl": repo["gitea"]["readUrl"], "writeUrlHost": urllib.parse.urlparse(gitea_write_url).netloc, "snapshotPrefix": repo["snapshot"]["prefix"], "legacyReadUrl": repo["legacyGitMirror"]["readUrl"]}) open(sys.argv[2], "w", encoding="utf-8").write("\n".join(script) + "\n") json.dump(meta, open(sys.argv[3], "w", encoding="utf-8"), ensure_ascii=False, indent=2) PY github_basic="$(python3 - <<'PY' import base64, os raw = f"x-access-token:{os.environ['UNIDESK_GITEA_GITHUB_TOKEN']}".encode() print(base64.b64encode(raw).decode()) PY )" GITHUB_AUTH_HEADER="Authorization: Basic $github_basic" GITEA_AUTH_HEADER="Authorization: Basic $basic" export GITHUB_AUTH_HEADER GITEA_AUTH_HEADER unset GIT_SSH GIT_SSH_COMMAND SSH_AUTH_SOCK export GIT_TERMINAL_PROMPT=0 export GIT_CONFIG_NOSYSTEM=1 export GIT_CONFIG_GLOBAL=/dev/null if [ "$UNIDESK_GITEA_GITHUB_PROXY_ENABLED" = "1" ]; then export https_proxy="$UNIDESK_GITEA_GITHUB_PROXY_URL" export http_proxy="$UNIDESK_GITEA_GITHUB_PROXY_URL" export no_proxy="$UNIDESK_GITEA_NO_PROXY" fi sh "$tmp/sync-plan.sh" >"$tmp/sync.out" 2>"$tmp/sync.err" sync_rc=$? python3 - "$sync_rc" "$tmp/repo-meta.json" "$tmp" <<'PY' import json, os, sys sync_rc = int(sys.argv[1]) meta = json.load(open(sys.argv[2], encoding="utf-8")) tmp = sys.argv[3] def text(path, limit=1200): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" def rc(path): try: return int(open(path, encoding="utf-8").read() or "1") except Exception: return 1 repos = [] bundles = [] for repo in meta: bundle_path = os.path.join(tmp, f"{repo['key']}.bundle") source_commit = None snapshot_ref = None try: parts = open(bundle_path, encoding="utf-8").read().strip().split() source_commit, snapshot_ref = parts[0], parts[1] except Exception: pass repos.append({ **repo, "sourceCommit": source_commit, "snapshotRef": snapshot_ref, "syncOk": source_commit is not None and snapshot_ref is not None and rc(os.path.join(tmp, f"{repo['key']}.gitops-push.rc")) == 0, "initRc": rc(os.path.join(tmp, f"{repo['key']}.init.rc")), "fetchRc": rc(os.path.join(tmp, f"{repo['key']}.fetch.rc")), "revparseRc": rc(os.path.join(tmp, f"{repo['key']}.revparse.rc")), "pushRc": rc(os.path.join(tmp, f"{repo['key']}.push.rc")), "gitopsPushRc": rc(os.path.join(tmp, f"{repo['key']}.gitops-push.rc")), "fetchStdoutTail": text(os.path.join(tmp, f"{repo['key']}.fetch.out")), "fetchTail": text(os.path.join(tmp, f"{repo['key']}.fetch.err")), "pushStdoutTail": text(os.path.join(tmp, f"{repo['key']}.push.out")), "pushTail": text(os.path.join(tmp, f"{repo['key']}.push.err")), "revparseTail": text(os.path.join(tmp, f"{repo['key']}.revparse.err")), }) if source_commit and snapshot_ref: bundles.append({"key": repo["key"], "sourceCommit": source_commit, "snapshotRef": snapshot_ref, "readUrl": repo["readUrl"], "sourceAuthority": "gitea"}) payload = {"ok": sync_rc == 0 and all(r["syncOk"] for r in repos), "repositories": repos, "sourceBundles": bundles, "valuesPrinted": False} print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY } run_mirror_status() { mirror_repos_json base_url="$(service_base_url)" basic="$(admin_basic_b64)" python3 - "$tmp/repos.json" "$tmp/status-plan.sh" "$tmp/repo-meta.json" "$base_url" <<'PY' import json, shlex, sys, urllib.parse repos = json.load(open(sys.argv[1], encoding="utf-8")) base_url = sys.argv[4].rstrip("/") script = [] meta = [] for repo in repos: key = repo["key"] branch = repo["upstream"]["branch"] gitops = repo["gitops"]["branch"] gitea_probe_url = base_url + urllib.parse.urlparse(repo["gitea"]["readUrl"]).path script.append(f"git -c http.extraHeader=\"$GITEA_AUTH_HEADER\" ls-remote {shlex.quote(gitea_probe_url)} refs/heads/{shlex.quote(branch)} '{shlex.quote(repo['snapshot']['prefix'])}/*' refs/heads/{shlex.quote(gitops)} >$tmp/{key}.ls.out 2>$tmp/{key}.ls.err") meta.append({"key": key, "branch": branch, "gitopsBranch": gitops, "readUrl": repo["gitea"]["readUrl"], "probeUrlHost": urllib.parse.urlparse(gitea_probe_url).netloc, "snapshotPrefix": repo["snapshot"]["prefix"], "legacyReadUrl": repo["legacyGitMirror"]["readUrl"], "legacyDisposition": repo["legacyGitMirror"]["disposition"]}) open(sys.argv[2], "w", encoding="utf-8").write("\n".join(script) + "\n") json.dump(meta, open(sys.argv[3], "w", encoding="utf-8"), ensure_ascii=False) PY GITEA_AUTH_HEADER="Authorization: Basic $basic" export GITEA_AUTH_HEADER sh "$tmp/status-plan.sh" >"$tmp/status.out" 2>"$tmp/status.err" status_rc=$? python3 - "$status_rc" "$tmp/repo-meta.json" "$tmp" <<'PY' import json, os, sys sys.path.insert(0, sys.argv[3]) from platform_infra_gitea_status_evaluator import select_snapshot status_rc = int(sys.argv[1]) meta = json.load(open(sys.argv[2], encoding="utf-8")) tmp = sys.argv[3] def text(path, limit=1200): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" repos = [] for repo in meta: out = text(os.path.join(tmp, f"{repo['key']}.ls.out"), 20000) refs = {} for line in out.splitlines(): parts = line.split() if len(parts) == 2: refs[parts[1]] = parts[0] branch_ref = f"refs/heads/{repo['branch']}" branch_commit = refs.get(branch_ref) selected_snapshot = select_snapshot(branch_commit, repo["snapshotPrefix"], refs) current_snapshot_ready = selected_snapshot["sourceBundleReady"] repos.append({ **repo, "branchCommit": branch_commit, "expectedSnapshotRef": selected_snapshot["expectedSnapshotRef"], "snapshotRef": selected_snapshot["snapshotRef"], "snapshotCommit": selected_snapshot["snapshotCommit"], "sourceBundleReady": current_snapshot_ready, "mirrorState": "ready" if current_snapshot_ready else "missing-ref", "errorTail": text(os.path.join(tmp, f"{repo['key']}.ls.err")), }) payload = {"ok": status_rc == 0 and all(r["sourceBundleReady"] for r in repos), "repositories": repos, "valuesPrinted": False} print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY } github_api_env() { if [ "$UNIDESK_GITEA_GITHUB_PROXY_ENABLED" = "1" ]; then export https_proxy="$UNIDESK_GITEA_GITHUB_PROXY_URL" export http_proxy="$UNIDESK_GITEA_GITHUB_PROXY_URL" export no_proxy="$UNIDESK_GITEA_NO_PROXY" fi } run_mirror_webhook_apply() { mirror_repos_json github_api_env python3 - "$tmp/repos.json" <<'PY' import json, os, sys, urllib.error, urllib.request repos = json.load(open(sys.argv[1], encoding="utf-8")) token = os.environ["UNIDESK_GITEA_GITHUB_TOKEN"] url = os.environ["UNIDESK_GITEA_WEBHOOK_PUBLIC_URL"] secret = os.environ["UNIDESK_GITEA_GITHUB_WEBHOOK_SECRET"] events = [item for item in os.environ.get("UNIDESK_GITEA_WEBHOOK_EVENTS", "push").split(",") if item] def request(method, api_path, payload=None, expected=(200, 201, 204), tolerate=()): data = None if payload is None else json.dumps(payload).encode() req = urllib.request.Request("https://api.github.com" + api_path, data=data, method=method) req.add_header("Authorization", "Bearer " + token) req.add_header("Accept", "application/vnd.github+json") req.add_header("X-GitHub-Api-Version", "2022-11-28") if payload is not None: req.add_header("Content-Type", "application/json") try: with urllib.request.urlopen(req, timeout=30) as resp: body = resp.read().decode("utf-8", errors="replace") return {"ok": resp.status in expected, "status": resp.status, "body": body} except urllib.error.HTTPError as exc: body = exc.read().decode("utf-8", errors="replace") return {"ok": exc.code in tolerate, "status": exc.code, "body": body[:1000]} rows = [] for repo in repos: repository = repo["upstream"]["repository"] list_result = request("GET", f"/repos/{repository}/hooks") hooks = [] try: hooks = json.loads(list_result.get("body") or "[]") except Exception: hooks = [] matches = [item for item in hooks if (item.get("config") or {}).get("url") == url] body = {"name": "web", "active": True, "events": events, "config": {"url": url, "content_type": "json", "secret": secret, "insecure_ssl": "0"}} if matches: hook_id = matches[0].get("id") result = request("PATCH", f"/repos/{repository}/hooks/{hook_id}", body) for duplicate in matches[1:]: duplicate_id = duplicate.get("id") if duplicate_id: request("DELETE", f"/repos/{repository}/hooks/{duplicate_id}", expected=(204,), tolerate=(404,)) else: result = request("POST", f"/repos/{repository}/hooks", body, expected=(201,)) try: hook_id = json.loads(result.get("body") or "{}").get("id") except Exception: hook_id = None rows.append({"key": repo["key"], "repository": repository, "hookId": hook_id, "hookReady": result.get("ok") is True, "status": result.get("status"), "error": None if result.get("ok") else result.get("body", "")[:500], "valuesPrinted": False}) payload = {"ok": all(row["hookReady"] for row in rows), "repositories": rows, "url": url, "valuesPrinted": False} print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY } run_mirror_webhook_status() { mirror_repos_json github_api_env bridge_ready=$(kubectl -n "$UNIDESK_GITEA_NAMESPACE" get deploy "$UNIDESK_GITEA_WEBHOOK_DEPLOYMENT" -o jsonpath='{.status.readyReplicas}/{.spec.replicas}' 2>/dev/null || echo "0/0") service_exists=$(kubectl -n "$UNIDESK_GITEA_NAMESPACE" get service "$UNIDESK_GITEA_WEBHOOK_SERVICE" -o name 2>/dev/null || true) run_mirror_status >"$tmp/mirror-status.json" 2>"$tmp/mirror-status.err" mirror_status_rc=$? kubectl -n "$UNIDESK_GITEA_NAMESPACE" logs "deployment/$UNIDESK_GITEA_WEBHOOK_DEPLOYMENT" --tail=80 >"$tmp/bridge.log" 2>"$tmp/bridge.err" bridge_logs_rc=$? kubectl -n "$UNIDESK_GITEA_NAMESPACE" exec -i "deployment/$UNIDESK_GITEA_WEBHOOK_DEPLOYMENT" -- node - "$UNIDESK_GITEA_WEBHOOK_SERVICE_PORT" >"$tmp/inbox-status.json" 2>"$tmp/inbox-status.err" <<'NODE' const port = process.argv[2]; fetch(`http://127.0.0.1:${port}/status`) .then(async (response) => process.stdout.write(await response.text())) .catch((error) => { process.stderr.write(String(error?.message || error)); process.exitCode = 1; }); NODE inbox_status_rc=$? python3 - "$tmp/repos.json" "$bridge_ready" "$service_exists" "$mirror_status_rc" "$bridge_logs_rc" "$inbox_status_rc" "$tmp/mirror-status.json" "$tmp/mirror-status.err" "$tmp/bridge.log" "$tmp/bridge.err" "$tmp/inbox-status.json" "$tmp/inbox-status.err" <<'PY' import json, os, sys, urllib.error, urllib.parse, urllib.request sys.path.insert(0, os.environ["tmp"]) from platform_infra_gitea_status_evaluator import evaluate_repository repos = json.load(open(sys.argv[1], encoding="utf-8")) bridge_ready = sys.argv[2] service_exists = bool(sys.argv[3]) mirror_status_rc = int(sys.argv[4]) bridge_logs_rc = int(sys.argv[5]) inbox_status_rc = int(sys.argv[6]) mirror_status_path = sys.argv[7] mirror_status_err_path = sys.argv[8] bridge_log_path = sys.argv[9] bridge_log_err_path = sys.argv[10] inbox_status_path = sys.argv[11] inbox_status_err_path = sys.argv[12] token = os.environ["UNIDESK_GITEA_GITHUB_TOKEN"] url = os.environ["UNIDESK_GITEA_WEBHOOK_PUBLIC_URL"] def text(path, limit=1600): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] except FileNotFoundError: return "" def request(api_path): req = urllib.request.Request("https://api.github.com" + api_path, method="GET") req.add_header("Authorization", "Bearer " + token) req.add_header("Accept", "application/vnd.github+json") req.add_header("X-GitHub-Api-Version", "2022-11-28") try: with urllib.request.urlopen(req, timeout=30) as resp: return {"ok": True, "status": resp.status, "body": resp.read().decode("utf-8", errors="replace")} except urllib.error.HTTPError as exc: return {"ok": False, "status": exc.code, "body": exc.read().decode("utf-8", errors="replace")[:1000]} def github_head(repository, branch): result = request(f"/repos/{repository}/git/ref/heads/{urllib.parse.quote(branch, safe='')}") try: payload = json.loads(result.get("body") or "{}") except Exception: payload = {} return {"ok": result.get("ok"), "status": result.get("status"), "sha": payload.get("object", {}).get("sha"), "error": None if result.get("ok") else result.get("body", "")[:300]} def hook_deliveries(repository, hook_id): if not hook_id: return {"ok": False, "status": None, "latest": None, "latestPush": None, "error": "hook not found"} result = request(f"/repos/{repository}/hooks/{hook_id}/deliveries?per_page=8") try: rows = json.loads(result.get("body") or "[]") except Exception: rows = [] def compact(item): if not isinstance(item, dict): return None return { "id": str(item.get("id")) if item.get("id") is not None else None, "guid": str(item.get("guid")) if item.get("guid") is not None else None, "deliveryId": str(item.get("guid") or item.get("id") or ""), "event": item.get("event"), "status": item.get("status"), "statusCode": item.get("status_code"), "deliveredAt": item.get("delivered_at"), "duration": item.get("duration"), } latest = compact(rows[0]) if rows else None latest_push = next((compact(item) for item in rows if isinstance(item, dict) and item.get("event") == "push"), None) return {"ok": result.get("ok"), "status": result.get("status"), "latest": latest, "latestPush": latest_push, "error": None if result.get("ok") else result.get("body", "")[:300]} try: mirror_status = json.load(open(mirror_status_path, encoding="utf-8")) except Exception: mirror_status = {} mirror_by_key = {item.get("key"): item for item in mirror_status.get("repositories", []) if isinstance(item, dict)} try: inbox_status = json.load(open(inbox_status_path, encoding="utf-8")) except Exception: inbox_status = {} inbox_records = [item for item in inbox_status.get("deliveries", []) if isinstance(item, dict)] bridge_events = [] for line in text(bridge_log_path, 12000).splitlines(): try: event = json.loads(line) except Exception: continue if isinstance(event, dict) and str(event.get("event", "")).startswith("github-to-gitea"): bridge_events.append({ "event": event.get("event"), "repo": event.get("repo"), "deliveryId": event.get("deliveryId"), "repository": event.get("repository"), "ref": event.get("ref"), "requestedCommit": event.get("requestedCommit"), "ok": event.get("ok"), "sourceCommit": event.get("sourceCommit"), "authorityCommit": event.get("authorityCommit"), "snapshotRef": event.get("snapshotRef"), "disposition": event.get("disposition"), "errorType": event.get("errorType"), "error": event.get("error"), "syncAttempt": event.get("syncAttempt"), "terminal": event.get("terminal"), "elapsedMs": event.get("elapsedMs"), }) rows = [] for repo in repos: repository = repo["upstream"]["repository"] branch = repo["upstream"]["branch"] result = request(f"/repos/{repository}/hooks") hooks = [] try: hooks = json.loads(result.get("body") or "[]") except Exception: hooks = [] match = next((item for item in hooks if (item.get("config") or {}).get("url") == url), None) hook_id = match.get("id") if match else None head = github_head(repository, branch) delivery = hook_deliveries(repository, hook_id) mirror = mirror_by_key.get(repo["key"], {}) latest_push_delivery = delivery.get("latestPush") hook = { "hookId": hook_id, "hookReady": bool(match and match.get("active") is True), "active": match.get("active") if match else None, "status": result.get("status"), "latestDelivery": delivery.get("latest"), "latestPushDelivery": latest_push_delivery, "latest": delivery.get("latest"), "latestPush": delivery.get("latestPush"), "error": result.get("body", "")[:500] if not result.get("ok") else delivery.get("error"), } rows.append(evaluate_repository(repo, hook, head, mirror, bridge_events, inbox_records, inbox_status.get("journal"))) bridge = { "deployment": os.environ["UNIDESK_GITEA_WEBHOOK_DEPLOYMENT"], "ready": bridge_ready not in ("0/0", "/") and bridge_ready.split("/")[0] == bridge_ready.split("/")[-1], "readyReplicas": bridge_ready, "serviceExists": service_exists, "durableInbox": inbox_status, } payload = { "ok": bridge["ready"] and service_exists and mirror_status_rc == 0 and inbox_status_rc == 0 and inbox_status.get("ready") is True and all(row["hookReady"] and row["authorityMatches"] and not row["bridgeEventStale"] for row in rows), "bridge": bridge, "repositories": rows, "url": url, "mirrorStatus": {"exitCode": mirror_status_rc, "errorTail": text(mirror_status_err_path)}, "bridgeLogs": {"exitCode": bridge_logs_rc, "events": bridge_events[-12:], "errorTail": text(bridge_log_err_path)}, "inboxStatus": {"exitCode": inbox_status_rc, "errorTail": text(inbox_status_err_path)}, "valuesPrinted": False, } print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if payload["ok"] else 1) PY } case "$UNIDESK_GITEA_ACTION" in apply) run_apply ;; status) run_status ;; validate) run_validate ;; mirror-bootstrap) run_mirror_bootstrap ;; mirror-sync) run_mirror_sync ;; mirror-status) run_mirror_status ;; mirror-webhook-apply) run_mirror_webhook_apply ;; mirror-webhook-status) run_mirror_webhook_status ;; *) printf '{"ok":false,"error":"unsupported-gitea-remote-action"}\n'; exit 2 ;; esac