Add server variables
All checks were successful
Queue Release Build / prepare (push) Successful in 21s
Deploy Web Apps / deploy (push) Successful in 15m14s
Queue Release Build / build-linux (push) Successful in 22m12s
Queue Release Build / build-windows (push) Successful in 23m20s
Queue Release Build / finalize (push) Successful in 2m12s
All checks were successful
Queue Release Build / prepare (push) Successful in 21s
Deploy Web Apps / deploy (push) Successful in 15m14s
Queue Release Build / build-linux (push) Successful in 22m12s
Queue Release Build / build-windows (push) Successful in 23m20s
Queue Release Build / finalize (push) Successful in 2m12s
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
# Toggle SSL for local development (true/false)
|
# Toggle SSL for local development (true/false)
|
||||||
# When true: ng serve uses --ssl, Express API uses HTTPS, Electron loads https://
|
# When true: ng serve uses --ssl, Express API uses HTTPS, Electron loads https://
|
||||||
# When false: plain HTTP everywhere (only works on localhost)
|
# When false: plain HTTP everywhere (only works on localhost)
|
||||||
|
# Overrides server/data/variables.json for local development only
|
||||||
SSL=true
|
SSL=true
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Desktop chat app with three parts:
|
|||||||
Root `.env`:
|
Root `.env`:
|
||||||
|
|
||||||
- `SSL=true` uses HTTPS for Angular, the server, and Electron dev mode
|
- `SSL=true` uses HTTPS for Angular, the server, and Electron dev mode
|
||||||
- `PORT=3001` changes the server port
|
- `PORT=3001` changes the server port in local development and overrides the server app setting
|
||||||
|
|
||||||
If `SSL=true`, run `./generate-cert.sh` once.
|
If `SSL=true`, run `./generate-cert.sh` once.
|
||||||
|
|
||||||
@@ -25,6 +25,10 @@ Server files:
|
|||||||
|
|
||||||
- `server/data/variables.json` holds `klipyApiKey`
|
- `server/data/variables.json` holds `klipyApiKey`
|
||||||
- `server/data/variables.json` also holds `releaseManifestUrl` for desktop auto updates
|
- `server/data/variables.json` also holds `releaseManifestUrl` for desktop auto updates
|
||||||
|
- `server/data/variables.json` can now also hold optional `serverHost` (an IP address or hostname to bind to)
|
||||||
|
- `server/data/variables.json` can now also hold `serverProtocol` (`http` or `https`)
|
||||||
|
- `server/data/variables.json` can now also hold `serverPort` (1-65535)
|
||||||
|
- When `serverProtocol` is `https`, the certificate must match the configured `serverHost` or IP
|
||||||
|
|
||||||
## Main commands
|
## Main commands
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,20 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { resolveRuntimePath } from '../runtime-paths';
|
import { resolveRuntimePath } from '../runtime-paths';
|
||||||
|
|
||||||
|
export type ServerHttpProtocol = 'http' | 'https';
|
||||||
|
|
||||||
export interface ServerVariablesConfig {
|
export interface ServerVariablesConfig {
|
||||||
klipyApiKey: string;
|
klipyApiKey: string;
|
||||||
releaseManifestUrl: string;
|
releaseManifestUrl: string;
|
||||||
|
serverPort: number;
|
||||||
|
serverProtocol: ServerHttpProtocol;
|
||||||
|
serverHost: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DATA_DIR = resolveRuntimePath('data');
|
const DATA_DIR = resolveRuntimePath('data');
|
||||||
const VARIABLES_FILE = path.join(DATA_DIR, 'variables.json');
|
const VARIABLES_FILE = path.join(DATA_DIR, 'variables.json');
|
||||||
|
const DEFAULT_SERVER_PORT = 3001;
|
||||||
|
const DEFAULT_SERVER_PROTOCOL: ServerHttpProtocol = 'http';
|
||||||
|
|
||||||
function normalizeKlipyApiKey(value: unknown): string {
|
function normalizeKlipyApiKey(value: unknown): string {
|
||||||
return typeof value === 'string' ? value.trim() : '';
|
return typeof value === 'string' ? value.trim() : '';
|
||||||
@@ -18,6 +25,51 @@ function normalizeReleaseManifestUrl(value: unknown): string {
|
|||||||
return typeof value === 'string' ? value.trim() : '';
|
return typeof value === 'string' ? value.trim() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeServerHost(value: unknown): string {
|
||||||
|
return typeof value === 'string' ? value.trim() : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeServerProtocol(
|
||||||
|
value: unknown,
|
||||||
|
fallback: ServerHttpProtocol = DEFAULT_SERVER_PROTOCOL
|
||||||
|
): ServerHttpProtocol {
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return value ? 'https' : 'http';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = value.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (normalized === 'https' || normalized === 'true') {
|
||||||
|
return 'https';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === 'http' || normalized === 'false') {
|
||||||
|
return 'http';
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeServerPort(value: unknown, fallback = DEFAULT_SERVER_PORT): number {
|
||||||
|
const parsed = typeof value === 'number'
|
||||||
|
? value
|
||||||
|
: typeof value === 'string'
|
||||||
|
? Number.parseInt(value.trim(), 10)
|
||||||
|
: Number.NaN;
|
||||||
|
|
||||||
|
return Number.isInteger(parsed) && parsed >= 1 && parsed <= 65535
|
||||||
|
? parsed
|
||||||
|
: fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasEnvironmentOverride(value: string | undefined): value is string {
|
||||||
|
return typeof value === 'string' && value.trim().length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function readRawVariables(): { rawContents: string; parsed: Record<string, unknown> } {
|
function readRawVariables(): { rawContents: string; parsed: Record<string, unknown> } {
|
||||||
if (!fs.existsSync(VARIABLES_FILE)) {
|
if (!fs.existsSync(VARIABLES_FILE)) {
|
||||||
return { rawContents: '', parsed: {} };
|
return { rawContents: '', parsed: {} };
|
||||||
@@ -52,10 +104,14 @@ export function ensureVariablesConfig(): ServerVariablesConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { rawContents, parsed } = readRawVariables();
|
const { rawContents, parsed } = readRawVariables();
|
||||||
|
const { serverIpAddress: legacyServerIpAddress, ...remainingParsed } = parsed;
|
||||||
const normalized = {
|
const normalized = {
|
||||||
...parsed,
|
...remainingParsed,
|
||||||
klipyApiKey: normalizeKlipyApiKey(parsed.klipyApiKey),
|
klipyApiKey: normalizeKlipyApiKey(remainingParsed.klipyApiKey),
|
||||||
releaseManifestUrl: normalizeReleaseManifestUrl(parsed.releaseManifestUrl)
|
releaseManifestUrl: normalizeReleaseManifestUrl(remainingParsed.releaseManifestUrl),
|
||||||
|
serverPort: normalizeServerPort(remainingParsed.serverPort),
|
||||||
|
serverProtocol: normalizeServerProtocol(remainingParsed.serverProtocol),
|
||||||
|
serverHost: normalizeServerHost(remainingParsed.serverHost ?? legacyServerIpAddress)
|
||||||
};
|
};
|
||||||
const nextContents = JSON.stringify(normalized, null, 2) + '\n';
|
const nextContents = JSON.stringify(normalized, null, 2) + '\n';
|
||||||
|
|
||||||
@@ -65,7 +121,10 @@ export function ensureVariablesConfig(): ServerVariablesConfig {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
klipyApiKey: normalized.klipyApiKey,
|
klipyApiKey: normalized.klipyApiKey,
|
||||||
releaseManifestUrl: normalized.releaseManifestUrl
|
releaseManifestUrl: normalized.releaseManifestUrl,
|
||||||
|
serverPort: normalized.serverPort,
|
||||||
|
serverProtocol: normalized.serverProtocol,
|
||||||
|
serverHost: normalized.serverHost
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,3 +143,29 @@ export function hasKlipyApiKey(): boolean {
|
|||||||
export function getReleaseManifestUrl(): string {
|
export function getReleaseManifestUrl(): string {
|
||||||
return getVariablesConfig().releaseManifestUrl;
|
return getVariablesConfig().releaseManifestUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getServerProtocol(): ServerHttpProtocol {
|
||||||
|
if (hasEnvironmentOverride(process.env.SSL)) {
|
||||||
|
return normalizeServerProtocol(process.env.SSL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getVariablesConfig().serverProtocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getServerPort(): number {
|
||||||
|
if (hasEnvironmentOverride(process.env.PORT)) {
|
||||||
|
return normalizeServerPort(process.env.PORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getVariablesConfig().serverPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getServerHost(): string | undefined {
|
||||||
|
const serverHost = getVariablesConfig().serverHost;
|
||||||
|
|
||||||
|
return serverHost || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isHttpsServerEnabled(): boolean {
|
||||||
|
return getServerProtocol() === 'https';
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,23 +14,39 @@ import { deleteStaleJoinRequests } from './cqrs';
|
|||||||
import { createApp } from './app';
|
import { createApp } from './app';
|
||||||
import {
|
import {
|
||||||
ensureVariablesConfig,
|
ensureVariablesConfig,
|
||||||
|
getServerHost,
|
||||||
getVariablesConfigPath,
|
getVariablesConfigPath,
|
||||||
hasKlipyApiKey
|
getServerPort,
|
||||||
|
getServerProtocol,
|
||||||
|
ServerHttpProtocol
|
||||||
} from './config/variables';
|
} from './config/variables';
|
||||||
import { setupWebSocket } from './websocket';
|
import { setupWebSocket } from './websocket';
|
||||||
|
|
||||||
const USE_SSL = (process.env.SSL ?? 'false').toLowerCase() === 'true';
|
function formatHostForUrl(host: string): string {
|
||||||
const PORT = process.env.PORT || 3001;
|
if (host.startsWith('[') || !host.includes(':')) {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
function buildServer(app: ReturnType<typeof createApp>) {
|
return `[${host}]`;
|
||||||
if (USE_SSL) {
|
}
|
||||||
|
|
||||||
|
function getDisplayHost(serverHost: string | undefined): string {
|
||||||
|
if (!serverHost || serverHost === '0.0.0.0' || serverHost === '::') {
|
||||||
|
return 'localhost';
|
||||||
|
}
|
||||||
|
|
||||||
|
return serverHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildServer(app: ReturnType<typeof createApp>, serverProtocol: ServerHttpProtocol) {
|
||||||
|
if (serverProtocol === 'https') {
|
||||||
const certDir = resolveCertificateDirectory();
|
const certDir = resolveCertificateDirectory();
|
||||||
const certFile = path.join(certDir, 'localhost.crt');
|
const certFile = path.join(certDir, 'localhost.crt');
|
||||||
const keyFile = path.join(certDir, 'localhost.key');
|
const keyFile = path.join(certDir, 'localhost.key');
|
||||||
|
|
||||||
if (!fs.existsSync(certFile) || !fs.existsSync(keyFile)) {
|
if (!fs.existsSync(certFile) || !fs.existsSync(keyFile)) {
|
||||||
console.error(`SSL=true but certs not found in ${certDir}`);
|
console.error(`HTTPS is enabled but certs were not found in ${certDir}`);
|
||||||
console.error('Run ./generate-cert.sh first.');
|
console.error('Add localhost.crt and localhost.key there, or switch serverProtocol to "http".');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,17 +60,31 @@ function buildServer(app: ReturnType<typeof createApp>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function bootstrap(): Promise<void> {
|
async function bootstrap(): Promise<void> {
|
||||||
ensureVariablesConfig();
|
const variablesConfig = ensureVariablesConfig();
|
||||||
|
const serverProtocol = getServerProtocol();
|
||||||
|
const serverPort = getServerPort();
|
||||||
|
const serverHost = getServerHost();
|
||||||
|
const bindHostLabel = serverHost || 'default interface';
|
||||||
|
|
||||||
console.log('[Config] Variables loaded from:', getVariablesConfigPath());
|
console.log('[Config] Variables loaded from:', getVariablesConfigPath());
|
||||||
|
|
||||||
if (!hasKlipyApiKey()) {
|
if (
|
||||||
|
variablesConfig.serverProtocol !== serverProtocol
|
||||||
|
|| variablesConfig.serverPort !== serverPort
|
||||||
|
) {
|
||||||
|
console.log(`[Config] Server runtime override active: protocol=${serverProtocol}, host=${bindHostLabel}, port=${serverPort}`);
|
||||||
|
} else {
|
||||||
|
console.log(`[Config] Server runtime config: protocol=${serverProtocol}, host=${bindHostLabel}, port=${serverPort}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!variablesConfig.klipyApiKey) {
|
||||||
console.log('[KLIPY] API key not configured. GIF search is disabled.');
|
console.log('[KLIPY] API key not configured. GIF search is disabled.');
|
||||||
}
|
}
|
||||||
|
|
||||||
await initDatabase();
|
await initDatabase();
|
||||||
|
|
||||||
const app = createApp();
|
const app = createApp();
|
||||||
const server = buildServer(app);
|
const server = buildServer(app, serverProtocol);
|
||||||
|
|
||||||
setupWebSocket(server);
|
setupWebSocket(server);
|
||||||
|
|
||||||
@@ -64,14 +94,24 @@ async function bootstrap(): Promise<void> {
|
|||||||
.catch(err => console.error('Failed to clean up stale join requests:', err));
|
.catch(err => console.error('Failed to clean up stale join requests:', err));
|
||||||
}, 60 * 1000);
|
}, 60 * 1000);
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
const onListening = () => {
|
||||||
const proto = USE_SSL ? 'https' : 'http';
|
const displayHost = formatHostForUrl(getDisplayHost(serverHost));
|
||||||
const wsProto = USE_SSL ? 'wss' : 'ws';
|
const wsProto = serverProtocol === 'https' ? 'wss' : 'ws';
|
||||||
|
|
||||||
console.log(`MetoYou signaling server running on port ${PORT} (SSL=${USE_SSL})`);
|
console.log(`MetoYou signaling server running on port ${serverPort} (${serverProtocol.toUpperCase()}, bind host=${bindHostLabel})`);
|
||||||
console.log(` REST API: ${proto}://localhost:${PORT}/api`);
|
console.log(` REST API: ${serverProtocol}://${displayHost}:${serverPort}/api`);
|
||||||
console.log(` WebSocket: ${wsProto}://localhost:${PORT}`);
|
console.log(` WebSocket: ${wsProto}://${displayHost}:${serverPort}`);
|
||||||
});
|
|
||||||
|
if (serverProtocol === 'https' && serverHost && !['localhost', '127.0.0.1', '::1'].includes(serverHost)) {
|
||||||
|
console.warn('[Config] HTTPS certificates must match the configured serverHost/server IP.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (serverHost) {
|
||||||
|
server.listen(serverPort, serverHost, onListening);
|
||||||
|
} else {
|
||||||
|
server.listen(serverPort, onListening);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap().catch((err) => {
|
bootstrap().catch((err) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user