sync: update from private repo (f6b8c40)
Some checks failed
CI / build-and-test (push) Has been cancelled
Some checks failed
CI / build-and-test (push) Has been cancelled
This commit is contained in:
parent
a0923abc40
commit
e0c03ef10b
@ -319,6 +319,7 @@ tools:
|
||||
# プロキシが TLS を終端しているので、ここで有効にすると二重終端になり接続が壊れる。
|
||||
#
|
||||
# server:
|
||||
# port: 9876 # HTTP(S) 待ち受けポート (default 9876)。環境変数 PORT (.env 含む) が優先。反映には再起動が必要
|
||||
# tls:
|
||||
# enabled: true # フレッシュインストールのデフォルト; ブロック未記載=アップグレード時 false
|
||||
# cert_file: null # PEM 証明書パス (任意); cert_file と key_file は両方設定するか両方省略
|
||||
|
||||
@ -107,7 +107,7 @@ import { readGatewayConfig } from '../gateway/config.js';
|
||||
import { createAdminGatewayStatusRouter } from './admin-gateway-status-api.js';
|
||||
import { createServer as createHttpsServer } from 'https';
|
||||
import { X509Certificate } from 'crypto';
|
||||
import { mergeServerConfig } from '../server/config.js';
|
||||
import { mergeServerConfig, resolveListenPort } from '../server/config.js';
|
||||
import { resolveTlsOptions } from '../net/tls-options.js';
|
||||
import { createHttpRedirectServer } from '../net/http-redirect.js';
|
||||
|
||||
@ -297,7 +297,9 @@ export function createCoreServer(opts: CoreServerOptions): {
|
||||
// tls.enabled and the cookie secure flag.
|
||||
const serverCfg = mergeServerConfig(loadConfig().server, {
|
||||
freshInstall: false,
|
||||
httpsPort: opts.listenPort ?? Number(process.env['PORT'] ?? 9876),
|
||||
// startCoreServer threads the resolved port in via listenPort; the fallback
|
||||
// (for callers that bypass it) applies the same PORT-env > config precedence.
|
||||
httpsPort: opts.listenPort ?? resolveListenPort(process.env['PORT'], loadConfig().server?.port),
|
||||
});
|
||||
|
||||
if (authActive) {
|
||||
@ -1152,9 +1154,7 @@ export function createCoreServer(opts: CoreServerOptions): {
|
||||
// (startCoreServer always sets it) over the PORT env-var guess so
|
||||
// the status endpoint matches what the bridge actually bound.
|
||||
// env-var fallback is kept for callers that bypass startCoreServer.
|
||||
const envPortRaw = Number(process.env['PORT']);
|
||||
const envPort = Number.isFinite(envPortRaw) && envPortRaw > 0 ? envPortRaw : 9876;
|
||||
const actualPort = opts.listenPort ?? envPort;
|
||||
const actualPort = opts.listenPort ?? resolveListenPort(process.env['PORT'], loadConfig().server?.port);
|
||||
const statusRouter = createAdminGatewayStatusRouter({
|
||||
mount: gatewayMount,
|
||||
configManager: opts.configManager ?? null,
|
||||
@ -1267,7 +1267,9 @@ function deriveCallbackBaseUrl(authConfig: AuthConfig | undefined): string {
|
||||
}
|
||||
}
|
||||
}
|
||||
const port = Number(process.env.PORT ?? 9876);
|
||||
// Same precedence as the listener (PORT env > server.port > default) so the
|
||||
// derived OAuth callback origin matches the port the bridge actually binds.
|
||||
const port = resolveListenPort(process.env.PORT, loadConfig().server?.port);
|
||||
return `http://localhost:${port}`;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mergeServerConfig, SERVER_TLS_DEFAULTS } from './config.js';
|
||||
import { mergeServerConfig, SERVER_TLS_DEFAULTS, resolveListenPort, DEFAULT_SERVER_PORT } from './config.js';
|
||||
|
||||
describe('mergeServerConfig', () => {
|
||||
it('upgrade-safe: an absent server block disables TLS', () => {
|
||||
@ -58,4 +58,56 @@ describe('mergeServerConfig', () => {
|
||||
mergeServerConfig({ tls: { enabled: false, certFile: '/x/c.pem' } }, { freshInstall: false }),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('resolves port: httpsPort wins, else config port, else default', () => {
|
||||
expect(mergeServerConfig({ port: 3000 }, { freshInstall: false }).port).toBe(3000);
|
||||
expect(mergeServerConfig({ port: 3000 }, { freshInstall: false, httpsPort: 8443 }).port).toBe(8443);
|
||||
expect(mergeServerConfig(undefined, { freshInstall: false }).port).toBe(DEFAULT_SERVER_PORT);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveListenPort', () => {
|
||||
it('PORT env wins over config port and default', () => {
|
||||
expect(resolveListenPort('4000', 5000)).toBe(4000);
|
||||
});
|
||||
|
||||
it('falls back to config port when env is unset/empty', () => {
|
||||
expect(resolveListenPort(undefined, 5000)).toBe(5000);
|
||||
expect(resolveListenPort('', 5000)).toBe(5000);
|
||||
});
|
||||
|
||||
it('falls back to the default when neither is set', () => {
|
||||
expect(resolveListenPort(undefined, undefined)).toBe(DEFAULT_SERVER_PORT);
|
||||
});
|
||||
|
||||
it('ignores an invalid env PORT and warns, then uses config', () => {
|
||||
const warns: string[] = [];
|
||||
expect(resolveListenPort('not-a-port', 5000, (m) => warns.push(m))).toBe(5000);
|
||||
expect(resolveListenPort('70000', 5000, (m) => warns.push(m))).toBe(5000); // out of range
|
||||
expect(warns.length).toBe(2);
|
||||
});
|
||||
|
||||
it('ignores an invalid config port and warns, then uses the default', () => {
|
||||
const warns: string[] = [];
|
||||
expect(resolveListenPort(undefined, 0, (m) => warns.push(m))).toBe(DEFAULT_SERVER_PORT);
|
||||
expect(resolveListenPort(undefined, 99999, (m) => warns.push(m))).toBe(DEFAULT_SERVER_PORT);
|
||||
expect(resolveListenPort(undefined, 1.5, (m) => warns.push(m))).toBe(DEFAULT_SERVER_PORT); // non-integer
|
||||
expect(warns.length).toBe(3);
|
||||
});
|
||||
|
||||
it('accepts the boundary ports 1 and 65535', () => {
|
||||
expect(resolveListenPort('1', undefined)).toBe(1);
|
||||
expect(resolveListenPort('65535', undefined)).toBe(65535);
|
||||
});
|
||||
|
||||
it('rejects env "0" and falls through', () => {
|
||||
expect(resolveListenPort('0', 5000)).toBe(5000);
|
||||
expect(resolveListenPort('0', undefined)).toBe(DEFAULT_SERVER_PORT);
|
||||
});
|
||||
|
||||
it('accepts a quoted-string config port symmetrically with env', () => {
|
||||
expect(resolveListenPort(undefined, '5000' as unknown as number)).toBe(5000);
|
||||
expect(resolveListenPort(undefined, '' as unknown as number)).toBe(DEFAULT_SERVER_PORT);
|
||||
expect(resolveListenPort(undefined, 'abc' as unknown as number)).toBe(DEFAULT_SERVER_PORT);
|
||||
});
|
||||
});
|
||||
|
||||
@ -16,9 +16,42 @@ export interface ServerTlsConfig {
|
||||
}
|
||||
|
||||
export interface ServerConfig {
|
||||
/** HTTP(S) listen port. Resolved by mergeServerConfig (default 9876). */
|
||||
port: number;
|
||||
tls: ServerTlsConfig;
|
||||
}
|
||||
|
||||
/** Default HTTP(S) listen port when neither PORT nor server.port is set. */
|
||||
export const DEFAULT_SERVER_PORT = 9876;
|
||||
|
||||
/**
|
||||
* Resolve the effective listen port from the precedence
|
||||
* `PORT env > server.port (config) > default`. Invalid values (non-integer or
|
||||
* out of the 1–65535 range) are ignored with a caller-supplied warn hook so a
|
||||
* typo can't bind port 0 / NaN. Pure: the env string is passed in.
|
||||
*/
|
||||
export function resolveListenPort(
|
||||
envPort: string | undefined,
|
||||
configPort: number | string | undefined,
|
||||
warn?: (msg: string) => void,
|
||||
): number {
|
||||
const isValid = (n: unknown): n is number =>
|
||||
typeof n === 'number' && Number.isInteger(n) && n >= 1 && n <= 65535;
|
||||
|
||||
if (envPort !== undefined && envPort !== '') {
|
||||
const n = Number(envPort);
|
||||
if (isValid(n)) return n;
|
||||
warn?.(`[server] ignoring invalid PORT env "${envPort}" (want an integer 1–65535)`);
|
||||
}
|
||||
if (configPort !== undefined && configPort !== '') {
|
||||
// Accept a numeric YAML value or a quoted "9876" string symmetrically with env.
|
||||
const n = typeof configPort === 'string' ? Number(configPort) : configPort;
|
||||
if (isValid(n)) return n;
|
||||
warn?.(`[server] ignoring invalid server.port "${configPort}" (want an integer 1–65535)`);
|
||||
}
|
||||
return DEFAULT_SERVER_PORT;
|
||||
}
|
||||
|
||||
export const SERVER_TLS_DEFAULTS: ServerTlsConfig = {
|
||||
enabled: false,
|
||||
certFile: null,
|
||||
@ -33,6 +66,11 @@ export const SERVER_TLS_DEFAULTS: ServerTlsConfig = {
|
||||
|
||||
export interface MergeServerOpts {
|
||||
freshInstall: boolean;
|
||||
/**
|
||||
* The already-resolved effective listen port (PORT env > server.port >
|
||||
* default), threaded in by the caller. Becomes the merged `port` and is the
|
||||
* value the http_redirect_port collision check compares against.
|
||||
*/
|
||||
httpsPort?: number;
|
||||
}
|
||||
|
||||
@ -59,5 +97,8 @@ export function mergeServerConfig(
|
||||
throw new Error(`server.tls: http_redirect_port (${tls.httpRedirectPort}) must differ from the HTTPS port`);
|
||||
}
|
||||
}
|
||||
return { tls };
|
||||
// opts.httpsPort is the already-resolved listen port in production; the
|
||||
// fallback validates/coerces a raw config value for callers that don't pass it.
|
||||
const port = opts.httpsPort ?? resolveListenPort(undefined, partial?.port);
|
||||
return { port, tls };
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
*/
|
||||
import { Repository, BrowserSessionRepo } from './db/repository.js';
|
||||
import { startCoreServer } from './bridge/server.js';
|
||||
import { resolveListenPort } from './server/config.js';
|
||||
import { runMigrations } from './db/migrate.js';
|
||||
import { logger } from './logger.js';
|
||||
import { accessSync, existsSync, mkdirSync, constants } from 'fs';
|
||||
@ -147,7 +148,10 @@ export async function start(opts: StartWorkerOptions = {}): Promise<void> {
|
||||
const workerManager = new WorkerManager(repo, configManager);
|
||||
workerManager.start();
|
||||
|
||||
const port = parseInt(process.env['PORT'] ?? '9876', 10);
|
||||
// Listen port precedence: PORT env (incl. .env loaded by scripts/server.sh)
|
||||
// > config.yaml server.port > default 9876. UI/config changes apply on the
|
||||
// next restart (no live re-bind), same as the TLS settings.
|
||||
const port = resolveListenPort(process.env['PORT'], config.server?.port, (m) => logger.warn(m));
|
||||
|
||||
// タイトル自動生成関数を作成(roles に 'title' を持つ worker を優先、なければ最初の worker)
|
||||
const titleWorker =
|
||||
|
||||
@ -23,6 +23,7 @@ export function ServerTlsForm({ config, onChange }: SectionFormProps) {
|
||||
|
||||
// Navigate to the server.tls sub-object; fall back to empty object if absent.
|
||||
const tls = (config?.server?.tls) ?? {};
|
||||
const server = (config?.server) ?? {};
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
@ -35,6 +36,21 @@ export function ServerTlsForm({ config, onChange }: SectionFormProps) {
|
||||
{t('serverTls.restartBanner')}
|
||||
</div>
|
||||
|
||||
{/* HTTP(S) listen port */}
|
||||
<div>
|
||||
<FieldLabel>{t('serverTls.port')}</FieldLabel>
|
||||
<FieldInput
|
||||
type="number"
|
||||
value={server.port != null ? String(server.port) : ''}
|
||||
onChange={v => {
|
||||
const n = parseInt(v, 10);
|
||||
onChange('server.port', isNaN(n) ? undefined : n);
|
||||
}}
|
||||
placeholder="9876"
|
||||
/>
|
||||
<HelpText>{t('serverTls.portHelp')}</HelpText>
|
||||
</div>
|
||||
|
||||
{/* Enable HTTPS */}
|
||||
<div>
|
||||
<label className="inline-flex items-center gap-2 text-[13px] text-slate-700 dark:text-slate-200">
|
||||
|
||||
@ -782,6 +782,8 @@
|
||||
},
|
||||
"serverTls": {
|
||||
"title": "HTTPS / TLS",
|
||||
"port": "HTTP(S) port",
|
||||
"portHelp": "Port the server listens on (default 9876). The PORT environment variable (incl. .env) takes precedence over this value. Requires a restart to apply.",
|
||||
"enabled": "Serve over HTTPS",
|
||||
"enabledHelp": "Terminate TLS in the app. Self-signed by default — browsers (and the noVNC / SSH console over wss) will warn until you install a real certificate. Requires a restart to apply.",
|
||||
"certFile": "Certificate file (PEM)",
|
||||
|
||||
@ -782,6 +782,8 @@
|
||||
},
|
||||
"serverTls": {
|
||||
"title": "HTTPS / TLS",
|
||||
"port": "HTTP(S) ポート",
|
||||
"portHelp": "サーバーが待ち受けるポート番号(デフォルト 9876)。環境変数 PORT(.env 含む)がこの値より優先されます。反映にはサーバーの再起動が必要です。",
|
||||
"enabled": "HTTPS で配信",
|
||||
"enabledHelp": "アプリ内で TLS を終端します。デフォルトは自己署名証明書のため、正式な証明書を導入するまでブラウザ(および wss 経由の noVNC・SSH コンソール)に警告が表示されます。反映にはサーバーの再起動が必要です。",
|
||||
"certFile": "証明書ファイル(PEM)",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user