diff --git a/CONFIGURATION.md b/CONFIGURATION.md index a0db187..c9f3c68 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -6,17 +6,24 @@ Starting from version 1.0.0-alpha, Open Notebook uses a simplified API connectio ### How It Works -The frontend now automatically discovers the API location at runtime, eliminating the need for complex network configurations. This works for both Docker deployment modes: +The frontend automatically discovers the API location at runtime by analyzing the incoming HTTP request. This eliminates the need for complex network configurations and works for both Docker deployment modes: - Multi-container (docker-compose with separate SurrealDB) - Single-container (all services in one container) -### Default Configuration +**Auto-detection logic:** +1. If `API_URL` environment variable is set → use it (explicit override) +2. Otherwise, detect from the HTTP request: + - Uses the same hostname you're accessing the frontend from + - Automatically changes port to 5055 (API port) + - Respects `X-Forwarded-Proto` header for reverse proxy setups +3. Falls back to `http://localhost:5055` if detection fails -By default, the API is accessible at `http://localhost:5055`. This works for most local Docker deployments where: -- You access the frontend at `http://localhost:8502` -- Your browser can directly reach `http://localhost:5055` +**Examples:** +- Access frontend at `http://localhost:8502` → API at `http://localhost:5055` ✅ +- Access frontend at `http://10.20.30.20:8502` → API at `http://10.20.30.20:5055` ✅ +- Access frontend at `http://my-server:8502` → API at `http://my-server:5055` ✅ -**No configuration needed** for standard localhost deployments! +**No configuration needed** for most deployments! ### Custom Configuration diff --git a/MIGRATION.md b/MIGRATION.md index f48d644..faafd6f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -99,12 +99,15 @@ curl http://your-server-ip:5055/api/config Expected response: ```json { - "apiUrl": "http://localhost:5055", "version": "1.0.0", - "dbStatus": "connected" + "latestVersion": "1.0.0", + "hasUpdate": false, + "dbStatus": "online" } ``` +Note: The API URL is now auto-detected by the frontend from the hostname you're accessing, so `/api/config` no longer returns `apiUrl`. + ### Troubleshooting **Problem:** Frontend shows "Cannot connect to API" error diff --git a/api/routers/config.py b/api/routers/config.py index b999565..24de56c 100644 --- a/api/routers/config.py +++ b/api/routers/config.py @@ -118,34 +118,13 @@ async def check_database_health() -> dict: async def get_config(request: Request): """ Get frontend configuration. - This endpoint provides runtime configuration to the frontend, - allowing the same Docker image to work in different environments. - Auto-detection logic: - 1. If API_URL env var is set, use it (explicit override) - 2. Otherwise, detect from incoming HTTP request (zero-config) + Returns version information and health status. + Note: The frontend determines the API URL via its own runtime-config endpoint, + so this endpoint no longer returns apiUrl. Also checks for version updates from GitHub (with caching and error handling). """ - # Check if API_URL is explicitly set - env_api_url = os.getenv("API_URL") - - if env_api_url: - logger.debug(f"Using API_URL from environment: {env_api_url}") - api_url = env_api_url - else: - # Auto-detect from request - # Get the protocol (http or https) - # Check X-Forwarded-Proto first (for reverse proxies), then fallback to request scheme - proto = request.headers.get("x-forwarded-proto", request.url.scheme) - - # Get the host (includes port if non-standard) - host = request.headers.get("host", f"{request.client.host}:5055") - - # Construct the API URL - api_url = f"{proto}://{host}" - logger.info(f"Auto-detected API URL from request: {api_url} (proto={proto}, host={host})") - # Get current version current_version = get_version() @@ -168,7 +147,6 @@ async def get_config(request: Request): logger.warning(f"Database offline: {db_health.get('error', 'Unknown error')}") return { - "apiUrl": api_url, "version": current_version, "latestVersion": latest_version, "hasUpdate": has_update, diff --git a/docs/deployment/reverse-proxy.md b/docs/deployment/reverse-proxy.md index 92faa76..1b749ac 100644 --- a/docs/deployment/reverse-proxy.md +++ b/docs/deployment/reverse-proxy.md @@ -12,7 +12,14 @@ The frontend uses a three-tier priority system to determine the API URL: 1. **Runtime Configuration** (Highest Priority): `API_URL` environment variable set at container runtime 2. **Build-time Configuration**: `NEXT_PUBLIC_API_URL` baked into the Docker image -3. **Auto-detection** (Fallback): Infers from the incoming HTTP request +3. **Auto-detection** (Fallback): Infers from the incoming HTTP request headers + +**Auto-detection details:** +- The Next.js frontend analyzes the incoming HTTP request +- Extracts the hostname from the `host` header +- Respects the `X-Forwarded-Proto` header (for HTTPS behind reverse proxies) +- Constructs the API URL as `{protocol}://{hostname}:5055` +- Example: Request to `http://10.20.30.20:8502` → API URL becomes `http://10.20.30.20:5055` ## Common Scenarios diff --git a/frontend/src/app/api/runtime-config/route.ts b/frontend/src/app/api/runtime-config/route.ts index deb8811..af6b000 100644 --- a/frontend/src/app/api/runtime-config/route.ts +++ b/frontend/src/app/api/runtime-config/route.ts @@ -1,4 +1,4 @@ -import { NextResponse } from 'next/server' +import { NextRequest, NextResponse } from 'next/server' /** * Runtime Configuration Endpoint @@ -6,17 +6,54 @@ import { NextResponse } from 'next/server' * This endpoint provides server-side environment variables to the client at runtime. * This solves the NEXT_PUBLIC_* limitation where variables are baked into the build. * - * Users can now set API_URL in their docker.env and it will be picked up at runtime, - * allowing the same Docker image to work in different deployment scenarios. + * Auto-detection logic: + * 1. If API_URL env var is set, use it (explicit override) + * 2. Otherwise, detect from incoming HTTP request headers (zero-config) + * 3. Fallback to localhost:5055 if detection fails + * + * This allows the same Docker image to work in different deployment scenarios. */ -export async function GET() { - // Priority: - // 1. API_URL from environment (set by user at runtime) - // 2. NEXT_PUBLIC_API_URL from build time (fallback) - // 3. Default to localhost:5055 - const apiUrl = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5055' +export async function GET(request: NextRequest) { + // Priority 1: Check if API_URL is explicitly set + const envApiUrl = process.env.API_URL || process.env.NEXT_PUBLIC_API_URL + if (envApiUrl) { + return NextResponse.json({ + apiUrl: envApiUrl, + }) + } + + // Priority 2: Auto-detect from request headers + try { + // Get the protocol (http or https) + // Check X-Forwarded-Proto first (for reverse proxies), then fallback to request scheme + const proto = request.headers.get('x-forwarded-proto') || + request.nextUrl.protocol.replace(':', '') || + 'http' + + // Get the host header (includes port if non-standard) + const hostHeader = request.headers.get('host') + + if (hostHeader) { + // Extract just the hostname (remove port if present) + const hostname = hostHeader.split(':')[0] + + // Construct the API URL with port 5055 + const apiUrl = `${proto}://${hostname}:5055` + + console.log(`[runtime-config] Auto-detected API URL: ${apiUrl} (proto=${proto}, host=${hostHeader})`) + + return NextResponse.json({ + apiUrl, + }) + } + } catch (error) { + console.error('[runtime-config] Auto-detection failed:', error) + } + + // Priority 3: Fallback to localhost + console.log('[runtime-config] Using fallback: http://localhost:5055') return NextResponse.json({ - apiUrl, + apiUrl: 'http://localhost:5055', }) } diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts index 8b965dc..a264760 100644 --- a/frontend/src/lib/config.ts +++ b/frontend/src/lib/config.ts @@ -3,7 +3,7 @@ * This allows the same Docker image to work in different environments. */ -import { AppConfig } from '@/lib/types/config' +import { AppConfig, BackendConfigResponse } from '@/lib/types/config' // Build timestamp for debugging - set at build time const BUILD_TIME = new Date().toISOString() @@ -116,9 +116,9 @@ async function fetchConfig(): Promise { }) if (response.ok) { - const data = await response.json() + const data: BackendConfigResponse = await response.json() config = { - apiUrl: data.apiUrl || baseUrl, + apiUrl: baseUrl, // Use baseUrl from runtime-config (Python no longer returns this) version: data.version || 'unknown', buildTime: BUILD_TIME, latestVersion: data.latestVersion || null, diff --git a/frontend/src/lib/types/config.ts b/frontend/src/lib/types/config.ts index b9f2fe8..f44ddad 100644 --- a/frontend/src/lib/types/config.ts +++ b/frontend/src/lib/types/config.ts @@ -1,5 +1,18 @@ /** - * Application configuration received from backend /api/config + * Backend configuration response from Python API /api/config endpoint. + * Note: apiUrl is determined by the Next.js runtime-config endpoint, + * not returned by the Python backend. + */ +export interface BackendConfigResponse { + version: string + latestVersion?: string | null + hasUpdate?: boolean + dbStatus?: "online" | "offline" +} + +/** + * Complete application configuration used by the frontend. + * This is constructed from the backend response + runtime-config. */ export interface AppConfig { apiUrl: string