mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 17:45:42 +00:00
0b82160592
Store PUBLIC_* on the console-data volume so panel settings survive compose recreate; keep Compose env override and .env mirror for IaC and bare-metal.
229 lines
9.3 KiB
JavaScript
229 lines
9.3 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const { upsertEnvKey } = require('../lib/envMerge');
|
|
|
|
const {
|
|
parseEnvFile,
|
|
validateSettings,
|
|
isEnvOverrideActive,
|
|
normalizeSettings,
|
|
writePublicEndpointSettingsToEnv,
|
|
readPublicEndpointEnv,
|
|
readPanelPublicHostValue,
|
|
ensureMigratedPublicEndpoints,
|
|
getDurableEnvPath,
|
|
DURABLE_BASENAME,
|
|
_setPathsForTests,
|
|
} = require('../services/rustDeskPublicEndpointsService');
|
|
|
|
describe('rustDeskPublicEndpointsService', () => {
|
|
const originalPublicServerId = process.env.PUBLIC_SERVER_ID;
|
|
const originalPublicRelay = process.env.PUBLIC_RELAY_SERVER;
|
|
const originalPublicApi = process.env.PUBLIC_API_URL;
|
|
const originalPanelHost = process.env.PANEL_PUBLIC_HOST;
|
|
|
|
let tmpRoot;
|
|
let dataDir;
|
|
let envPath;
|
|
let durablePath;
|
|
|
|
beforeEach(() => {
|
|
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'bd-public-endpoints-'));
|
|
dataDir = path.join(tmpRoot, 'data');
|
|
envPath = path.join(tmpRoot, '.env');
|
|
durablePath = path.join(dataDir, DURABLE_BASENAME);
|
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
_setPathsForTests({ envPath, durablePath, dataDir });
|
|
|
|
delete process.env.PUBLIC_SERVER_ID;
|
|
delete process.env.PUBLIC_RELAY_SERVER;
|
|
delete process.env.PUBLIC_API_URL;
|
|
delete process.env.PANEL_PUBLIC_HOST;
|
|
});
|
|
|
|
afterEach(() => {
|
|
_setPathsForTests(null);
|
|
if (originalPublicServerId === undefined) delete process.env.PUBLIC_SERVER_ID;
|
|
else process.env.PUBLIC_SERVER_ID = originalPublicServerId;
|
|
if (originalPublicRelay === undefined) delete process.env.PUBLIC_RELAY_SERVER;
|
|
else process.env.PUBLIC_RELAY_SERVER = originalPublicRelay;
|
|
if (originalPublicApi === undefined) delete process.env.PUBLIC_API_URL;
|
|
else process.env.PUBLIC_API_URL = originalPublicApi;
|
|
if (originalPanelHost === undefined) delete process.env.PANEL_PUBLIC_HOST;
|
|
else process.env.PANEL_PUBLIC_HOST = originalPanelHost;
|
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
});
|
|
|
|
it('validateSettings accepts split-domain values', () => {
|
|
const normalized = validateSettings({
|
|
public_server_id: 'remote.example.com',
|
|
public_relay_server: 'relay.example.com',
|
|
public_api_url: 'https://api.example.com/',
|
|
});
|
|
expect(normalized.public_server_id).toBe('remote.example.com');
|
|
expect(normalized.public_relay_server).toBe('relay.example.com');
|
|
expect(normalized.public_api_url).toBe('https://api.example.com');
|
|
});
|
|
|
|
it('validateSettings allows clearing all fields', () => {
|
|
expect(validateSettings({
|
|
public_server_id: '',
|
|
public_relay_server: '',
|
|
public_api_url: '',
|
|
})).toEqual({
|
|
public_server_id: '',
|
|
public_relay_server: '',
|
|
public_api_url: '',
|
|
});
|
|
});
|
|
|
|
it('validateSettings rejects invalid host', () => {
|
|
expect(() => validateSettings({ public_server_id: 'not valid host!!' }))
|
|
.toThrow('invalid_public_host');
|
|
});
|
|
|
|
it('validateSettings rejects invalid API URL', () => {
|
|
expect(() => validateSettings({ public_api_url: 'ftp://bad' }))
|
|
.toThrow('invalid_public_api_url');
|
|
});
|
|
|
|
it('validateSettings rejects CR/LF injection in host', () => {
|
|
expect(() => validateSettings({
|
|
public_server_id: 'evil.example.com\nSESSION_SECRET=hacked',
|
|
})).toThrow('invalid_public_host');
|
|
});
|
|
|
|
it('validateSettings rejects CR/LF injection in API URL', () => {
|
|
expect(() => validateSettings({
|
|
public_api_url: 'https://api.example.com\nOTHER=1',
|
|
})).toThrow();
|
|
});
|
|
|
|
it('isEnvOverrideActive detects any configured value', () => {
|
|
expect(isEnvOverrideActive({
|
|
public_server_id: '',
|
|
public_relay_server: '',
|
|
public_api_url: '',
|
|
})).toBe(false);
|
|
expect(isEnvOverrideActive({
|
|
public_server_id: '',
|
|
public_relay_server: '',
|
|
public_api_url: 'https://api.example.com',
|
|
})).toBe(true);
|
|
});
|
|
|
|
it('parseEnvFile and upsertEnvKey persist public endpoint keys', () => {
|
|
let content = 'PORT=5000\n';
|
|
const normalized = normalizeSettings({
|
|
public_server_id: 'remote.example.com',
|
|
public_relay_server: 'remote.example.com',
|
|
public_api_url: 'https://api.example.com',
|
|
});
|
|
content = upsertEnvKey(content, 'PUBLIC_SERVER_ID', normalized.public_server_id);
|
|
content = upsertEnvKey(content, 'PUBLIC_RELAY_SERVER', normalized.public_relay_server);
|
|
content = upsertEnvKey(content, 'PUBLIC_API_URL', normalized.public_api_url);
|
|
fs.writeFileSync(envPath, content, 'utf8');
|
|
|
|
const parsed = parseEnvFile(fs.readFileSync(envPath, 'utf8'));
|
|
expect(parsed.PUBLIC_SERVER_ID).toBe('remote.example.com');
|
|
expect(parsed.PUBLIC_RELAY_SERVER).toBe('remote.example.com');
|
|
expect(parsed.PUBLIC_API_URL).toBe('https://api.example.com');
|
|
expect(parsed.PORT).toBe('5000');
|
|
});
|
|
|
|
it('writePublicEndpointSettingsToEnv writes durable and mirrors .env', () => {
|
|
writePublicEndpointSettingsToEnv({
|
|
public_server_id: 'gateway.example.net',
|
|
public_relay_server: 'gateway.example.net',
|
|
public_api_url: 'https://api.example.net:21121',
|
|
});
|
|
|
|
expect(fs.existsSync(durablePath)).toBe(true);
|
|
expect(getDurableEnvPath()).toBe(durablePath);
|
|
|
|
const durable = parseEnvFile(fs.readFileSync(durablePath, 'utf8'));
|
|
expect(durable.PUBLIC_SERVER_ID).toBe('gateway.example.net');
|
|
expect(durable.PUBLIC_RELAY_SERVER).toBe('gateway.example.net');
|
|
expect(durable.PUBLIC_API_URL).toBe('https://api.example.net:21121');
|
|
|
|
const legacy = parseEnvFile(fs.readFileSync(envPath, 'utf8'));
|
|
expect(legacy.PUBLIC_SERVER_ID).toBe('gateway.example.net');
|
|
expect(process.env.PUBLIC_SERVER_ID).toBe('gateway.example.net');
|
|
});
|
|
|
|
it('read prefers non-empty process.env over durable over .env', () => {
|
|
fs.writeFileSync(envPath, 'PUBLIC_SERVER_ID=from-legacy.example\n', 'utf8');
|
|
fs.writeFileSync(durablePath, 'PUBLIC_SERVER_ID=from-durable.example\n', 'utf8');
|
|
|
|
let settings = readPublicEndpointEnv();
|
|
expect(settings.public_server_id).toBe('from-durable.example');
|
|
|
|
process.env.PUBLIC_SERVER_ID = 'from-compose.example';
|
|
settings = readPublicEndpointEnv();
|
|
expect(settings.public_server_id).toBe('from-compose.example');
|
|
});
|
|
|
|
it('empty process.env does not mask durable values', () => {
|
|
fs.writeFileSync(durablePath, 'PUBLIC_SERVER_ID=durable.example\n', 'utf8');
|
|
process.env.PUBLIC_SERVER_ID = '';
|
|
const settings = readPublicEndpointEnv();
|
|
expect(settings.public_server_id).toBe('durable.example');
|
|
});
|
|
|
|
it('survives .env loss after recreate (durable remains)', () => {
|
|
writePublicEndpointSettingsToEnv({
|
|
public_server_id: 'gateway.example.net',
|
|
public_relay_server: 'relay.example.net',
|
|
public_api_url: 'https://api.example.net:21121',
|
|
});
|
|
delete process.env.PUBLIC_SERVER_ID;
|
|
delete process.env.PUBLIC_RELAY_SERVER;
|
|
delete process.env.PUBLIC_API_URL;
|
|
fs.unlinkSync(envPath);
|
|
|
|
const settings = readPublicEndpointEnv();
|
|
expect(settings.public_server_id).toBe('gateway.example.net');
|
|
expect(settings.public_relay_server).toBe('relay.example.net');
|
|
expect(settings.public_api_url).toBe('https://api.example.net:21121');
|
|
});
|
|
|
|
it('migrates from .env into durable when durable is empty', () => {
|
|
fs.writeFileSync(envPath, [
|
|
'PUBLIC_SERVER_ID=migrated.example',
|
|
'PUBLIC_RELAY_SERVER=migrated-relay.example',
|
|
'PUBLIC_API_URL=https://api.migrated.example',
|
|
'PANEL_PUBLIC_HOST=panel.migrated.example',
|
|
].join('\n') + '\n', 'utf8');
|
|
|
|
ensureMigratedPublicEndpoints();
|
|
|
|
expect(fs.existsSync(durablePath)).toBe(true);
|
|
const durable = parseEnvFile(fs.readFileSync(durablePath, 'utf8'));
|
|
expect(durable.PUBLIC_SERVER_ID).toBe('migrated.example');
|
|
expect(durable.PUBLIC_RELAY_SERVER).toBe('migrated-relay.example');
|
|
expect(durable.PUBLIC_API_URL).toBe('https://api.migrated.example');
|
|
expect(durable.PANEL_PUBLIC_HOST).toBe('panel.migrated.example');
|
|
});
|
|
|
|
it('migration never overwrites non-empty durable', () => {
|
|
fs.writeFileSync(durablePath, 'PUBLIC_SERVER_ID=keep.example\n', 'utf8');
|
|
fs.writeFileSync(envPath, 'PUBLIC_SERVER_ID=should-not-win.example\n', 'utf8');
|
|
|
|
ensureMigratedPublicEndpoints();
|
|
|
|
const durable = parseEnvFile(fs.readFileSync(durablePath, 'utf8'));
|
|
expect(durable.PUBLIC_SERVER_ID).toBe('keep.example');
|
|
});
|
|
|
|
it('readPanelPublicHostValue uses same precedence', () => {
|
|
fs.writeFileSync(durablePath, 'PANEL_PUBLIC_HOST=durable-panel.example\n', 'utf8');
|
|
expect(readPanelPublicHostValue()).toBe('durable-panel.example');
|
|
|
|
process.env.PANEL_PUBLIC_HOST = 'compose-panel.example';
|
|
expect(readPanelPublicHostValue()).toBe('compose-panel.example');
|
|
});
|
|
});
|