From 6cab9a3c245bb5983cc8f06146f650a1b90a10f0 Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 24 Mar 2026 10:35:45 +0100 Subject: [PATCH] fix(infra): remove n8n and increase health check intervals to fix port exhaustion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mac Mini had 25k+ TIME_WAIT sockets exhausting the 16k ephemeral port range, blocking all outgoing TCP connections. Root cause: ~50 health checks at 30s intervals + n8n automation creating excessive short-lived connections. - Remove n8n service and volume (no longer needed) - Increase health check intervals: 30s → 120s (app services), 10s → 30s (infra) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/location/location-lookup.service.ts | 27 +++ .../apps/web/src/lib/i18n/locales/de.json | 8 +- .../apps/web/src/lib/i18n/locales/en.json | 8 +- .../apps/web/src/routes/(app)/+page.svelte | 17 +- .../web/src/routes/(app)/add/+page.svelte | 108 ++++++++++- .../apps/web/src/routes/(app)/+page.svelte | 6 + docker-compose.macmini.yml | 179 +++++++----------- .../src/auth/services/better-auth.service.ts | 7 +- .../src/common/guards/jwt-auth.guard.ts | 6 +- 9 files changed, 237 insertions(+), 129 deletions(-) diff --git a/apps/citycorners/apps/backend/src/location/location-lookup.service.ts b/apps/citycorners/apps/backend/src/location/location-lookup.service.ts index 37a22e267..74f459c3a 100644 --- a/apps/citycorners/apps/backend/src/location/location-lookup.service.ts +++ b/apps/citycorners/apps/backend/src/location/location-lookup.service.ts @@ -6,6 +6,7 @@ export interface LookupResult { description: string; address?: string; category?: string; + imageUrl?: string; sources: { url: string; title: string }[]; } @@ -82,11 +83,15 @@ export class LocationLookupService { // Build a description from the best snippet or extracted text const description = this.buildDescription(snippets, extractedTexts); + // Try to find an image URL from search results + const imageUrl = this.extractImageUrl(results); + return { name: query, description, address, category, + imageUrl, sources: results.slice(0, 5).map((r: any) => ({ url: r.url, title: r.title, @@ -140,6 +145,28 @@ export class LocationLookupService { return 'sight'; } + private extractImageUrl(results: any[]): string | undefined { + for (const result of results) { + // SearXNG results may include img_src or thumbnail + if (result.img_src && this.isValidImageUrl(result.img_src)) { + return result.img_src; + } + if (result.thumbnail && this.isValidImageUrl(result.thumbnail)) { + return result.thumbnail; + } + } + return undefined; + } + + private isValidImageUrl(url: string): boolean { + try { + const parsed = new URL(url); + return parsed.protocol === 'https:' && /\.(jpg|jpeg|png|webp)/i.test(parsed.pathname); + } catch { + return false; + } + } + private buildDescription(snippets: string[], extractedTexts: string[]): string { // Prefer extracted text (more detailed) if (extractedTexts.length > 0) { diff --git a/apps/citycorners/apps/web/src/lib/i18n/locales/de.json b/apps/citycorners/apps/web/src/lib/i18n/locales/de.json index 7e9f5b0f3..f78917c1e 100644 --- a/apps/citycorners/apps/web/src/lib/i18n/locales/de.json +++ b/apps/citycorners/apps/web/src/lib/i18n/locales/de.json @@ -96,7 +96,13 @@ "submit": "Ort einreichen", "submitting": "Wird eingereicht...", "loginRequired": "Melde dich an, um Orte hinzuzufügen.", - "error": "Fehler beim Einreichen. Bitte versuche es erneut." + "error": "Fehler beim Einreichen. Bitte versuche es erneut.", + "imageUrl": "Bild-URL (optional)", + "imageUrlPlaceholder": "https://example.com/bild.jpg", + "imagePreview": "Bildvorschau", + "imageLoadError": "Bild konnte nicht geladen werden.", + "geocoding": "Koordinaten werden ermittelt...", + "coordinatesFound": "Koordinaten gefunden" }, "offline": { "title": "Keine Verbindung", diff --git a/apps/citycorners/apps/web/src/lib/i18n/locales/en.json b/apps/citycorners/apps/web/src/lib/i18n/locales/en.json index 352b0a2a7..2ee6fead0 100644 --- a/apps/citycorners/apps/web/src/lib/i18n/locales/en.json +++ b/apps/citycorners/apps/web/src/lib/i18n/locales/en.json @@ -96,7 +96,13 @@ "submit": "Submit place", "submitting": "Submitting...", "loginRequired": "Sign in to add places.", - "error": "Failed to submit. Please try again." + "error": "Failed to submit. Please try again.", + "imageUrl": "Image URL (optional)", + "imageUrlPlaceholder": "https://example.com/image.jpg", + "imagePreview": "Image preview", + "imageLoadError": "Image could not be loaded.", + "geocoding": "Finding coordinates...", + "coordinatesFound": "Coordinates found" }, "offline": { "title": "No connection", diff --git a/apps/citycorners/apps/web/src/routes/(app)/+page.svelte b/apps/citycorners/apps/web/src/routes/(app)/+page.svelte index b86655c89..6695b0c71 100644 --- a/apps/citycorners/apps/web/src/routes/(app)/+page.svelte +++ b/apps/citycorners/apps/web/src/routes/(app)/+page.svelte @@ -53,9 +53,20 @@ {$_('app.name')} - {$_('app.tagline')} -
-

{$_('home.title')}

-

{$_('home.subtitle')}

+
+
+

{$_('home.title')}

+

{$_('home.subtitle')}

+
+ + + + +
diff --git a/apps/citycorners/apps/web/src/routes/(app)/add/+page.svelte b/apps/citycorners/apps/web/src/routes/(app)/add/+page.svelte index 381e700e1..feb9d811f 100644 --- a/apps/citycorners/apps/web/src/routes/(app)/add/+page.svelte +++ b/apps/citycorners/apps/web/src/routes/(app)/add/+page.svelte @@ -15,8 +15,13 @@ let category = $state('sight'); let description = $state(''); let address = $state(''); + let imageUrl = $state(''); + let latitude = $state(undefined); + let longitude = $state(undefined); let submitting = $state(false); let error = $state(''); + let geocoding = $state(false); + let imageError = $state(false); const categories = [ { value: 'sight', labelKey: 'category.sight' }, @@ -46,13 +51,25 @@ address = data.result.address || ''; category = data.result.category || 'sight'; sources = data.result.sources || []; + if (data.result.imageUrl) { + imageUrl = data.result.imageUrl; + imageError = false; + } + if (data.result.latitude && data.result.longitude) { + latitude = data.result.latitude; + longitude = data.result.longitude; + } } else { name = searchQuery.trim(); } lookupDone = true; + + // Auto-geocode if we got an address but no coordinates + if (address && !latitude) { + geocodeAddress(); + } } catch { - // Fallback: just use the search query as name name = searchQuery.trim(); lookupDone = true; } finally { @@ -71,9 +88,46 @@ name = ''; description = ''; address = ''; + imageUrl = ''; category = 'sight'; + latitude = undefined; + longitude = undefined; sources = []; error = ''; + imageError = false; + } + + async function geocodeAddress() { + const addr = address.trim(); + if (!addr) return; + + geocoding = true; + try { + const q = addr.includes('Konstanz') ? addr : `${addr}, Konstanz`; + const res = await fetch( + `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(q)}&limit=1`, + { headers: { 'User-Agent': 'CityCorners/1.0' } } + ); + const results = await res.json(); + if (results.length > 0) { + latitude = parseFloat(results[0].lat); + longitude = parseFloat(results[0].lon); + } + } catch { + // Geocoding is best-effort + } finally { + geocoding = false; + } + } + + let geocodeTimeout: ReturnType | undefined; + function handleAddressInput() { + clearTimeout(geocodeTimeout); + geocodeTimeout = setTimeout(() => { + if (address.trim().length > 5) { + geocodeAddress(); + } + }, 1000); } async function handleSubmit() { @@ -89,18 +143,25 @@ return; } + const body: Record = { + name: name.trim(), + category, + description: description.trim(), + }; + if (address.trim()) body.address = address.trim(); + if (imageUrl.trim() && !imageError) body.imageUrl = imageUrl.trim(); + if (latitude !== undefined && longitude !== undefined) { + body.latitude = latitude; + body.longitude = longitude; + } + const res = await fetch(api('/locations'), { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, - body: JSON.stringify({ - name: name.trim(), - category, - description: description.trim(), - address: address.trim() || undefined, - }), + body: JSON.stringify(body), }); if (res.ok) { @@ -261,9 +322,42 @@ id="address" type="text" bind:value={address} + oninput={handleAddressInput} placeholder={$_('add.addressPlaceholder')} class="w-full rounded-lg border border-border bg-background px-4 py-2.5 text-foreground placeholder:text-foreground-secondary/50 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" /> + {#if geocoding} +

{$_('add.geocoding')}

+ {:else if latitude !== undefined && longitude !== undefined} +

{$_('add.coordinatesFound')}

+ {/if} +
+ + +
+ + (imageError = false)} + placeholder={$_('add.imageUrlPlaceholder')} + class="w-full rounded-lg border border-border bg-background px-4 py-2.5 text-foreground placeholder:text-foreground-secondary/50 focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" + /> + {#if imageUrl.trim() && !imageError} +
+ {$_('add.imagePreview')} (imageError = true)} + /> +
+ {:else if imageError} +

