mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-10-26 22:37:07 +00:00
e1bd92a7e4
Persist panel-managed NTP settings in the Go server database and apply them without requiring systemd or host Docker access in split image deployments. Keep native restart behavior and make failed restart transactions dismissible in the UI. Refs #431 Thanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow)
182 lines
6.0 KiB
JavaScript
182 lines
6.0 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const net = require('net');
|
|
const path = require('path');
|
|
const config = require('../config/config');
|
|
const { upsertEnvKey } = require('../lib/envMerge');
|
|
const updateService = require('./updateService');
|
|
const serverConnectionConfigService = require('./serverConnectionConfigService');
|
|
const { apiClient } = require('./betterdeskApi');
|
|
|
|
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 isDockerSplitDeployment() {
|
|
return config.isDocker === true
|
|
&& String(process.env.BETTERDESK_DOCKER_LAYOUT || '').trim().toLowerCase() === 'split';
|
|
}
|
|
|
|
async function getRuntimeClockSettings() {
|
|
if (!isDockerSplitDeployment()) return getClockSettings();
|
|
const response = await apiClient.get('/timesync/config');
|
|
return normalizeSettings(response.data || {});
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
function restoreClockSettings(settings) {
|
|
return writeClockSettingsToEnv(settings);
|
|
}
|
|
|
|
async function saveClockSettings(settings, opts = {}) {
|
|
const normalized = validateSettings(settings);
|
|
if (isDockerSplitDeployment()) {
|
|
const response = await apiClient.put('/timesync/config', normalized);
|
|
const data = response.data || {};
|
|
if (data.error) throw new Error(data.error);
|
|
return {
|
|
settings: normalizeSettings(data.config || normalized),
|
|
serviceConfig: null,
|
|
status: data.status || null,
|
|
restart: null,
|
|
dockerMode: true,
|
|
};
|
|
}
|
|
|
|
writeClockSettingsToEnv(normalized);
|
|
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,
|
|
getRuntimeClockSettings,
|
|
isDockerSplitDeployment,
|
|
restoreClockSettings,
|
|
saveClockSettings,
|
|
};
|