diff --git a/api/src/modules/gitea-setup/gitea-setup.service.ts b/api/src/modules/gitea-setup/gitea-setup.service.ts index c22b6e1e..5307d56c 100644 --- a/api/src/modules/gitea-setup/gitea-setup.service.ts +++ b/api/src/modules/gitea-setup/gitea-setup.service.ts @@ -393,41 +393,29 @@ async function autoSetupIfNeeded(): Promise<{ alreadyComplete: boolean; success: // DB might not be ready yet } - // Wait for Gitea to be available and admin user to exist (up to 6 retries, 10s apart). - // The gitea-init.sh script creates the admin user after migrations, + // Wait for Gitea to be available and admin user to exist (up to 8 retries, 10s apart). + // The gitea-init container creates the admin user after gitea-app is healthy, // so we need to wait for both Gitea web AND admin auth to be ready. + // Note: REQUIRE_SIGNIN_VIEW=true blocks unauthenticated /version, so we use + // Basic Auth for the readiness check directly. let giteaReady = false; - for (let i = 0; i < 6; i++) { + for (let i = 0; i < 8; i++) { try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 5000); - try { - // Check if Gitea is online - const res = await fetch(`${env.GITEA_URL}/api/v1/version`, { signal: controller.signal }); - if (res.ok) { - // Also verify admin auth works (user may not exist yet if init script is still running) - try { - await giteaBasicRequest<{ login: string }>('GET', '/user', 'admin', password); - giteaReady = true; - break; - } catch { - // Admin user not ready yet — gitea-init.sh may still be running - } - } - } finally { - clearTimeout(timeout); - } + // Check if Gitea is online AND admin auth works in one shot + await giteaBasicRequest<{ login: string }>('GET', '/user', 'admin', password); + giteaReady = true; + break; } catch { - // Not ready yet + // Not ready yet — Gitea may not be up, or admin user may not exist yet } - if (i < 5) { - logger.info(`Gitea auto-setup: waiting for Gitea + admin user (attempt ${i + 1}/6)...`); + if (i < 7) { + logger.info(`Gitea auto-setup: waiting for Gitea + admin user (attempt ${i + 1}/8)...`); await new Promise(r => setTimeout(r, 10000)); } } if (!giteaReady) { - return { alreadyComplete: false, success: false, error: 'Gitea not reachable or admin user not ready after 6 attempts' }; + return { alreadyComplete: false, success: false, error: 'Gitea not reachable or admin user not ready after 8 attempts' }; } // Run setup diff --git a/scripts/gitea-init.sh b/scripts/gitea-init.sh index 2d7a0ffd..879a16b2 100755 --- a/scripts/gitea-init.sh +++ b/scripts/gitea-init.sh @@ -14,6 +14,12 @@ log() { echo "$PREFIX $1"; } # The gitea binary needs app.ini to know the database connection. # On the Gitea Docker image, GITEA_CUSTOM defaults to /data/gitea # and app.ini lives at /data/gitea/conf/app.ini (created by gitea-app's entrypoint). +# Gitea refuses to run as root. The init container bypasses the Gitea entrypoint, +# so we must drop privileges ourselves. Re-exec as 'git' user via su-exec. +if [ "$(id -u)" = "0" ]; then + exec su-exec git "$0" "$@" +fi + export GITEA_CUSTOM="${GITEA_CUSTOM:-/data/gitea}" if [ ! -f "$GITEA_CUSTOM/conf/app.ini" ]; then @@ -35,7 +41,7 @@ if gitea admin user create --admin \ --username "$GITEA_ADMIN_USER" \ --password "$GITEA_ADMIN_PASSWORD" \ --email "$GITEA_ADMIN_EMAIL" \ - --must-change-password false 2>&1; then + --must-change-password=false 2>&1; then log "Admin user created successfully" else log "Admin user already exists (or creation skipped)"