{$_('add.imageLoadError')}

+ {/if}
diff --git a/apps/todo/apps/web/src/routes/(app)/+page.svelte b/apps/todo/apps/web/src/routes/(app)/+page.svelte index 8d2cb69c1..ca6274bb4 100644 --- a/apps/todo/apps/web/src/routes/(app)/+page.svelte +++ b/apps/todo/apps/web/src/routes/(app)/+page.svelte @@ -4,6 +4,7 @@ import { de } from 'date-fns/locale'; import { Sparkle, ArrowDown } from '@manacore/shared-icons'; import { tasksStore } from '$lib/stores/tasks.svelte'; + import { authStore } from '$lib/stores/auth.svelte'; import { viewStore } from '$lib/stores/view.svelte'; import { applyTaskFilters } from '$lib/utils/task-filters'; import TaskList from '$lib/components/TaskList.svelte'; @@ -29,6 +30,11 @@ onMount(async () => { viewStore.setToday(); + // Wait for auth to be initialized (layout onMount runs after page onMount in Svelte) + while (!authStore.initialized) { + await new Promise((r) => setTimeout(r, 50)); + } + try { // Fetch tasks (works in both guest and authenticated mode) await tasksStore.fetchAllTasks(); diff --git a/docker-compose.macmini.yml b/docker-compose.macmini.yml index 6ff54ea46..757a76493 100644 --- a/docker-compose.macmini.yml +++ b/docker-compose.macmini.yml @@ -6,7 +6,6 @@ # 4000-4099: Matrix Stack # 5000-5099: Web Frontends # 5100-5199: Games -# 6000-6099: Automation & Workflows # 8000-8099: Monitoring Dashboards # 9000-9199: Infrastructure & Exporters # @@ -32,7 +31,7 @@ services: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 10s + interval: 30s timeout: 5s retries: 5 @@ -47,7 +46,7 @@ services: - "6379:6379" healthcheck: test: ["CMD", "redis-cli", "--raw", "incr", "ping"] - interval: 10s + interval: 30s timeout: 5s retries: 5 @@ -67,7 +66,7 @@ services: - "9001:9001" healthcheck: test: ["CMD", "mc", "ready", "local"] - interval: 30s + interval: 120s timeout: 20s retries: 3 @@ -163,7 +162,7 @@ services: - "3001:3001" healthcheck: test: ["CMD", "node", "-e", "const http = require('http'); http.get('http://127.0.0.1:3001/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -203,7 +202,7 @@ services: - "3010:3010" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3010/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -221,7 +220,7 @@ services: # Internal only - no external port mapping healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/healthz"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 15s @@ -254,7 +253,7 @@ services: - "3020:3020" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3020/api/v1/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -294,7 +293,7 @@ services: - "3015:3015" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3015/api/v1/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -320,7 +319,7 @@ services: - "3050:3050" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3050/api/v1/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -356,7 +355,7 @@ services: - "3030:3030" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3030/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -383,7 +382,7 @@ services: - "3031:3031" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3031/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -414,7 +413,7 @@ services: - "3032:3032" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3032/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -444,7 +443,7 @@ services: - "3033:3033" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3033/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -480,7 +479,7 @@ services: - "3034:3034" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3034/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -515,7 +514,7 @@ services: - "3035:3035" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3035/api/v1/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -542,7 +541,7 @@ services: - "3036:3036" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3036/api/v1/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -572,7 +571,7 @@ services: - "3037:3037" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3037/api/v1/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -601,7 +600,7 @@ services: - "3038:3038" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3038/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -634,7 +633,7 @@ services: - "3039:3039" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3039/api/v1/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -664,7 +663,7 @@ services: - "3007:3007" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3007/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -697,7 +696,7 @@ services: - "3010:3010" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3010/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -735,7 +734,7 @@ services: - "3022:3022" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3022/api/v1/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -765,7 +764,7 @@ services: - "3041:3041" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3041/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -800,7 +799,7 @@ services: - "9002:9002" # Metrics healthcheck: test: ["CMD", "curl", "-fSs", "http://localhost:8008/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 60s @@ -818,7 +817,7 @@ services: - "4080:80" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:80/"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -840,7 +839,7 @@ services: - "4090:5180" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5180/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -879,7 +878,7 @@ services: - "4010:4010" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4010/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -908,7 +907,7 @@ services: - "4011:4011" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4011/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -949,7 +948,7 @@ services: - "4012:4012" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4012/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -982,7 +981,7 @@ services: - "4013:4013" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4013/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1018,7 +1017,7 @@ services: - "4014:4014" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4014/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1052,7 +1051,7 @@ services: - "4015:4015" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4015/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1086,7 +1085,7 @@ services: - "4016:4016" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4016/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1116,7 +1115,7 @@ services: - "4017:4017" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4017/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1157,7 +1156,7 @@ services: - "4018:4018" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4018/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1191,7 +1190,7 @@ services: - "4019:4019" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4019/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1224,7 +1223,7 @@ services: - "4021:4021" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4021/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1261,7 +1260,7 @@ services: - "4020:4020" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4020/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1301,7 +1300,7 @@ services: - "4022:4022" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:4022/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1337,7 +1336,7 @@ services: - "5000:5000" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5000/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1360,7 +1359,7 @@ services: - "5010:5010" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5010/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1386,7 +1385,7 @@ services: - "5011:5011" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5011/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1412,7 +1411,7 @@ services: - "5018:5018" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5018/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1440,7 +1439,7 @@ services: - "5012:5012" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5012/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1463,7 +1462,7 @@ services: - "5013:5013" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5013/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1493,7 +1492,7 @@ services: - "5014:5014" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5014/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1522,7 +1521,7 @@ services: - "5015:5015" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5015/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1545,7 +1544,7 @@ services: - "5016:5016" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5016/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1568,7 +1567,7 @@ services: - "5017:5017" healthcheck: test: ["CMD", "node", "-e", "const http = require('http'); http.get('http://127.0.0.1:5017/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1597,7 +1596,7 @@ services: - "5020:5020" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5020/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1629,7 +1628,7 @@ services: - "5019:5019" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5019/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1658,7 +1657,7 @@ services: - "5180:5180" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5180/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1687,7 +1686,7 @@ services: - "5022:5022" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5022/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1726,7 +1725,7 @@ services: - "3040:3040" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3040/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1755,7 +1754,7 @@ services: - "5021:5021" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5021/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1790,7 +1789,7 @@ services: - "3025:3025" healthcheck: test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:3025/health').raise_for_status()"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 30s @@ -1817,53 +1816,13 @@ services: - "5090:5090" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:5090/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 10s labels: - "com.centurylinklabs.watchtower.enable=true" - # ============================================ - # Tier 6: Automation & Workflows (Ports 6000-6099) - # ============================================ - - n8n: - image: n8nio/n8n:latest - container_name: mana-auto-n8n - restart: always - depends_on: - postgres: - condition: service_healthy - environment: - DB_TYPE: postgresdb - DB_POSTGRESDB_HOST: postgres - DB_POSTGRESDB_PORT: 5432 - DB_POSTGRESDB_DATABASE: n8n - DB_POSTGRESDB_USER: postgres - DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD:-mana123} - N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:-change-me-n8n-encryption-key} - N8N_USER_MANAGEMENT_JWT_SECRET: ${N8N_JWT_SECRET:-change-me-n8n-jwt-secret} - N8N_HOST: n8n.mana.how - N8N_PROTOCOL: https - WEBHOOK_URL: https://n8n.mana.how/ - N8N_BASIC_AUTH_ACTIVE: "false" - N8N_PORT: 6000 - GENERIC_TIMEZONE: Europe/Berlin - TZ: Europe/Berlin - N8N_DIAGNOSTICS_ENABLED: "false" - N8N_VERSION_NOTIFICATIONS_ENABLED: "false" - volumes: - - n8n_data:/home/node/.n8n - ports: - - "6000:6000" - healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:6000/healthz"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 40s - # ============================================ # Tier 7: Monitoring Dashboards (Ports 8000-8099) # ============================================ @@ -1893,7 +1852,7 @@ services: - "8000:8000" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8000/api/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -1913,7 +1872,7 @@ services: - "8010:3000" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:3000/api/heartbeat"] - interval: 30s + interval: 120s timeout: 10s retries: 3 start_period: 40s @@ -1942,7 +1901,7 @@ services: - "9090:9090" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9090/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -1954,7 +1913,7 @@ services: - "9091:9091" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9091/-/healthy"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -1973,7 +1932,7 @@ services: - "9110:8080" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/healthz"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -2022,7 +1981,7 @@ services: - "9100:9100" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9100/metrics"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -2053,7 +2012,7 @@ services: - "8880:8880" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8880/health"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -2075,7 +2034,7 @@ services: - "9093:9093" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:9093/-/healthy"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -2095,7 +2054,7 @@ services: - "9095:8080" healthcheck: test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:8080/health"] - interval: 30s + interval: 120s timeout: 5s retries: 3 start_period: 5s @@ -2148,7 +2107,7 @@ services: condition: service_healthy healthcheck: test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8020/_health/')"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -2189,7 +2148,7 @@ services: - "5100:5100" healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:5100/"] - interval: 30s + interval: 120s timeout: 10s retries: 3 @@ -2204,7 +2163,5 @@ volumes: name: mana-grafana-data analytics_data: name: mana-analytics-data - n8n_data: - name: mana-n8n-data matrix_bots_data: name: mana-matrix-bots-data diff --git a/services/mana-core-auth/src/auth/services/better-auth.service.ts b/services/mana-core-auth/src/auth/services/better-auth.service.ts index 4767fbb28..2e7fe3bf8 100644 --- a/services/mana-core-auth/src/auth/services/better-auth.service.ts +++ b/services/mana-core-auth/src/auth/services/better-auth.service.ts @@ -1139,15 +1139,16 @@ export class BetterAuthService { // Decode to check the algorithm const decoded = jwt.decode(token, { complete: true }); - // Use our JWKS endpoint (NestJS prefix: /api/v1) - const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3001'; - const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl); + // Use our JWKS endpoint via localhost (self-referencing avoids external URL issues in Docker) + const port = this.configService.get('PORT') || 3001; + const jwksUrl = new URL(`http://localhost:${port}/api/v1/auth/jwks`); // Create JWKS fetcher const JWKS = createRemoteJWKSet(jwksUrl); // IMPORTANT: Match Better Auth signing config exactly (better-auth.config.ts) // Signing uses: issuer = BASE_URL, audience = JWT_AUDIENCE || 'manacore' + const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3001'; const issuer = baseUrl; // Better Auth uses BASE_URL as issuer for OIDC compatibility const audience = this.configService.get('jwt.audience') || 'manacore'; diff --git a/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts b/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts index de1970822..04618b9c5 100644 --- a/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts +++ b/services/mana-core-auth/src/common/guards/jwt-auth.guard.ts @@ -35,10 +35,10 @@ export class JwtAuthGuard implements CanActivate { } try { - // Lazy initialize JWKS + // Lazy initialize JWKS via localhost (self-referencing avoids external URL issues in Docker) if (!this.jwks) { - const baseUrl = this.configService.get('BASE_URL') || 'http://localhost:3001'; - const jwksUrl = new URL('/api/v1/auth/jwks', baseUrl); + const port = this.configService.get('PORT') || 3001; + const jwksUrl = new URL(`http://localhost:${port}/api/v1/auth/jwks`); this.jwks = createRemoteJWKSet(jwksUrl); }