From 9e335fa3053cffbe5f4c6025c8f091bb1a580c0d Mon Sep 17 00:00:00 2001 From: weishu Date: Mon, 19 Jan 2026 12:57:37 +0800 Subject: [PATCH] refactor: rename configuration variables for clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Standardize naming across CLI and server components: - CLI: serverUrl → apiUrl, HAPI_SERVER_URL → HAPI_API_URL - Server: webapp* → listen*, miniAppUrl → publicUrl, WEBAPP_* → HAPI_LISTEN_*, WEBAPP_URL → HAPI_PUBLIC_URL - Rename serverUrlInit.ts → apiUrlInit.ts with updated logic for backward compatibility - Update all imports, function calls, and documentation accordingly --- .github/workflows/test.yml | 2 +- cli/CLAUDE.md | 2 +- cli/README.md | 2 +- cli/src/api/api.ts | 4 +- cli/src/api/apiMachine.ts | 2 +- cli/src/api/apiSession.ts | 4 +- cli/src/commands/auth.ts | 2 +- cli/src/commands/claude.ts | 2 +- cli/src/configuration.ts | 12 +- cli/src/persistence.ts | 4 +- cli/src/runner/runner.integration.test.ts | 4 +- cli/src/terminal/TerminalManager.ts | 2 +- .../ui/{serverUrlInit.ts => apiUrlInit.ts} | 19 +-- cli/src/ui/doctor.ts | 8 +- cli/src/ui/logger.ts | 6 +- cli/src/ui/tokenInit.ts | 6 +- cli/src/utils/autoStartServer.ts | 16 +-- docs/guide/faq.md | 2 +- docs/guide/installation.md | 12 +- server/README.md | 13 +- server/src/config/serverSettings.ts | 113 +++++++++++------- server/src/config/settings.ts | 6 +- server/src/configuration.ts | 29 ++--- server/src/index.ts | 16 +-- server/src/telegram/bot.ts | 14 +-- server/src/telegram/sessionView.ts | 6 +- server/src/web/server.ts | 8 +- 27 files changed, 176 insertions(+), 140 deletions(-) rename cli/src/ui/{serverUrlInit.ts => apiUrlInit.ts} (55%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1d5190a5..6a4a2856 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: run: | { echo "HAPI_HOME=~/.hapi-dev-test" - echo "HAPI_SERVER_URL=http://localhost:3006" + echo "HAPI_API_URL=http://localhost:3006" echo "CLI_API_TOKEN=${CLI_API_TOKEN:-dev-test-token}" echo "HAPI_DAEMON_HTTP_TIMEOUT=60000" echo "HAPI_DAEMON_HEARTBEAT_INTERVAL=30000" diff --git a/cli/CLAUDE.md b/cli/CLAUDE.md index 60c19698..8d3a4d02 100644 --- a/cli/CLAUDE.md +++ b/cli/CLAUDE.md @@ -139,7 +139,7 @@ User interface components. hapi runner start # With custom bot URL (for local development): -HAPI_SERVER_URL=http://localhost:3006 CLI_API_TOKEN=your_token hapi runner start +HAPI_API_URL=http://localhost:3006 CLI_API_TOKEN=your_token hapi runner start # Stop the runner: hapi runner stop diff --git a/cli/README.md b/cli/README.md index 35f9b32e..a58afdce 100644 --- a/cli/README.md +++ b/cli/README.md @@ -67,7 +67,7 @@ See `src/configuration.ts` for all options. ### Required - `CLI_API_TOKEN` - Shared secret; must match the server. Can be set via env or `~/.hapi/settings.json` (env wins). -- `HAPI_SERVER_URL` - Server base URL (default: http://localhost:3006). +- `HAPI_API_URL` - Server base URL (default: http://localhost:3006). ### Optional diff --git a/cli/src/api/api.ts b/cli/src/api/api.ts index a1402feb..0ad52f33 100644 --- a/cli/src/api/api.ts +++ b/cli/src/api/api.ts @@ -19,7 +19,7 @@ export class ApiClient { state: AgentState | null }): Promise { const response = await axios.post( - `${configuration.serverUrl}/cli/sessions`, + `${configuration.apiUrl}/cli/sessions`, { tag: opts.tag, metadata: opts.metadata, @@ -79,7 +79,7 @@ export class ApiClient { runnerState?: RunnerState }): Promise { const response = await axios.post( - `${configuration.serverUrl}/cli/machines`, + `${configuration.apiUrl}/cli/machines`, { id: opts.machineId, metadata: opts.metadata, diff --git a/cli/src/api/apiMachine.ts b/cli/src/api/apiMachine.ts index c6c87412..b96b494e 100644 --- a/cli/src/api/apiMachine.ts +++ b/cli/src/api/apiMachine.ts @@ -215,7 +215,7 @@ export class ApiMachineClient { } connect(): void { - this.socket = io(`${configuration.serverUrl}/cli`, { + this.socket = io(`${configuration.apiUrl}/cli`, { transports: ['websocket'], auth: { token: this.token, diff --git a/cli/src/api/apiSession.ts b/cli/src/api/apiSession.ts index aeca16ac..7386c548 100644 --- a/cli/src/api/apiSession.ts +++ b/cli/src/api/apiSession.ts @@ -71,7 +71,7 @@ export class ApiSessionClient extends EventEmitter { registerCommonHandlers(this.rpcHandlerManager, this.metadata.path) } - this.socket = io(`${configuration.serverUrl}/cli`, { + this.socket = io(`${configuration.apiUrl}/cli`, { auth: { token: this.token, clientType: 'session-scoped' as const, @@ -270,7 +270,7 @@ export class ApiSessionClient extends EventEmitter { let cursor = startSeq while (true) { const response = await axios.get( - `${configuration.serverUrl}/cli/sessions/${encodeURIComponent(this.sessionId)}/messages`, + `${configuration.apiUrl}/cli/sessions/${encodeURIComponent(this.sessionId)}/messages`, { params: { afterSeq: cursor, limit }, headers: { diff --git a/cli/src/commands/auth.ts b/cli/src/commands/auth.ts index 3bc14eaf..486be85c 100644 --- a/cli/src/commands/auth.ts +++ b/cli/src/commands/auth.ts @@ -21,7 +21,7 @@ export async function handleAuthCommand(args: string[]): Promise { const hasToken = Boolean(envToken || settingsToken) const tokenSource = envToken ? 'environment' : (settingsToken ? 'settings file' : 'none') console.log(chalk.bold('\nDirect Connect Status\n')) - console.log(chalk.gray(` HAPI_SERVER_URL: ${configuration.serverUrl}`)) + console.log(chalk.gray(` HAPI_API_URL: ${configuration.apiUrl}`)) console.log(chalk.gray(` CLI_API_TOKEN: ${hasToken ? 'set' : 'missing'}`)) console.log(chalk.gray(` Token Source: ${tokenSource}`)) console.log(chalk.gray(` Machine ID: ${settings.machineId ?? 'not set'}`)) diff --git a/cli/src/commands/claude.ts b/cli/src/commands/claude.ts index 38db4c0f..53b16042 100644 --- a/cli/src/commands/claude.ts +++ b/cli/src/commands/claude.ts @@ -139,7 +139,7 @@ ${chalk.bold.cyan('Claude Code Options (from `claude --help`):')} messageLower.includes('network error') ) { console.error(chalk.yellow('Unable to connect to HAPI server')) - console.error(chalk.gray(` Server URL: ${configuration.serverUrl}`)) + console.error(chalk.gray(` Server URL: ${configuration.apiUrl}`)) console.error(chalk.gray(' Please check your network connection or server status')) } else if (httpStatus === 403 && responseErrorText === 'Machine access denied') { console.error(chalk.red('Machine access denied.')) diff --git a/cli/src/configuration.ts b/cli/src/configuration.ts index 93c9817f..ac65d6ac 100644 --- a/cli/src/configuration.ts +++ b/cli/src/configuration.ts @@ -12,7 +12,7 @@ import packageJson from '../package.json' import { getCliArgs } from '@/utils/cliArgs' class Configuration { - private _serverUrl: string + private _apiUrl: string private _cliApiToken: string public readonly isRunnerProcess: boolean @@ -29,7 +29,7 @@ class Configuration { constructor() { // Server configuration - this._serverUrl = process.env.HAPI_SERVER_URL || 'http://localhost:3006' + this._apiUrl = process.env.HAPI_API_URL || 'http://localhost:3006' this._cliApiToken = process.env.CLI_API_TOKEN || '' // Check if we're running as runner based on process args @@ -64,12 +64,12 @@ class Configuration { } } - get serverUrl(): string { - return this._serverUrl + get apiUrl(): string { + return this._apiUrl } - _setServerUrl(url: string): void { - this._serverUrl = url + _setApiUrl(url: string): void { + this._apiUrl = url } get cliApiToken(): string { diff --git a/cli/src/persistence.ts b/cli/src/persistence.ts index e17cfaa5..69ea7c0c 100644 --- a/cli/src/persistence.ts +++ b/cli/src/persistence.ts @@ -17,7 +17,9 @@ interface Settings { machineIdConfirmedByServer?: boolean runnerAutoStartWhenRunningHappy?: boolean cliApiToken?: string - // Server URL for API connections (priority: env HAPI_SERVER_URL > this > default) + // API URL for server connections (priority: env HAPI_API_URL > this > default) + apiUrl?: string + // Legacy field name (for migration, read-only) serverUrl?: string } diff --git a/cli/src/runner/runner.integration.test.ts b/cli/src/runner/runner.integration.test.ts index 98d403c5..207056fe 100644 --- a/cli/src/runner/runner.integration.test.ts +++ b/cli/src/runner/runner.integration.test.ts @@ -11,7 +11,7 @@ * * The integration test environment uses .env.integration-test which sets: * - HAPI_HOME=~/.hapi-dev-test (DIFFERENT from dev's ~/.hapi-dev!) - * - HAPI_SERVER_URL=http://localhost:3006 (local hapi-server) + * - HAPI_API_URL=http://localhost:3006 (local hapi-server) * - CLI_API_TOKEN=... (must match the server) */ @@ -56,7 +56,7 @@ async function isServerHealthy(): Promise { return false; } - const url = `${configuration.serverUrl}/cli/machines/__healthcheck__`; + const url = `${configuration.apiUrl}/cli/machines/__healthcheck__`; const response = await fetch(url, { headers: { Authorization: `Bearer ${configuration.cliApiToken}` }, signal: AbortSignal.timeout(1000) diff --git a/cli/src/terminal/TerminalManager.ts b/cli/src/terminal/TerminalManager.ts index 2f222ae4..d918c061 100644 --- a/cli/src/terminal/TerminalManager.ts +++ b/cli/src/terminal/TerminalManager.ts @@ -28,7 +28,7 @@ const DEFAULT_IDLE_TIMEOUT_MS = 15 * 60_000 const DEFAULT_MAX_TERMINALS = 4 const SENSITIVE_ENV_KEYS = new Set([ 'CLI_API_TOKEN', - 'HAPI_SERVER_URL', + 'HAPI_API_URL', 'HAPI_HTTP_MCP_URL', 'TELEGRAM_BOT_TOKEN', 'OPENAI_API_KEY', diff --git a/cli/src/ui/serverUrlInit.ts b/cli/src/ui/apiUrlInit.ts similarity index 55% rename from cli/src/ui/serverUrlInit.ts rename to cli/src/ui/apiUrlInit.ts index cc65d5cb..f173d58f 100644 --- a/cli/src/ui/serverUrlInit.ts +++ b/cli/src/ui/apiUrlInit.ts @@ -1,7 +1,7 @@ /** - * Server URL initialization module + * API URL initialization module * - * Handles HAPI_SERVER_URL initialization with priority: + * Handles HAPI_API_URL initialization with priority: * 1. Environment variable (highest - allows temporary override) * 2. Settings file (~/.hapi/settings.json) * 3. Default value (http://localhost:3006) @@ -11,19 +11,24 @@ import { configuration } from '@/configuration' import { readSettings } from '@/persistence' /** - * Initialize server URL + * Initialize API URL * Must be called before any API operations */ -export async function initializeServerUrl(): Promise { +export async function initializeApiUrl(): Promise { // 1. Environment variable has highest priority (allows temporary override) - if (process.env.HAPI_SERVER_URL) { + if (process.env.HAPI_API_URL) { return } - // 2. Read from settings file + // 2. Read from settings file (new name first, then legacy) const settings = await readSettings() + if (settings.apiUrl) { + configuration._setApiUrl(settings.apiUrl) + return + } if (settings.serverUrl) { - configuration._setServerUrl(settings.serverUrl) + // Migrate from legacy field name + configuration._setApiUrl(settings.serverUrl) return } diff --git a/cli/src/ui/doctor.ts b/cli/src/ui/doctor.ts index 3ce1e78c..62e905e5 100644 --- a/cli/src/ui/doctor.ts +++ b/cli/src/ui/doctor.ts @@ -24,7 +24,7 @@ export function getEnvironmentInfo(): Record { return { PWD: process.env.PWD, HAPI_HOME: process.env.HAPI_HOME, - HAPI_SERVER_URL: process.env.HAPI_SERVER_URL, + HAPI_API_URL: process.env.HAPI_API_URL, HAPI_PROJECT_ROOT: process.env.HAPI_PROJECT_ROOT, CLI_API_TOKEN_SET: Boolean(process.env.CLI_API_TOKEN), DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING: process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING, @@ -33,7 +33,7 @@ export function getEnvironmentInfo(): Record { workingDirectory: process.cwd(), processArgv: process.argv, happyDir: configuration?.happyHomeDir, - serverUrl: configuration?.serverUrl, + apiUrl: configuration?.apiUrl, logsDir: configuration?.logsDir, processPid: process.pid, nodeVersion: process.version, @@ -107,14 +107,14 @@ export async function runDoctorCommand(filter?: 'all' | 'runner'): Promise // Configuration console.log(chalk.bold('⚙️ Configuration')); console.log(`hapi Home: ${chalk.blue(configuration.happyHomeDir)}`); - console.log(`Bot URL: ${chalk.blue(configuration.serverUrl)}`); + console.log(`Bot URL: ${chalk.blue(configuration.apiUrl)}`); console.log(`Logs Dir: ${chalk.blue(configuration.logsDir)}`); // Environment console.log(chalk.bold('\n🌍 Environment Variables')); const env = getEnvironmentInfo(); console.log(`HAPI_HOME: ${env.HAPI_HOME ? chalk.green(env.HAPI_HOME) : chalk.gray('not set')}`); - console.log(`HAPI_SERVER_URL: ${env.HAPI_SERVER_URL ? chalk.green(env.HAPI_SERVER_URL) : chalk.gray('not set')}`); + console.log(`HAPI_API_URL: ${env.HAPI_API_URL ? chalk.green(env.HAPI_API_URL) : chalk.gray('not set')}`); console.log(`CLI_API_TOKEN: ${env.CLI_API_TOKEN_SET ? chalk.green('set') : chalk.gray('not set')}`); console.log(`DANGEROUSLY_LOG_TO_SERVER: ${env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING ? chalk.yellow('ENABLED') : chalk.gray('not set')}`); console.log(`DEBUG: ${env.DEBUG ? chalk.green(env.DEBUG) : chalk.gray('not set')}`); diff --git a/cli/src/ui/logger.ts b/cli/src/ui/logger.ts index 4c45fd9c..3ebb34d8 100644 --- a/cli/src/ui/logger.ts +++ b/cli/src/ui/logger.ts @@ -50,10 +50,10 @@ class Logger { constructor( public readonly logFilePath = getSessionLogPath() ) { - // Remote logging enabled only when explicitly set with server URL + // Remote logging enabled only when explicitly set with API URL if (process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING - && process.env.HAPI_SERVER_URL) { - this.dangerouslyUnencryptedServerLoggingUrl = process.env.HAPI_SERVER_URL + && process.env.HAPI_API_URL) { + this.dangerouslyUnencryptedServerLoggingUrl = process.env.HAPI_API_URL console.log(chalk.yellow('[REMOTE LOGGING] Sending logs to server for AI debugging')) } } diff --git a/cli/src/ui/tokenInit.ts b/cli/src/ui/tokenInit.ts index 337f7b9b..a3ac529a 100644 --- a/cli/src/ui/tokenInit.ts +++ b/cli/src/ui/tokenInit.ts @@ -12,15 +12,15 @@ import { stdin as input, stdout as output } from 'node:process' import chalk from 'chalk' import { configuration } from '@/configuration' import { readSettings, updateSettings } from '@/persistence' -import { initializeServerUrl } from '@/ui/serverUrlInit' +import { initializeApiUrl } from '@/ui/apiUrlInit' /** * Initialize CLI API token * Must be called before any API operations */ export async function initializeToken(): Promise { - // Initialize server URL first (env > settings.json > default) - await initializeServerUrl() + // Initialize API URL first (env > settings.json > default) + await initializeApiUrl() // 1. Environment variable has highest priority (allows temporary override) if (configuration.cliApiToken) { diff --git a/cli/src/utils/autoStartServer.ts b/cli/src/utils/autoStartServer.ts index c584c708..27441885 100644 --- a/cli/src/utils/autoStartServer.ts +++ b/cli/src/utils/autoStartServer.ts @@ -3,7 +3,7 @@ * * Automatically starts the HAPI server when CLI is launched * if specific conditions are met: - * 1. HAPI_SERVER_URL is not set (using default localhost:3006) + * 1. HAPI_API_URL is not set (using default localhost:3006) * 2. cliApiToken exists in settings.json (server was previously started) * 3. Port 3006 is not currently listening */ @@ -90,18 +90,18 @@ async function waitForServerReady( * Determine if server should be auto-started */ async function shouldAutoStartServer(): Promise { - // Condition 1: HAPI_SERVER_URL not set (using default localhost:3006) - if (process.env.HAPI_SERVER_URL) { - logger.debug('[AUTO-START] HAPI_SERVER_URL is set, skipping auto-start') + // Condition 1: HAPI_API_URL not set (using default localhost:3006) + if (process.env.HAPI_API_URL) { + logger.debug('[AUTO-START] HAPI_API_URL is set, skipping auto-start') return false } // Condition 2: Check settings.json const settings = await readSettings() - // 2a: serverUrl is set in settings.json (user configured a specific server) - if (settings.serverUrl) { - logger.debug('[AUTO-START] serverUrl is set in settings.json, skipping auto-start') + // 2a: apiUrl is set in settings.json (user configured a specific server) + if (settings.apiUrl || settings.serverUrl) { + logger.debug('[AUTO-START] apiUrl is set in settings.json, skipping auto-start') return false } @@ -154,7 +154,7 @@ export async function maybeAutoStartServer(): Promise { startServerAsChild() - const isReady = await waitForServerReady(configuration.serverUrl) + const isReady = await waitForServerReady(configuration.apiUrl) if (!isReady) { console.log(chalk.yellow('Warning: Server did not start within expected time')) diff --git a/docs/guide/faq.md b/docs/guide/faq.md index f0754032..bd9bca9b 100644 --- a/docs/guide/faq.md +++ b/docs/guide/faq.md @@ -115,7 +115,7 @@ Only if they have your access token. For additional security: - Ensure server is running: `hapi server` - Check firewall allows port 3006 -- Verify `HAPI_SERVER_URL` is correct +- Verify `HAPI_API_URL` is correct ### "Invalid token" error diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 354117e8..4346e723 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -106,8 +106,10 @@ On first run, HAPI: | Variable | Default | Description | |----------|---------|-------------| | `CLI_API_TOKEN` | Auto-generated | Shared secret for authentication | -| `HAPI_SERVER_URL` | `http://localhost:3006` | Server URL for CLI | -| `WEBAPP_PORT` | `3006` | HTTP server port | +| `HAPI_API_URL` | `http://localhost:3006` | Server URL for CLI | +| `HAPI_LISTEN_HOST` | `127.0.0.1` | HTTP server bind address | +| `HAPI_LISTEN_PORT` | `3006` | HTTP server port | +| `HAPI_PUBLIC_URL` | - | Public URL for external access | | `HAPI_HOME` | `~/.hapi` | Config directory path | | `DB_PATH` | `~/.hapi/hapi.db` | Database file path | | `CORS_ORIGINS` | - | Allowed CORS origins | @@ -118,7 +120,7 @@ On first run, HAPI: If the server is not on localhost, set these before running `hapi`: ```bash -export HAPI_SERVER_URL="http://your-server:3006" +export HAPI_API_URL="http://your-server:3006" export CLI_API_TOKEN="your-token-here" ``` @@ -154,7 +156,7 @@ If you prefer not to use the public relay (e.g., for lower latency or self-manag https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/ ```bash -export WEBAPP_URL="https://your-tunnel.trycloudflare.com" +export HAPI_PUBLIC_URL="https://your-tunnel.trycloudflare.com" hapi server ``` @@ -194,7 +196,7 @@ Enable Telegram notifications and Mini App access: ```bash export TELEGRAM_BOT_TOKEN="your-bot-token" -export WEBAPP_URL="https://your-public-url" +export HAPI_PUBLIC_URL="https://your-public-url" hapi server ``` diff --git a/server/README.md b/server/README.md index 43f9839e..d15f93b4 100644 --- a/server/README.md +++ b/server/README.md @@ -22,11 +22,12 @@ See `src/configuration.ts` for all options. ### Optional (Telegram) - `TELEGRAM_BOT_TOKEN` - Token from @BotFather. -- `WEBAPP_URL` - Public HTTPS URL for Telegram Mini App access. Also used to derive default CORS origins for the web app. +- `HAPI_PUBLIC_URL` - Public HTTPS URL for Telegram Mini App access. Also used to derive default CORS origins for the web app. ### Optional -- `WEBAPP_PORT` - HTTP port (default: 3006). +- `HAPI_LISTEN_HOST` - HTTP bind address (default: 127.0.0.1). +- `HAPI_LISTEN_PORT` - HTTP port (default: 3006). - `CORS_ORIGINS` - Comma-separated origins, or `*`. - `HAPI_HOME` - Data directory (default: ~/.hapi). - `DB_PATH` - SQLite database path (default: HAPI_HOME/hapi.db). @@ -38,13 +39,13 @@ Binary (single executable): ```bash export TELEGRAM_BOT_TOKEN="..." export CLI_API_TOKEN="shared-secret" -export WEBAPP_URL="https://your-domain.example" +export HAPI_PUBLIC_URL="https://your-domain.example" hapi server ``` If you only need web + CLI, you can omit TELEGRAM_BOT_TOKEN. -To enable Telegram, set TELEGRAM_BOT_TOKEN and WEBAPP_URL, start the server, open `/app` +To enable Telegram, set TELEGRAM_BOT_TOKEN and HAPI_PUBLIC_URL, start the server, open `/app` in the bot chat, and bind the Mini App with `CLI_API_TOKEN:` when prompted. From source: @@ -200,14 +201,14 @@ The server build output is `server/dist/index.js`, and the web assets are in `we ## Networking notes - Telegram Mini Apps require HTTPS and a public URL. If the server has no public IP, use Cloudflare Tunnel or Tailscale and set `WEBAPP_URL` to the HTTPS endpoint. -- If the web app is hosted on a different origin, set `CORS_ORIGINS` (or `WEBAPP_URL`) to include that static host origin. +- If the web app is hosted on a different origin, set `CORS_ORIGINS` (or `HAPI_PUBLIC_URL`) to include that static host origin. ## Standalone web hosting The web UI can be hosted separately from the server (for example on GitHub Pages or Cloudflare Pages): 1. Build and deploy `web/dist` from the repo root. -2. Set `CORS_ORIGINS` (or `WEBAPP_URL`) to the static host origin. +2. Set `CORS_ORIGINS` (or `HAPI_PUBLIC_URL`) to the static host origin. 3. Open the static site, click the Server button on the login screen, and enter the hapi server origin. Leaving the server override empty preserves the default same-origin behavior when the server serves the web assets directly. diff --git a/server/src/config/serverSettings.ts b/server/src/config/serverSettings.ts index 9c316b03..ab03fa8b 100644 --- a/server/src/config/serverSettings.ts +++ b/server/src/config/serverSettings.ts @@ -13,9 +13,9 @@ import { getSettingsFile, readSettings, writeSettings } from './settings' export interface ServerSettings { telegramBotToken: string | null telegramNotification: boolean - webappHost: string - webappPort: number - webappUrl: string + listenHost: string + listenPort: number + publicUrl: string corsOrigins: string[] } @@ -24,9 +24,9 @@ export interface ServerSettingsResult { sources: { telegramBotToken: 'env' | 'file' | 'default' telegramNotification: 'env' | 'file' | 'default' - webappHost: 'env' | 'file' | 'default' - webappPort: 'env' | 'file' | 'default' - webappUrl: 'env' | 'file' | 'default' + listenHost: 'env' | 'file' | 'default' + listenPort: 'env' | 'file' | 'default' + publicUrl: 'env' | 'file' | 'default' corsOrigins: 'env' | 'file' | 'default' } savedToFile: boolean @@ -58,11 +58,11 @@ function parseCorsOrigins(str: string): string[] { } /** - * Derive CORS origins from webapp URL + * Derive CORS origins from public URL */ -function deriveCorsOrigins(webappUrl: string): string[] { +function deriveCorsOrigins(publicUrl: string): string[] { try { - return [new URL(webappUrl).origin] + return [new URL(publicUrl).origin] } catch { return [] } @@ -87,9 +87,9 @@ export async function loadServerSettings(dataDir: string): Promise file > null @@ -120,53 +120,74 @@ export async function loadServerSettings(dataDir: string): Promise file > 127.0.0.1 - let webappHost = '127.0.0.1' - if (process.env.WEBAPP_HOST) { - webappHost = process.env.WEBAPP_HOST - sources.webappHost = 'env' - if (settings.webappHost === undefined) { - settings.webappHost = webappHost + // listenHost: env > file (new or old name) > default + let listenHost = '127.0.0.1' + if (process.env.HAPI_LISTEN_HOST) { + listenHost = process.env.HAPI_LISTEN_HOST + sources.listenHost = 'env' + if (settings.listenHost === undefined) { + settings.listenHost = listenHost needsSave = true } + } else if (settings.listenHost !== undefined) { + listenHost = settings.listenHost + sources.listenHost = 'file' } else if (settings.webappHost !== undefined) { - webappHost = settings.webappHost - sources.webappHost = 'file' + // Migrate from old field name + listenHost = settings.webappHost + sources.listenHost = 'file' + settings.listenHost = listenHost + delete settings.webappHost + needsSave = true } - // webappPort: env > file > 3006 - let webappPort = 3006 - if (process.env.WEBAPP_PORT) { - const parsed = parseInt(process.env.WEBAPP_PORT, 10) + // listenPort: env > file (new or old name) > default + let listenPort = 3006 + if (process.env.HAPI_LISTEN_PORT) { + const parsed = parseInt(process.env.HAPI_LISTEN_PORT, 10) if (!Number.isFinite(parsed) || parsed <= 0) { - throw new Error('WEBAPP_PORT must be a valid port number') + throw new Error('HAPI_LISTEN_PORT must be a valid port number') } - webappPort = parsed - sources.webappPort = 'env' - if (settings.webappPort === undefined) { - settings.webappPort = webappPort + listenPort = parsed + sources.listenPort = 'env' + if (settings.listenPort === undefined) { + settings.listenPort = listenPort needsSave = true } + } else if (settings.listenPort !== undefined) { + listenPort = settings.listenPort + sources.listenPort = 'file' } else if (settings.webappPort !== undefined) { - webappPort = settings.webappPort - sources.webappPort = 'file' + // Migrate from old field name + listenPort = settings.webappPort + sources.listenPort = 'file' + settings.listenPort = listenPort + delete settings.webappPort + needsSave = true } - // webappUrl: env > file > http://localhost:{port} - let webappUrl = `http://localhost:${webappPort}` - if (process.env.WEBAPP_URL) { - webappUrl = process.env.WEBAPP_URL - sources.webappUrl = 'env' - if (settings.webappUrl === undefined) { - settings.webappUrl = webappUrl + // publicUrl: env > file (new or old name) > default + let publicUrl = `http://localhost:${listenPort}` + if (process.env.HAPI_PUBLIC_URL) { + publicUrl = process.env.HAPI_PUBLIC_URL + sources.publicUrl = 'env' + if (settings.publicUrl === undefined) { + settings.publicUrl = publicUrl needsSave = true } + } else if (settings.publicUrl !== undefined) { + publicUrl = settings.publicUrl + sources.publicUrl = 'file' } else if (settings.webappUrl !== undefined) { - webappUrl = settings.webappUrl - sources.webappUrl = 'file' + // Migrate from old field name + publicUrl = settings.webappUrl + sources.publicUrl = 'file' + settings.publicUrl = publicUrl + delete settings.webappUrl + needsSave = true } - // corsOrigins: env > file > derived from webappUrl + // corsOrigins: env > file > derived from publicUrl let corsOrigins: string[] if (process.env.CORS_ORIGINS) { corsOrigins = parseCorsOrigins(process.env.CORS_ORIGINS) @@ -179,7 +200,7 @@ export async function loadServerSettings(dataDir: string): Promise private syncEngine: SyncEngine | null = null private isRunning = false - private readonly miniAppUrl: string + private readonly publicUrl: string private readonly store: Store constructor(config: HappyBotConfig) { this.syncEngine = config.syncEngine - this.miniAppUrl = config.miniAppUrl + this.publicUrl = config.publicUrl this.store = config.store this.bot = new Bot(config.botToken) @@ -108,13 +108,13 @@ export class HappyBot implements NotificationChannel { private setupCommands(): void { // /app - Open Telegram Mini App (primary entry point) this.bot.command('app', async (ctx) => { - const keyboard = new InlineKeyboard().webApp('Open App', this.miniAppUrl) + const keyboard = new InlineKeyboard().webApp('Open App', this.publicUrl) await ctx.reply('Open HAPI Mini App:', { reply_markup: keyboard }) }) // /start - Simple welcome with Mini App link this.bot.command('start', async (ctx) => { - const keyboard = new InlineKeyboard().webApp('Open App', this.miniAppUrl) + const keyboard = new InlineKeyboard().webApp('Open App', this.publicUrl) await ctx.reply( 'Welcome to HAPI Bot!\n\n' + 'Use the Mini App for full session management.', @@ -190,7 +190,7 @@ export class HappyBot implements NotificationChannel { } const agentName = getAgentName(session) - const url = buildMiniAppDeepLink(this.miniAppUrl, `session_${session.id}`) + const url = buildMiniAppDeepLink(this.publicUrl, `session_${session.id}`) const keyboard = new InlineKeyboard() .webApp('Open Session', url) @@ -221,7 +221,7 @@ export class HappyBot implements NotificationChannel { } const text = formatSessionNotification(session) - const keyboard = createNotificationKeyboard(session, this.miniAppUrl) + const keyboard = createNotificationKeyboard(session, this.publicUrl) const chatIds = this.getBoundChatIds(session.namespace) if (chatIds.length === 0) { diff --git a/server/src/telegram/sessionView.ts b/server/src/telegram/sessionView.ts index 5795f68c..678870f8 100644 --- a/server/src/telegram/sessionView.ts +++ b/server/src/telegram/sessionView.ts @@ -38,7 +38,7 @@ export function formatSessionNotification(session: Session): string { /** * Create notification keyboard for quick actions */ -export function createNotificationKeyboard(session: Session, miniAppUrl: string): InlineKeyboard { +export function createNotificationKeyboard(session: Session, publicUrl: string): InlineKeyboard { const keyboard = new InlineKeyboard() const requests = session.agentState?.requests ?? null const hasRequests = Boolean(requests && Object.keys(requests).length > 0) @@ -55,14 +55,14 @@ export function createNotificationKeyboard(session: Session, miniAppUrl: string) keyboard.webApp( 'Details', - buildMiniAppDeepLink(miniAppUrl, `session_${session.id}`) + buildMiniAppDeepLink(publicUrl, `session_${session.id}`) ) return keyboard } keyboard.webApp( 'Open Session', - buildMiniAppDeepLink(miniAppUrl, `session_${session.id}`) + buildMiniAppDeepLink(publicUrl, `session_${session.id}`) ) return keyboard } diff --git a/server/src/web/server.ts b/server/src/web/server.ts index 11d5f78e..c5136d73 100644 --- a/server/src/web/server.ts +++ b/server/src/web/server.ts @@ -228,8 +228,8 @@ export async function startWebServer(options: { const socketHandler = options.socketEngine.handler() const server = Bun.serve({ - hostname: configuration.webappHost, - port: configuration.webappPort, + hostname: configuration.listenHost, + port: configuration.listenPort, idleTimeout: Math.max(30, socketHandler.idleTimeout), maxRequestBodySize: socketHandler.maxRequestBodySize, websocket: socketHandler.websocket, @@ -242,8 +242,8 @@ export async function startWebServer(options: { } }) - console.log(`[Web] Mini App server listening on ${configuration.webappHost}:${configuration.webappPort}`) - console.log(`[Web] Mini App public URL: ${configuration.miniAppUrl}`) + console.log(`[Web] server listening on ${configuration.listenHost}:${configuration.listenPort}`) + console.log(`[Web] public URL: ${configuration.publicUrl}`) return server }