From a56f8446f7ee75c257fc14af09c3211216c515d1 Mon Sep 17 00:00:00 2001 From: bunker-admin Date: Mon, 23 Mar 2026 15:47:57 -0600 Subject: [PATCH] Fix Pangolin setup: root domain support + disable SSO auth on resources - Omit subdomain field for root domain resources (Pangolin rejects empty string but accepts absent field) - Set sso:false + blockAccess:false after resource creation so resources are publicly accessible without Pangolin auth redirects - Make subdomain optional in CreateHttpResourcePayload type - Applied to both /setup and /sync endpoints Bunker Admin --- api/src/modules/pangolin/pangolin.routes.ts | 12 +++++++----- api/src/services/pangolin.client.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/api/src/modules/pangolin/pangolin.routes.ts b/api/src/modules/pangolin/pangolin.routes.ts index eb691b2b..cceee5d8 100644 --- a/api/src/modules/pangolin/pangolin.routes.ts +++ b/api/src/modules/pangolin/pangolin.routes.ts @@ -571,10 +571,11 @@ router.post('/setup', pangolinSetupLimiter, async (req: Request, res: Response) try { // Step 4a: Create HTTP resource + // Root domain: omit subdomain field entirely (Pangolin rejects empty string but accepts absent field) const resourcePayload: CreateHttpResourcePayload = { name: def.name, domainId: matchingDomain.domainId, - subdomain: def.subdomain || '', + ...(def.subdomain ? { subdomain: def.subdomain } : {}), http: true, protocol: 'tcp', }; @@ -583,10 +584,10 @@ router.post('/setup', pangolinSetupLimiter, async (req: Request, res: Response) const resourceId = resource.resourceId; logger.info(`Created resource: ${fullDomain} (ID: ${resourceId})`); - // Make resource publicly accessible + // Make resource publicly accessible (no SSO auth, no access block) try { - await pangolinClient.updateResource(resourceId, { blockAccess: false }); - logger.info(`Set ${fullDomain} as publicly accessible`); + await pangolinClient.updateResource(resourceId, { sso: false, blockAccess: false }); + logger.info(`Set ${fullDomain} as public (no auth)`); } catch (updateErr) { logger.warn(`Created ${fullDomain} but failed to set public access:`, updateErr); } @@ -814,10 +815,11 @@ router.post('/sync', pangolinSetupLimiter, async (_req: Request, res: Response) } else { // Create new resource + target try { + // Root domain: omit subdomain field entirely (Pangolin rejects empty string) const resource = await pangolinClient.createResource({ name: def.name, domainId: matchingDomain.domainId, - subdomain: def.subdomain || '', + ...(def.subdomain ? { subdomain: def.subdomain } : {}), http: true, protocol: 'tcp', }); diff --git a/api/src/services/pangolin.client.ts b/api/src/services/pangolin.client.ts index c6f805a2..b35655ad 100644 --- a/api/src/services/pangolin.client.ts +++ b/api/src/services/pangolin.client.ts @@ -82,7 +82,7 @@ export interface CreateSitePayload { export interface CreateHttpResourcePayload { name: string; domainId: string; - subdomain: string; // Subdomain only (e.g., "app") or empty string for root + subdomain?: string; // Subdomain only (e.g., "app"); omit entirely for root domain http: true; // REQUIRED: marks as HTTP proxy resource protocol: 'tcp'; // REQUIRED for HTTP resources // Note: ssl and enabled are NOT valid creation fields — set via updateResource()