Automate Gitea init, NocoDB auto sign-in, and fix prod compose

- Add scripts/gitea-init.sh: runs migrations + creates admin user on
  first boot, replacing the manual installation wizard
- Set GITEA__security__INSTALL_LOCK=true in both compose files
- Add NocoDB auth bridge (nginx) + /api/services/nocodb-auth proxy
  endpoint so the admin iframe auto-authenticates
- Update NocoDBPage.tsx to fetch token and use auth bridge flow
- Fix docker-compose.prod.yml missing Gitea env vars for API container
  (GITEA_URL, GITEA_API_TOKEN, GITEA_ADMIN_PASSWORD, etc.)
- Pass NC_ADMIN_EMAIL/PASSWORD to API for NocoDB auth proxy
- Increase Gitea auto-setup retries from 3 to 6 with admin auth check
- Update config.sh non-interactive mode to set GITEA_ADMIN_USER
- Include gitea-init.sh in release tarball (build-release.sh)

Bunker Admin
This commit is contained in:
2026-04-09 12:49:33 -06:00
parent 0a8e1fe46b
commit 36b709b911
11 changed files with 223 additions and 34 deletions

View File

@@ -96,6 +96,8 @@ const envSchema = z.object({
// Platform Services (NocoDB, n8n, Gitea)
NOCODB_URL: z.string().default('http://changemaker-v2-nocodb:8080'),
NC_ADMIN_EMAIL: z.string().default(''),
NC_ADMIN_PASSWORD: z.string().default(''),
NOCODB_PORT: z.coerce.number().default(8091),
NOCODB_EMBED_PORT: z.coerce.number().default(8881),
N8N_URL: z.string().default('http://n8n-changemaker:5678'),

View File

@@ -393,17 +393,26 @@ async function autoSetupIfNeeded(): Promise<{ alreadyComplete: boolean; success:
// DB might not be ready yet
}
// Wait for Gitea to be available (up to 3 retries, 15s apart)
// 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,
// so we need to wait for both Gitea web AND admin auth to be ready.
let giteaReady = false;
for (let i = 0; i < 3; i++) {
for (let i = 0; i < 6; 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) {
giteaReady = true;
break;
// 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);
@@ -411,14 +420,14 @@ async function autoSetupIfNeeded(): Promise<{ alreadyComplete: boolean; success:
} catch {
// Not ready yet
}
if (i < 2) {
logger.info(`Gitea auto-setup: waiting for Gitea to be ready (attempt ${i + 1}/3)...`);
await new Promise(r => setTimeout(r, 15000));
if (i < 5) {
logger.info(`Gitea auto-setup: waiting for Gitea + admin user (attempt ${i + 1}/6)...`);
await new Promise(r => setTimeout(r, 10000));
}
}
if (!giteaReady) {
return { alreadyComplete: false, success: false, error: 'Gitea not reachable after 3 attempts' };
return { alreadyComplete: false, success: false, error: 'Gitea not reachable or admin user not ready after 6 attempts' };
}
// Run setup

View File

@@ -64,6 +64,48 @@ router.get(
},
);
// GET /api/services/nocodb-auth — proxy NocoDB signin to get an auth token for iframe auto-login
router.get(
'/nocodb-auth',
async (_req: Request, res: Response, next: NextFunction) => {
try {
if (!env.NC_ADMIN_EMAIL || !env.NC_ADMIN_PASSWORD) {
res.status(503).json({ error: 'NocoDB admin credentials not configured' });
return;
}
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(`${env.NOCODB_URL}/api/v1/auth/user/signin`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: env.NC_ADMIN_EMAIL, password: env.NC_ADMIN_PASSWORD }),
signal: controller.signal,
});
if (!response.ok) {
res.status(502).json({ error: 'NocoDB authentication failed' });
return;
}
const data = (await response.json()) as { token?: string };
if (!data.token) {
res.status(502).json({ error: 'No token in NocoDB response' });
return;
}
res.json({ token: data.token });
} finally {
clearTimeout(timeout);
}
} catch (err) {
logger.error('NocoDB auth proxy failed', err);
next(err);
}
},
);
// GET /api/services/config — return public-facing port numbers + subdomain info for iframe URLs
router.get(
'/config',