mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 17:45:42 +00:00
bb2ff08f65
Wire billing/NTP env from console .env into Go on Linux, Windows, and Docker; add OS NTP fallback and Commercialization settings UI to configure and restart the server.
148 lines
4.9 KiB
JavaScript
148 lines
4.9 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const net = require('net');
|
|
const path = require('path');
|
|
const { upsertEnvKey } = require('../lib/envMerge');
|
|
const updateService = require('./updateService');
|
|
const serverConnectionConfigService = require('./serverConnectionConfigService');
|
|
|
|
const CONSOLE_ROOT = path.join(__dirname, '..');
|
|
const ENV_PATH = path.join(CONSOLE_ROOT, '.env');
|
|
|
|
const ENV_KEYS = {
|
|
ntp_servers: 'NTP_SERVERS',
|
|
max_skew_ms: 'BILLING_MAX_CLOCK_SKEW_MS',
|
|
require_synced_clock: 'BILLING_REQUIRE_SYNCED_CLOCK',
|
|
trust_os_ntp: 'BILLING_TRUST_OS_NTP',
|
|
};
|
|
|
|
const DEFAULTS = {
|
|
ntp_servers: 'pool.ntp.org,time.google.com,time.cloudflare.com',
|
|
max_skew_ms: 2000,
|
|
require_synced_clock: true,
|
|
trust_os_ntp: process.platform === 'linux',
|
|
};
|
|
|
|
function parseEnvFile(content) {
|
|
const out = {};
|
|
if (!content) return out;
|
|
for (const line of String(content).split(/\r?\n/)) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
const eq = trimmed.indexOf('=');
|
|
if (eq <= 0) continue;
|
|
out[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim();
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function envToBool(value, defaultValue) {
|
|
if (value === undefined || value === null || value === '') return defaultValue;
|
|
const v = String(value).trim().toUpperCase();
|
|
if (['Y', 'YES', '1', 'TRUE', 'ON'].includes(v)) return true;
|
|
if (['N', 'NO', '0', 'FALSE', 'OFF'].includes(v)) return false;
|
|
return defaultValue;
|
|
}
|
|
|
|
function boolToEnv(value) {
|
|
return value ? '1' : '0';
|
|
}
|
|
|
|
function isValidNTPServerEntry(entry) {
|
|
const s = String(entry || '').trim();
|
|
if (!s || s.length > 253) return false;
|
|
if (net.isIP(s)) return true;
|
|
return /^[a-zA-Z0-9]([a-zA-Z0-9.-]*[a-zA-Z0-9])?$/.test(s);
|
|
}
|
|
|
|
function normalizeSettings(raw = {}) {
|
|
const servers = String(raw.ntp_servers ?? DEFAULTS.ntp_servers)
|
|
.split(',')
|
|
.map((s) => s.trim())
|
|
.filter(Boolean);
|
|
const maxSkew = Number.parseInt(raw.max_skew_ms, 10);
|
|
return {
|
|
ntp_servers: servers.join(','),
|
|
max_skew_ms: Number.isFinite(maxSkew) && maxSkew > 0 ? maxSkew : DEFAULTS.max_skew_ms,
|
|
require_synced_clock: raw.require_synced_clock !== undefined
|
|
? !!raw.require_synced_clock
|
|
: DEFAULTS.require_synced_clock,
|
|
trust_os_ntp: raw.trust_os_ntp !== undefined
|
|
? !!raw.trust_os_ntp
|
|
: DEFAULTS.trust_os_ntp,
|
|
};
|
|
}
|
|
|
|
function validateSettings(settings) {
|
|
const normalized = normalizeSettings(settings);
|
|
const servers = normalized.ntp_servers.split(',').map((s) => s.trim()).filter(Boolean);
|
|
if (!servers.length) {
|
|
throw new Error('invalid_ntp_servers');
|
|
}
|
|
for (const server of servers) {
|
|
if (!isValidNTPServerEntry(server)) {
|
|
throw new Error('invalid_ntp_servers');
|
|
}
|
|
}
|
|
if (normalized.max_skew_ms <= 0 || normalized.max_skew_ms > 600000) {
|
|
throw new Error('invalid_max_skew');
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
function settingsFromEnvContent(content) {
|
|
const env = parseEnvFile(content);
|
|
return normalizeSettings({
|
|
ntp_servers: env[ENV_KEYS.ntp_servers] || DEFAULTS.ntp_servers,
|
|
max_skew_ms: env[ENV_KEYS.max_skew_ms] || DEFAULTS.max_skew_ms,
|
|
require_synced_clock: envToBool(env[ENV_KEYS.require_synced_clock], DEFAULTS.require_synced_clock),
|
|
trust_os_ntp: envToBool(env[ENV_KEYS.trust_os_ntp], DEFAULTS.trust_os_ntp),
|
|
});
|
|
}
|
|
|
|
function getClockSettings() {
|
|
const content = fs.existsSync(ENV_PATH) ? fs.readFileSync(ENV_PATH, 'utf8') : '';
|
|
return settingsFromEnvContent(content);
|
|
}
|
|
|
|
function writeClockSettingsToEnv(settings) {
|
|
const normalized = validateSettings(settings);
|
|
let content = fs.existsSync(ENV_PATH) ? fs.readFileSync(ENV_PATH, 'utf8') : '';
|
|
content = upsertEnvKey(content, ENV_KEYS.ntp_servers, normalized.ntp_servers);
|
|
content = upsertEnvKey(content, ENV_KEYS.max_skew_ms, String(normalized.max_skew_ms));
|
|
content = upsertEnvKey(content, ENV_KEYS.require_synced_clock, boolToEnv(normalized.require_synced_clock));
|
|
content = upsertEnvKey(content, ENV_KEYS.trust_os_ntp, boolToEnv(normalized.trust_os_ntp));
|
|
fs.writeFileSync(ENV_PATH, content, { encoding: 'utf8', mode: 0o600 });
|
|
return normalized;
|
|
}
|
|
|
|
async function saveClockSettings(settings, opts = {}) {
|
|
const normalized = writeClockSettingsToEnv(settings);
|
|
const serviceConfig = updateService.sanitizeGoServerServiceConfig();
|
|
const result = {
|
|
settings: normalized,
|
|
serviceConfig,
|
|
restart: null,
|
|
};
|
|
if (opts.restart !== false) {
|
|
result.restart = serverConnectionConfigService.restartServer();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
module.exports = {
|
|
DEFAULTS,
|
|
ENV_PATH,
|
|
ENV_KEYS,
|
|
parseEnvFile,
|
|
envToBool,
|
|
boolToEnv,
|
|
isValidNTPServerEntry,
|
|
normalizeSettings,
|
|
validateSettings,
|
|
settingsFromEnvContent,
|
|
getClockSettings,
|
|
saveClockSettings,
|
|
};
|