From dbe249ce9fdac22f7f568b364222033d9e3e4315 Mon Sep 17 00:00:00 2001 From: chrisfu Date: Sat, 9 May 2026 18:53:55 -0700 Subject: [PATCH] fix(gitea): REST API bootstrap path bypasses broken admin CLI; fix DB namespace resolver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deploy.sh: resolve_knoe_db_namespace() now checks DATABASE_NAMESPACE from config (k3s.cfg: knoe-db) before probing namespaces. Previously it fell back to 'default' because only 'knoe' was probed, baking a wrong DB hostname into app.ini. The running server was unaffected (uses GITEA__database__HOST env var) but the gitea admin CLI, which reads app.ini directly, could not connect. init_knoe_users.sh: add gitea_helm_admin_token() which reads the Helm bootstrap admin password from the gitea-gitea k8s secret and exchanges it for a REST API token — entirely avoiding the broken CLI. Add gitea_api_set_password() which uses PATCH /api/v1/admin/users/{user} to set the password via the running web server (which has the correct DB connection) instead of kubectl exec. promote_gitea_admin() now prefers the REST-API-only path: 1. existing token (env / k8s secret) 2. Helm admin k8s secret → REST token; set KNOE_ADMIN_PRINCIPAL password via API 3. 1Password → CLI set-password → basic-auth token (fallback) 4. kubectl exec generate-access-token (last resort) Co-Authored-By: Claude Sonnet 4.6 --- etc/init_knoe_users.sh | 109 ++++++++++++++++++++++++++++++++++------- gitea/deploy.sh | 21 ++++++-- 2 files changed, 106 insertions(+), 24 deletions(-) diff --git a/etc/init_knoe_users.sh b/etc/init_knoe_users.sh index c7d61ad..5dda788 100755 --- a/etc/init_knoe_users.sh +++ b/etc/init_knoe_users.sh @@ -897,6 +897,57 @@ gitea_api_token() { printf '%s' "$resp" | grep -o '"sha1":"[^"]*"' | cut -d'"' -f4 } +# gitea_helm_admin_token [token_name] +# Reads the Helm bootstrap admin password from the k8s secret created by the +# Gitea Helm chart ({release}-gitea, key admin-password) and exchanges it for +# a REST API token. This works even when the gitea admin CLI is broken (e.g. +# wrong DB hostname in app.ini) because the request goes through the running +# web server which has the correct GITEA__database__HOST env var. +# Prints the token sha1 to stdout; returns 1 on failure. +gitea_helm_admin_token() { + local token_name="${1:-knoe-installer-helm}" + local secret="${GITEA_HELM_RELEASE:-gitea}-gitea" + local admin_user="${GITEA_HELM_ADMIN_USER:-gitea_admin}" + + local admin_pw + admin_pw=$(kubectl -n "$GITEA_NAMESPACE" get secret "$secret" \ + -o jsonpath='{.data.admin-password}' 2>/dev/null | b64_decode || true) + if [[ -z "$admin_pw" ]]; then + warn " Gitea: Helm admin secret '${secret}' not found or empty in namespace ${GITEA_NAMESPACE}" + return 1 + fi + + local tok + tok=$(gitea_api_token "$admin_user" "$admin_pw" "$token_name") + if [[ -z "$tok" ]]; then + warn " Gitea: could not obtain API token for Helm admin '${admin_user}'" + return 1 + fi + log " Gitea: obtained bootstrap token from Helm admin '${admin_user}'" + printf '%s' "$tok" +} + +# gitea_api_set_password +# Changes a user's password via the Gitea admin REST API. +# Uses the running web server (not the CLI), so it works even when app.ini +# has a wrong database hostname (the server overrides via GITEA__ env vars). +gitea_api_set_password() { + local token="$1" username="$2" pw="$3" + local http_code + http_code=$(curl -s -o /dev/null -w '%{http_code}' \ + -X PATCH \ + -H "Authorization: token ${token}" \ + -H "Content-Type: application/json" \ + -d "{\"login_name\":\"${username}\",\"source_id\":0,\"password\":\"${pw}\"}" \ + "https://${GITEA_HOST}/api/v1/admin/users/${username}" 2>/dev/null) || true + if [[ "$http_code" =~ ^2 ]]; then + return 0 + else + warn " Gitea: admin API set-password returned HTTP ${http_code} for '${username}'" + return 1 + fi +} + promote_gitea_admin() { local token="$GITEA_ADMIN_TOKEN" @@ -906,27 +957,47 @@ promote_gitea_admin() { -o jsonpath='{.data.token}' 2>/dev/null | b64_decode || true) fi - # 2. Obtain token via 1Password-backed password for KNOE_ADMIN_PRINCIPAL. - # gitea_ensure_password idempotently creates the 1Password item if needed, - # then sets the password in Gitea and exchanges it for a short-lived token. - # The plaintext password is held only in a local variable and cleared below. + # 2. Bootstrap via the Helm admin account (k8s secret → REST API token). + # This path works even when the gitea admin CLI is broken (app.ini has wrong + # DB hostname) because REST calls go through the running server which has the + # correct GITEA__database__HOST env var. Once we have a bootstrap token, use + # it to also set KNOE_ADMIN_PRINCIPAL's password via API (from 1Password). + local _helm_token="" if [[ -z "$token" ]]; then - local _pw="" - if _pw=$(gitea_ensure_password "${KNOE_ADMIN_PRINCIPAL}"); then - log "Gitea: setting password for '${KNOE_ADMIN_PRINCIPAL}' from 1Password ..." - if gitea_set_password "${KNOE_ADMIN_PRINCIPAL}" "$_pw"; then - log "Gitea: password set; requesting API token ..." - token=$(gitea_api_token "${KNOE_ADMIN_PRINCIPAL}" "$_pw" "knoe-installer") - [[ -n "$token" ]] && log "Gitea: API token obtained for '${KNOE_ADMIN_PRINCIPAL}'" - else - warn "Gitea: failed to set password for '${KNOE_ADMIN_PRINCIPAL}'" + if _helm_token=$(gitea_helm_admin_token "knoe-installer-helm"); then + token="$_helm_token" + # Opportunistically set KNOE_ADMIN_PRINCIPAL's 1Password-backed password + # via REST so chrisfu can log in as fallback if SPNEGO is unavailable. + if command -v op >/dev/null 2>&1; then + local _pw="" + if _pw=$(gitea_ensure_password "${KNOE_ADMIN_PRINCIPAL}"); then + log "Gitea: setting password for '${KNOE_ADMIN_PRINCIPAL}' via admin REST API ..." + gitea_api_set_password "$_helm_token" "${KNOE_ADMIN_PRINCIPAL}" "$_pw" \ + && log "Gitea: password set for '${KNOE_ADMIN_PRINCIPAL}'" \ + || true + _pw="" + fi fi - _pw="" # clear plaintext from memory immediately fi fi - # 3. Fall back to Helm bootstrap admin via kubectl exec generate-access-token. - # Useful when KNOE_ADMIN_PRINCIPAL has not yet logged in and op is unavailable. + # 3. Try 1Password → set password via CLI → exchange for token. + # Kept as fallback for when the Helm admin secret is absent (e.g. non-Helm deploy). + if [[ -z "$token" ]]; then + local _pw="" + if _pw=$(gitea_ensure_password "${KNOE_ADMIN_PRINCIPAL}"); then + log "Gitea: setting password for '${KNOE_ADMIN_PRINCIPAL}' via kubectl exec ..." + if gitea_set_password "${KNOE_ADMIN_PRINCIPAL}" "$_pw"; then + token=$(gitea_api_token "${KNOE_ADMIN_PRINCIPAL}" "$_pw" "knoe-installer") + [[ -n "$token" ]] && log "Gitea: API token obtained for '${KNOE_ADMIN_PRINCIPAL}'" + else + warn "Gitea: CLI password change failed (DB host mismatch in app.ini?)" + fi + _pw="" + fi + fi + + # 4. Last resort: kubectl exec generate-access-token (CLI path, may fail if DB broken). if [[ -z "$token" ]]; then local _gitea_pod _gitea_pod=$(kubectl -n "$GITEA_NAMESPACE" get pods \ @@ -934,12 +1005,12 @@ promote_gitea_admin() { --field-selector=status.phase=Running \ -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) if [[ -n "$_gitea_pod" ]]; then - log "Gitea: attempting token generation via kubectl exec (Helm bootstrap admin) ..." + log "Gitea: attempting token generation via kubectl exec (last resort) ..." local _u for _u in "gitea_admin" "${KNOE_ADMIN_PRINCIPAL}"; do token=$(kubectl -n "$GITEA_NAMESPACE" exec "$_gitea_pod" -c gitea -- \ su git -s /bin/bash -c \ - "gitea admin user generate-access-token --username ${_u} --token-name knoe-installer --scopes 'write:admin,read:user' --raw 2>/dev/null" \ + "gitea admin user generate-access-token --username ${_u} --token-name knoe-installer --scopes write:admin,read:user --raw 2>/dev/null" \ 2>/dev/null | tail -1 || true) [[ -n "$token" ]] && { log "Gitea: token generated for user '${_u}'"; break; } done @@ -948,7 +1019,7 @@ promote_gitea_admin() { if [[ -z "$token" ]]; then warn "No Gitea admin token available — skipping admin promotion" - warn " Ensure 'op signin' is active on this host, then re-run." + warn " Ensure 'op signin' is active and the Helm admin secret exists, then re-run." warn " Or set GITEA_ADMIN_TOKEN and re-run." return 0 fi diff --git a/gitea/deploy.sh b/gitea/deploy.sh index 5eb9d95..fdfe30c 100755 --- a/gitea/deploy.sh +++ b/gitea/deploy.sh @@ -623,16 +623,27 @@ resolve_knoe_db_namespace() { return 0 fi + # Prefer DATABASE_NAMESPACE from config (k3s.cfg sets this to knoe-db) + if [[ -n "${DATABASE_NAMESPACE:-}" ]]; then + KNOE_DB_NAMESPACE="$DATABASE_NAMESPACE" + return 0 + fi + if [[ -n "${NAMESPACE:-}" ]]; then KNOE_DB_NAMESPACE="$NAMESPACE" return 0 fi - if kubectl get namespace knoe >/dev/null 2>&1; then - KNOE_DB_NAMESPACE="knoe" - else - KNOE_DB_NAMESPACE="default" - fi + # Probe known namespace patterns before falling back to 'default' + local _ns + for _ns in knoe-db knoe; do + if kubectl get namespace "$_ns" >/dev/null 2>&1; then + KNOE_DB_NAMESPACE="$_ns" + return 0 + fi + done + + KNOE_DB_NAMESPACE="default" } setup_knoe_db_for_gitea() {