From ffc9e125264062f2d8e67d60fb7cfa191d278112 Mon Sep 17 00:00:00 2001 From: Lars Rossen Date: Mon, 24 Aug 2026 20:59:55 +0200 Subject: [PATCH] portainer: reopen Portainer's admin-init window when it has closed Portainer stops accepting POST /api/users/admin/init a few minutes after start and answers 303 with Redirect-Reason: AdminInitTimeout. Any transient failure during that window therefore made the module permanently un-bootstrappable. update.sh now restarts the container to reset the timer, waits for the API and retries once, re-reading the setup token because a fresh one is minted at every start. Also splits the OIDC precondition: the single condition reported the secrets env as missing whenever the admin session was absent, which is what sent the first debugging pass down the wrong path. --- src/containers/portainer/update.sh | 48 ++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/containers/portainer/update.sh b/src/containers/portainer/update.sh index 39b30e2..b0a9203 100755 --- a/src/containers/portainer/update.sh +++ b/src/containers/portainer/update.sh @@ -142,18 +142,40 @@ portainer_jwt() { -d '{\"Username\":\"admin\",\"Password\":\"${ADMIN_PW}\"}'" 2>/dev/null | jq -r '.jwt // empty' } +wait_for_api() { + local code + for _ in $(seq 1 24); do + code="$(vm "curl -fsk -o /dev/null -w '%{http_code}' https://localhost:9443/api/status 2>/dev/null" || echo 000)" + [[ "${code}" == "200" ]] && return 0 + sleep 5 + done + return 1 +} + +# Echoes the HTTP status of an admin-init attempt. The setup token is minted +# afresh at every container start, so re-read it each time. +init_admin() { + local tok hdr="" + tok="$(vm "sudo podman logs ${CONTAINER} 2>&1 | grep setup_token | grep -oE '[0-9a-f]{64}' | tail -1" | tr -d '\r')" + [[ -n "${tok}" ]] && hdr="-H 'X-Setup-Token: ${tok}'" + vm "curl -sk -o /dev/null -w '%{http_code}' -X POST https://localhost:9443/api/users/admin/init \ + -H 'Content-Type: application/json' ${hdr} \ + -d '{\"Username\":\"admin\",\"Password\":\"${ADMIN_PW}\"}'" 2>/dev/null || echo 000 +} + JWT="$(portainer_jwt)" if [[ -z "${JWT}" ]]; then info " Bootstrapping the break-glass admin account..." - # Portainer >= 2.39 prints a one-time setup token at startup and refuses - # admin creation without it (HTTP 403). The log line wraps the value in ANSI - # colour codes, so match the 64-hex token rather than "setup_token=". - SETUP_TOKEN="$(vm "sudo podman logs ${CONTAINER} 2>&1 | grep setup_token | grep -oE '[0-9a-f]{64}' | tail -1" | tr -d '\r')" - TOKEN_HDR="" - [[ -n "${SETUP_TOKEN}" ]] && TOKEN_HDR="-H 'X-Setup-Token: ${SETUP_TOKEN}'" - init_code="$(vm "curl -sk -o /dev/null -w '%{http_code}' -X POST https://localhost:9443/api/users/admin/init \ - -H 'Content-Type: application/json' ${TOKEN_HDR} \ - -d '{\"Username\":\"admin\",\"Password\":\"${ADMIN_PW}\"}'" || echo 000)" + init_code="$(init_admin)" + if [[ "${init_code}" == "303" ]]; then + # Portainer closes the admin-init window a few minutes after start + # (Redirect-Reason: AdminInitTimeout). Without this the module can never + # be bootstrapped again after any transient failure — restarting resets + # the timer and mints a new setup token. + info " admin-init window had closed — restarting ${CONTAINER} to reopen it" + vm "sudo podman restart ${CONTAINER} >/dev/null" || warn " restart failed" + wait_for_api && init_code="$(init_admin)" + fi case "${init_code}" in 200|204) info " admin created (password in ${ADMIN_SECRET} on the VM)" ;; 409) warn " an admin exists but ${ADMIN_SECRET} does not match it — reset it by hand" ;; @@ -167,7 +189,11 @@ fi # ── 5. Point Portainer at TAPPaaS identity (OIDC) ──────────────────── # identity:identity wrote these three values; if they are missing the module is # still usable with the local admin, so warn rather than fail. -if [[ -n "${JWT:-}" ]] && vm "sudo test -s ${SECRETS_ENV}" 2>/dev/null; then +if [[ -z "${JWT:-}" ]]; then + warn " no admin session — skipping OIDC configuration (see the admin init warning above)" +elif ! vm "sudo test -s ${SECRETS_ENV}" 2>/dev/null; then + warn " ${SECRETS_ENV} missing — is identity:identity in dependsOn?" +else info " Configuring OIDC login against Authentik..." CLIENT_ID="$(vm "sudo sh -c '. ${SECRETS_ENV}; printf %s \"\$OIDC_CLIENT_ID\"'" | tr -d '\r')" CLIENT_SECRET="$(vm "sudo sh -c '. ${SECRETS_ENV}; printf %s \"\$OIDC_CLIENT_SECRET\"'" | tr -d '\r')" @@ -213,8 +239,6 @@ if [[ -n "${JWT:-}" ]] && vm "sudo test -s ${SECRETS_ENV}" 2>/dev/null; then else warn " ${SECRETS_ENV} did not carry all three OIDC values — skipping OIDC configuration" fi -else - warn " ${SECRETS_ENV} missing — is identity:identity in dependsOn? Local admin login still works." fi echo ""