From eb792617a53a402e3cb7f331f2163cffd7993624 Mon Sep 17 00:00:00 2001 From: haikdc Date: Mon, 30 Mar 2026 07:56:15 -0700 Subject: [PATCH] [Haik]: ckpt, fixed nine router inter dependency issues with local and prod sharing ports. Also centralized all port handling to a ports.config.json in the root dir --- 9router/src/app/callback/page.js | 55 ++++++++++++------- CONTRIBUTING.md | 18 +++--- README.md | 4 +- backend/.env.example | 4 +- backend/apps/agents/agent_options.py | 7 ++- .../apps/agents/browser_agent_mcp_server.py | 2 +- backend/apps/agents/browser_mcp_server.py | 2 +- .../apps/agents/invoke_agent_mcp_server.py | 2 +- backend/apps/nine_router.py | 2 +- backend/apps/settings/credentials.py | 6 +- backend/apps/tools_lib/oauth.py | 6 +- backend/config/Apps.py | 3 +- backend/main.py | 4 +- backend/ports.py | 23 ++++++++ backend/run.sh | 7 ++- electron/main.js | 13 ++++- electron/package.json | 4 ++ frontend/src/shared/config.ts | 2 +- frontend/webpack.config.js | 6 +- ports.config.json | 10 ++++ run/local.sh | 16 ++++-- run/utils/build-app.sh | 2 + 22 files changed, 139 insertions(+), 59 deletions(-) create mode 100644 backend/ports.py create mode 100644 ports.config.json diff --git a/9router/src/app/callback/page.js b/9router/src/app/callback/page.js index b41048bb..c1730271 100644 --- a/9router/src/app/callback/page.js +++ b/9router/src/app/callback/page.js @@ -55,31 +55,44 @@ function CallbackContent() { } // Method 4: Direct exchange via OpenSwarm backend (works even when postMessage fails) - // Fetch pending OAuth data from OpenSwarm, then call 9Router's exchange endpoint + // The backend port is dynamic (8325 dev, 8324+ prod), so try known ports and + // use whichever has a matching pending state entry. if (code && state) { (async () => { - try { - const pendingRes = await fetch(`http://localhost:8324/api/subscriptions/pending/${encodeURIComponent(state)}`); - if (pendingRes.ok) { - const pending = await pendingRes.json(); - if (pending.provider && pending.code_verifier) { - const exchangeRes = await fetch(`/api/oauth/${pending.provider}/exchange`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - code, - redirectUri: pending.redirect_uri, - codeVerifier: pending.code_verifier, - state, - }), - }); - if (exchangeRes.ok) { - console.log("Direct exchange successful"); - } + const portsToTry = [8325, 8324]; + let pending = null; + for (const port of portsToTry) { + try { + const res = await fetch( + `http://localhost:${port}/api/subscriptions/pending/${encodeURIComponent(state)}`, + { signal: AbortSignal.timeout(1000) }, + ); + if (res.ok) { + pending = await res.json(); + break; } + } catch { + /* port not reachable, try next */ + } + } + if (pending?.provider && pending?.code_verifier) { + try { + const exchangeRes = await fetch(`/api/oauth/${pending.provider}/exchange`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + code, + redirectUri: pending.redirect_uri, + codeVerifier: pending.code_verifier, + state, + }), + }); + if (exchangeRes.ok) { + console.log("Direct exchange successful"); + } + } catch (e) { + console.log("Direct exchange failed:", e); } - } catch (e) { - console.log("Direct exchange fallback failed:", e); } })(); } diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f41cd881..7c7dceaf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ For other integrations, edit `backend/.env`: | Variable | Purpose | |----------|---------| -| `BACKEND_PORT` | Backend server port (default: `8324`) | +| `BACKEND_PORT` | Backend server port (default: `8325` for dev; prod uses `8324`) | | `GOOGLE_OAUTH_CLIENT_ID` | Google Workspace integration (Gmail, Calendar, Drive) | | `GOOGLE_OAUTH_CLIENT_SECRET` | Google Workspace integration | | `APPLE_ID` | macOS code signing & notarization (release builds only) | @@ -70,14 +70,14 @@ For other integrations, edit `backend/.env`: bash run/local.sh ``` -This starts the backend (port 8324), frontend (port 3000), and Electron shell together. The script handles virtual environments and dependency installation automatically. +This starts the backend (port 8325), frontend (port 3000), and Electron shell together. The script handles virtual environments and dependency installation automatically. ### Option B: Run services individually **Backend** (in one terminal): ```bash -bash backend/run.sh # API at http://localhost:8324 — docs at /docs +bash backend/run.sh # API at http://localhost:8325 — docs at /docs ``` **Frontend** (in another terminal): @@ -94,7 +94,7 @@ bash frontend/run.sh # App at http://localhost:3000 cd backend source .venv/bin/activate cd .. -python -m uvicorn backend.main:app --host 0.0.0.0 --port 8324 --reload --reload-dir backend +python -m uvicorn backend.main:app --host 0.0.0.0 --port 8325 --reload --reload-dir backend ``` **Terminal 2 — Frontend dev server:** @@ -113,8 +113,8 @@ Once everything is running: | Service | URL | |---------|-----| | **Frontend (UI)** | [http://localhost:3000](http://localhost:3000) | -| **Backend API** | [http://localhost:8324](http://localhost:8324) | -| **API Docs (Swagger)** | [http://localhost:8324/docs](http://localhost:8324/docs) | +| **Backend API** | [http://localhost:8325](http://localhost:8325) | +| **API Docs (Swagger)** | [http://localhost:8325/docs](http://localhost:8325/docs) | --- @@ -144,7 +144,7 @@ To use Google Calendar, Gmail, Drive, and other Google tools from your agents, y - Add your Google account as a test user (required while the app is in "Testing" status) 4. Back on the credentials page, create an **OAuth client ID**: - Application type: **Web application** - - Authorized redirect URIs: `http://localhost:8324/api/tools/oauth/callback` + - Authorized redirect URIs: `http://localhost:8325/api/tools/oauth/callback` 5. Copy the **Client ID** and **Client Secret** ### c. Add credentials to your `.env` @@ -239,11 +239,11 @@ Please open an issue first for larger changes so we can discuss the approach. Make sure you're running from the **project root** (not from `backend/`): ```bash cd openswarm -python -m uvicorn backend.main:app --host 0.0.0.0 --port 8324 --reload +python -m uvicorn backend.main:app --host 0.0.0.0 --port 8325 --reload ``` ### Frontend proxy errors / API calls failing -The frontend dev server proxies `/api` requests to `http://localhost:8324`. Make sure the backend is running first. +The frontend dev server proxies `/api` requests to `http://localhost:8325`. Make sure the backend is running first. ### Mock mode vs real mode If you see mock responses, either: diff --git a/README.md b/README.md index 1f892f08..0bc32bf1 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ cd openswarm bash run/local.sh ``` -This starts the backend (port 8324), frontend (port 3000), and Electron shell together. Once running, set your Anthropic API key in the in-app Settings page. +This starts the backend (port 8325), frontend (port 3000), and Electron shell together. Once running, set your Anthropic API key in the in-app Settings page. See the **[Contributing Guide](CONTRIBUTING.md)** for detailed setup options, environment configuration, Google Workspace integration, and troubleshooting. @@ -101,7 +101,7 @@ See the **[Contributing Guide](CONTRIBUTING.md)** for detailed setup options, en Electron Shell (desktop wrapper, auto-updater) ├─────────────────────────────────────────────────────────────────────┐ │ │ -│ Frontend (React/TypeScript :3000) Backend (FastAPI :8324) │ +│ Frontend (React/TypeScript :3000) Backend (FastAPI :8325) │ │ ┌───────────────────────────────┐ ┌───────────────────────┐ │ │ │ Spatial Dashboard Canvas │◄────►│ REST API (/api/*) │ │ │ │ Agent Chat (streaming) │ │ WebSocket (/ws/*) │ │ diff --git a/backend/.env.example b/backend/.env.example index ded015af..4dfa89b7 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,7 +1,9 @@ # ============================================================================= # Backend Server # ============================================================================= -BACKEND_PORT=8324 +# Dev default is 8325 (avoids collision with prod's 8324). +# Override via OPENSWARM_PORT env var or ports.config.json. +BACKEND_PORT=8325 GOOGLE_OAUTH_CLIENT_ID=your-google-oauth-client-id.apps.googleusercontent.com GOOGLE_OAUTH_CLIENT_SECRET=your-google-oauth-client-secret diff --git a/backend/apps/agents/agent_options.py b/backend/apps/agents/agent_options.py index 072fc68e..8aac1819 100644 --- a/backend/apps/agents/agent_options.py +++ b/backend/apps/agents/agent_options.py @@ -26,6 +26,7 @@ from backend.apps.tools_lib.tools_lib import ( load_builtin_permissions, ) from backend.apps.common.mcp_utils import sanitize_server_name as _sanitize_server_name +from backend.ports import BACKEND_DEV_PORT, NINE_ROUTER_PORT logger = logging.getLogger(__name__) @@ -71,7 +72,7 @@ async def build_agent_options( if not _browser_all_denied: browser_agent_server_path = os.path.join(os.path.dirname(__file__), "browser_agent_mcp_server.py") - backend_port = os.environ.get("OPENSWARM_PORT", "8324") + backend_port = os.environ.get("OPENSWARM_PORT", str(BACKEND_DEV_PORT)) pre_selected_bids = get_pre_selected_browser_ids(session.dashboard_id) mcp_servers["openswarm-browser-agent"] = { "command": sys.executable, @@ -91,7 +92,7 @@ async def build_agent_options( if not _invoke_all_denied: invoke_agent_server_path = os.path.join(os.path.dirname(__file__), "invoke_agent_mcp_server.py") - backend_port = os.environ.get("OPENSWARM_PORT", "8324") + backend_port = os.environ.get("OPENSWARM_PORT", str(BACKEND_DEV_PORT)) mcp_servers["openswarm-invoke-agent"] = { "command": sys.executable, "args": [invoke_agent_server_path], @@ -128,7 +129,7 @@ async def build_agent_options( elif _9r_running(): options_kwargs["env"] = { "ANTHROPIC_API_KEY": "9router", - "ANTHROPIC_BASE_URL": "http://localhost:20128", + "ANTHROPIC_BASE_URL": f"http://localhost:{NINE_ROUTER_PORT}", } options_kwargs["extra_args"] = {"bare": None} logger.info("[MCP-DEBUG] Using 9Router (bare mode)") diff --git a/backend/apps/agents/browser_agent_mcp_server.py b/backend/apps/agents/browser_agent_mcp_server.py index 08b1606b..8e967320 100644 --- a/backend/apps/agents/browser_agent_mcp_server.py +++ b/backend/apps/agents/browser_agent_mcp_server.py @@ -22,7 +22,7 @@ except ImportError: from browser_agent_mcp_schemas import TOOLS # noqa: E402 (sibling script import) -BACKEND_PORT = os.environ.get("OPENSWARM_PORT", "8324") +BACKEND_PORT = os.environ.get("OPENSWARM_PORT", "8325") BACKEND_URL = f"http://127.0.0.1:{BACKEND_PORT}/api/agents/browser-agent/run" MODEL = os.environ.get("OPENSWARM_AGENT_MODEL", "sonnet") DASHBOARD_ID = os.environ.get("OPENSWARM_DASHBOARD_ID", "") diff --git a/backend/apps/agents/browser_mcp_server.py b/backend/apps/agents/browser_mcp_server.py index 6fecc7ed..8878889f 100644 --- a/backend/apps/agents/browser_mcp_server.py +++ b/backend/apps/agents/browser_mcp_server.py @@ -23,7 +23,7 @@ except ImportError: from browser_mcp_schemas import TOOLS # noqa: E402 (sibling script import) -BACKEND_PORT = os.environ.get("OPENSWARM_PORT", "8324") +BACKEND_PORT = os.environ.get("OPENSWARM_PORT", "8325") BACKEND_URL = f"http://127.0.0.1:{BACKEND_PORT}/api/agents/browser/command" diff --git a/backend/apps/agents/invoke_agent_mcp_server.py b/backend/apps/agents/invoke_agent_mcp_server.py index 68a2f1ce..e681001e 100644 --- a/backend/apps/agents/invoke_agent_mcp_server.py +++ b/backend/apps/agents/invoke_agent_mcp_server.py @@ -13,7 +13,7 @@ import os import urllib.request import urllib.error -BACKEND_PORT = os.environ.get("OPENSWARM_PORT", "8324") +BACKEND_PORT = os.environ.get("OPENSWARM_PORT", "8325") BACKEND_URL = f"http://127.0.0.1:{BACKEND_PORT}/api/agents/invoke-agent/run" PARENT_SESSION_ID = os.environ.get("OPENSWARM_PARENT_SESSION_ID", "") DASHBOARD_ID = os.environ.get("OPENSWARM_DASHBOARD_ID", "") diff --git a/backend/apps/nine_router.py b/backend/apps/nine_router.py index 517b6883..7a28396f 100644 --- a/backend/apps/nine_router.py +++ b/backend/apps/nine_router.py @@ -17,7 +17,7 @@ import httpx logger = logging.getLogger(__name__) -NINE_ROUTER_PORT = 20128 +from backend.ports import NINE_ROUTER_PORT NINE_ROUTER_URL = f"http://localhost:{NINE_ROUTER_PORT}" NINE_ROUTER_API = f"{NINE_ROUTER_URL}/api" NINE_ROUTER_V1 = f"{NINE_ROUTER_URL}/v1" diff --git a/backend/apps/settings/credentials.py b/backend/apps/settings/credentials.py index 60026faf..e40908c4 100644 --- a/backend/apps/settings/credentials.py +++ b/backend/apps/settings/credentials.py @@ -19,7 +19,8 @@ def _check_9router() -> bool: """Check if 9Router is running locally.""" try: import httpx - r = httpx.get("http://localhost:20128/v1/models", timeout=2.0) + from backend.ports import NINE_ROUTER_PORT + r = httpx.get(f"http://localhost:{NINE_ROUTER_PORT}/v1/models", timeout=2.0) return r.status_code == 200 except Exception: return False @@ -146,9 +147,10 @@ def get_anthropic_client(settings: AppSettings) -> anthropic.AsyncAnthropic: # Fall back to 9Router subscription (free for users with Claude/ChatGPT/Gemini subscriptions) if _check_9router(): + from backend.ports import NINE_ROUTER_PORT return anthropic.AsyncAnthropic( api_key="9router", - base_url="http://localhost:20128", + base_url=f"http://localhost:{NINE_ROUTER_PORT}", ) raise ValueError("No AI provider configured. Set an API key or connect a subscription.") diff --git a/backend/apps/tools_lib/oauth.py b/backend/apps/tools_lib/oauth.py index 6d47ec82..91624909 100644 --- a/backend/apps/tools_lib/oauth.py +++ b/backend/apps/tools_lib/oauth.py @@ -13,6 +13,8 @@ from typing import Any, Optional from urllib.parse import urlencode import httpx + +from backend.ports import BACKEND_DEV_PORT from fastapi import HTTPException, Query from fastapi.responses import HTMLResponse @@ -43,7 +45,7 @@ async def oauth_callback(code: str = Query(...), state: str = Query("")): client_id = os.environ.get(provider.client_id_env, "") client_secret = os.environ.get(provider.client_secret_env, "") - _port = os.environ.get("OPENSWARM_PORT", "8324") + _port = os.environ.get("OPENSWARM_PORT", str(BACKEND_DEV_PORT)) redirect_uri = f"http://localhost:{_port}/api/tools/oauth/callback" token_data: dict[str, str] = { @@ -166,7 +168,7 @@ async def oauth_start(tool_id: str): if not client_id: raise HTTPException(status_code=400, detail=f"{provider.client_id_env} not set in backend .env") - _port = os.environ.get("OPENSWARM_PORT", "8324") + _port = os.environ.get("OPENSWARM_PORT", str(BACKEND_DEV_PORT)) redirect_uri = f"http://localhost:{_port}/api/tools/oauth/callback" provider_key = tool.oauth_provider or "google" state = f"{provider_key}:{tool_id}" diff --git a/backend/config/Apps.py b/backend/config/Apps.py index c2c41367..9b8fece2 100644 --- a/backend/config/Apps.py +++ b/backend/config/Apps.py @@ -1,5 +1,6 @@ import os +from backend.ports import BACKEND_DEV_PORT from fastapi import FastAPI, APIRouter # import debug from uuid import uuid4 @@ -32,7 +33,7 @@ class MainApp: for sub_app in sub_apps: # debug(sub_app.name) await stack.enter_async_context(sub_app.lifespan()) - _port = os.environ.get("OPENSWARM_PORT", "8324") + _port = os.environ.get("OPENSWARM_PORT", str(BACKEND_DEV_PORT)) print(f"\nCheck out the API docs at: http://127.0.0.1:{_port}/docs\n") yield diff --git a/backend/main.py b/backend/main.py index d34bdf30..cfca4815 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,6 +1,8 @@ import logging import os +from backend.ports import get_backend_port + logger = logging.getLogger(__name__) from backend.config.Apps import MainApp @@ -68,7 +70,7 @@ if __name__ == "__main__": import uvicorn parser = argparse.ArgumentParser(description="OpenSwarm backend server") - parser.add_argument("--port", type=int, default=int(os.environ.get("OPENSWARM_PORT", "8324"))) + parser.add_argument("--port", type=int, default=get_backend_port()) parser.add_argument("--host", default=os.environ.get("OPENSWARM_HOST", "127.0.0.1")) parser.add_argument("--reload", action="store_true", default=False) args = parser.parse_args() diff --git a/backend/ports.py b/backend/ports.py new file mode 100644 index 00000000..5fecd5e9 --- /dev/null +++ b/backend/ports.py @@ -0,0 +1,23 @@ +"""Centralized port configuration. + +Reads from ports.config.json at the project root so every part of the +Python backend uses the same port numbers without hardcoding them. +""" + +import json +import os + +_config_path = os.path.join(os.path.dirname(__file__), "..", "ports.config.json") +with open(_config_path) as _f: + _cfg = json.load(_f) + +BACKEND_DEV_PORT: int = _cfg["backend"]["dev"] +BACKEND_PROD_PORT_START: int = _cfg["backend"]["prod"]["start"] +BACKEND_PROD_PORT_END: int = _cfg["backend"]["prod"]["end"] +FRONTEND_DEV_PORT: int = _cfg["frontend"]["dev"] +NINE_ROUTER_PORT: int = _cfg["nineRouter"] + + +def get_backend_port() -> int: + """Return the active backend port (env override or dev default).""" + return int(os.environ.get("OPENSWARM_PORT", str(BACKEND_DEV_PORT))) diff --git a/backend/run.sh b/backend/run.sh index e4485111..ecddd343 100755 --- a/backend/run.sh +++ b/backend/run.sh @@ -68,9 +68,12 @@ if [[ $? -ne 0 ]]; then exit 1 fi +# --- Read dev port from ports.config.json --- +BACKEND_PORT=$(python3 -c "import json; print(json.load(open('$PROJECT_ROOT_ABSPATH/ports.config.json'))['backend']['dev'])") + # --- Start the backend server --- -echo "Starting backend server on http://0.0.0.0:8324 ..." +echo "Starting backend server on http://0.0.0.0:${BACKEND_PORT} ..." cd "$PROJECT_ROOT_ABSPATH" -python3 -m uvicorn backend.main:app --host 0.0.0.0 --port 8324 --reload \ +python3 -m uvicorn backend.main:app --host 0.0.0.0 --port "$BACKEND_PORT" --reload \ --reload-dir "$BACKEND_DIR_ABSPATH" \ --reload-exclude '*.pyc' \ No newline at end of file diff --git a/electron/main.js b/electron/main.js index 7512b888..7f703ad2 100644 --- a/electron/main.js +++ b/electron/main.js @@ -8,6 +8,15 @@ const fs = require('fs'); const getPort = require('get-port'); const http = require('http'); +const portsConfig = JSON.parse( + fs.readFileSync( + app.isPackaged + ? path.join(process.resourcesPath, 'ports.config.json') + : path.join(__dirname, '..', 'ports.config.json'), + 'utf8', + ) +); + app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling'); app.commandLine.appendSwitch('ignore-gpu-blocklist'); app.commandLine.appendSwitch('enable-gpu-rasterization'); @@ -139,7 +148,7 @@ function waitForBackend(port, timeoutMs = 60000) { } async function startBackend() { - backendPort = await getPort({ port: getPort.makeRange(8324, 8424) }); + backendPort = await getPort({ port: getPort.makeRange(portsConfig.backend.prod.start, portsConfig.backend.prod.end) }); const pythonPath = getPythonPath(); const backendDir = getResourcePath('backend'); @@ -368,7 +377,7 @@ app.whenReady().then(async () => { try { if (isDev) { - backendPort = parseInt(process.env.OPENSWARM_PORT || '8324', 10); + backendPort = parseInt(process.env.OPENSWARM_PORT || String(portsConfig.backend.dev), 10); console.log(`Dev mode: using existing backend on port ${backendPort}`); } else { await startBackend(); diff --git a/electron/package.json b/electron/package.json index 90708f80..a465f84b 100644 --- a/electron/package.json +++ b/electron/package.json @@ -59,6 +59,10 @@ ] }, "extraResources": [ + { + "from": "build-staging/ports.config.json", + "to": "ports.config.json" + }, { "from": "build-staging/frontend", "to": "frontend", diff --git a/frontend/src/shared/config.ts b/frontend/src/shared/config.ts index 48854857..88d066ef 100644 --- a/frontend/src/shared/config.ts +++ b/frontend/src/shared/config.ts @@ -1,4 +1,4 @@ -const port = (window as any).__OPENSWARM_PORT__ || 8324; +const port = (window as any).__OPENSWARM_PORT__ || 8325; const host = window.location.hostname || 'localhost'; export const API_BASE = `http://${host}:${port}/api`; diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 1e051e72..cf9224b1 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -2,6 +2,8 @@ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); +const portsConfig = require('../ports.config.json'); + module.exports = (env, argv) => { const isDevelopment = argv.mode === 'development'; @@ -79,13 +81,13 @@ module.exports = (env, argv) => { devServer: { static: { directory: path.join(__dirname, 'public') }, compress: true, - port: 3000, + port: portsConfig.frontend.dev, hot: true, open: false, historyApiFallback: true, proxy: { '/api': { - target: 'http://localhost:8324', + target: `http://localhost:${portsConfig.backend.dev}`, changeOrigin: true, }, }, diff --git a/ports.config.json b/ports.config.json new file mode 100644 index 00000000..79880e61 --- /dev/null +++ b/ports.config.json @@ -0,0 +1,10 @@ +{ + "backend": { + "dev": 8325, + "prod": { "start": 8326, "end": 8424 } + }, + "frontend": { + "dev": 3000 + }, + "nineRouter": 20128 +} diff --git a/run/local.sh b/run/local.sh index 02a74885..2fa20632 100755 --- a/run/local.sh +++ b/run/local.sh @@ -85,6 +85,10 @@ if [ ! -f "$UV_BIN_DIR/uvx" ]; then rm -rf /tmp/uv-*-apple-darwin fi +# --- Read ports from config --- +BACKEND_PORT=$(python3 -c "import json; print(json.load(open('$PROJECT_ROOT/ports.config.json'))['backend']['dev'])") +FRONTEND_PORT=$(python3 -c "import json; print(json.load(open('$PROJECT_ROOT/ports.config.json'))['frontend']['dev'])") + # --- Start backend --- echo -e "${BLUE}${BOLD}[backend]${RESET} Starting backend server..." bash "$PROJECT_ROOT/backend/run.sh" > >( @@ -95,11 +99,11 @@ bash "$PROJECT_ROOT/backend/run.sh" > >( BACKEND_PID=$! # --- Wait for backend to become healthy --- -echo -e "${YELLOW}${BOLD}Waiting for backend (http://localhost:8324) to be ready...${RESET}" +echo -e "${YELLOW}${BOLD}Waiting for backend (http://localhost:${BACKEND_PORT}) to be ready...${RESET}" MAX_WAIT=120 elapsed=0 while (( elapsed < MAX_WAIT )); do - if curl -s -o /dev/null --connect-timeout 1 http://localhost:8324/ 2>/dev/null; then + if curl -s -o /dev/null --connect-timeout 1 "http://localhost:${BACKEND_PORT}/" 2>/dev/null; then echo -e "${GREEN}${BOLD}Backend is ready!${RESET}" break fi @@ -126,11 +130,11 @@ bash "$PROJECT_ROOT/frontend/run.sh" > >( FRONTEND_PID=$! # --- Wait for frontend dev server to become available --- -echo -e "${YELLOW}${BOLD}Waiting for frontend (http://localhost:3000) to be ready...${RESET}" +echo -e "${YELLOW}${BOLD}Waiting for frontend (http://localhost:${FRONTEND_PORT}) to be ready...${RESET}" FRONTEND_MAX_WAIT=60 frontend_elapsed=0 while (( frontend_elapsed < FRONTEND_MAX_WAIT )); do - if curl -s -o /dev/null --connect-timeout 1 http://localhost:3000/ 2>/dev/null; then + if curl -s -o /dev/null --connect-timeout 1 "http://localhost:${FRONTEND_PORT}/" 2>/dev/null; then echo -e "${GREEN}${BOLD}Frontend is ready!${RESET}" break fi @@ -175,8 +179,8 @@ ELECTRON_PID=$! echo "" echo -e "${BOLD}All services are running. Press Ctrl+C to stop.${RESET}" -echo -e " Backend: ${BLUE}http://localhost:8324${RESET}" -echo -e " Frontend: ${GREEN}http://localhost:3000${RESET}" +echo -e " Backend: ${BLUE}http://localhost:${BACKEND_PORT}${RESET}" +echo -e " Frontend: ${GREEN}http://localhost:${FRONTEND_PORT}${RESET}" echo -e " Electron: ${MAGENTA}dev shell (pid $ELECTRON_PID)${RESET}" echo "" diff --git a/run/utils/build-app.sh b/run/utils/build-app.sh index 1d1ad06a..8f372b64 100755 --- a/run/utils/build-app.sh +++ b/run/utils/build-app.sh @@ -118,6 +118,8 @@ STAGING_DIR="$PROJECT_ROOT/electron/build-staging" rm -rf "$STAGING_DIR" mkdir -p "$STAGING_DIR" +cp "$PROJECT_ROOT/ports.config.json" "$STAGING_DIR/ports.config.json" + rsync -a \ --exclude='__pycache__' --exclude='**/__pycache__' \ --exclude='*.pyc' --exclude='.venv' \