mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 09:35:39 +00:00
chore: update Docker Compose image tags to 3.2.12 and enhance documentation for versioning
This commit is contained in:
@@ -260,8 +260,8 @@ async function downloadFont(family, weights = ['400', '500', '600', '700']) {
|
||||
if (downloadedFiles.length > 0) {
|
||||
const cssFaces = downloadedFiles.map(file => {
|
||||
const weightMatch = file.match(/-(\d+)\.woff2$/);
|
||||
const weight = (weightMatch && weightMatch[1]) || '400';
|
||||
const cssFamily = String(family).replace(/['\\]/g, '');
|
||||
const weight = (weightMatch && /^\d+$/.test(weightMatch[1])) ? weightMatch[1] : '400';
|
||||
const cssFamily = String(family).replace(/[^a-zA-Z0-9\s-]/g, '').trim() || 'sans-serif';
|
||||
return [
|
||||
'@font-face {',
|
||||
` font-family: '${cssFamily}';`,
|
||||
|
||||
@@ -34,6 +34,7 @@ const MONITOR_OPTS = { allowPrivate: true };
|
||||
const DEFAULT_POLL_INTERVAL_MS = 60_000; // 1 minute
|
||||
const DEFAULT_TIMEOUT_MS = 5_000; // 5 seconds
|
||||
const MAX_HISTORY_ROWS = 10_000; // per target
|
||||
const MAX_TARGETS_PER_POLL = 500; // cap poll workload
|
||||
const CLEANUP_INTERVAL_MS = 3600_000; // 1 hour
|
||||
|
||||
// Security: Strict hostname/IP validation regex
|
||||
@@ -237,6 +238,7 @@ class NetworkMonitor {
|
||||
this.db = dbAdapter;
|
||||
this.running = false;
|
||||
this.timer = null;
|
||||
this._pollInFlight = false;
|
||||
this.pollInterval = DEFAULT_POLL_INTERVAL_MS;
|
||||
}
|
||||
|
||||
@@ -328,7 +330,8 @@ class NetworkMonitor {
|
||||
if (!targets || targets.length === 0) return [];
|
||||
|
||||
const results = [];
|
||||
for (const target of targets) {
|
||||
const batch = targets.slice(0, MAX_TARGETS_PER_POLL);
|
||||
for (const target of batch) {
|
||||
const result = await this.checkTarget(target);
|
||||
results.push(result);
|
||||
}
|
||||
@@ -342,16 +345,18 @@ class NetworkMonitor {
|
||||
// --- Internal ---
|
||||
|
||||
async _poll() {
|
||||
if (!this.running) return;
|
||||
if (!this.running || this._pollInFlight) return;
|
||||
|
||||
this._pollInFlight = true;
|
||||
try {
|
||||
await this.checkAll();
|
||||
} catch (err) {
|
||||
console.error('[NetworkMonitor] Poll error:', err.message);
|
||||
}
|
||||
|
||||
if (this.running) {
|
||||
this.timer = setTimeout(() => this._poll(), this.pollInterval);
|
||||
} finally {
|
||||
this._pollInFlight = false;
|
||||
if (this.running) {
|
||||
this.timer = setTimeout(() => this._poll(), this.pollInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,6 +535,7 @@ let _updateInProgress = false;
|
||||
|
||||
/**
|
||||
* Run a shell command as a promise (non-blocking unlike execSync).
|
||||
* Prefer spawnPromise() when arguments are known — avoids shell interpolation.
|
||||
*/
|
||||
function execPromise(cmd, opts = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -550,6 +551,27 @@ function execPromise(cmd, opts = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
function spawnPromise(command, args, opts = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { spawn } = require('child_process');
|
||||
const proc = spawn(command, args, { shell: false, ...opts });
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
proc.stdout?.on('data', (chunk) => { stdout += chunk; });
|
||||
proc.stderr?.on('data', (chunk) => { stderr += chunk; });
|
||||
proc.on('error', reject);
|
||||
proc.on('close', (code) => {
|
||||
if (code !== 0) {
|
||||
const err = new Error(`${command} exited with code ${code}`);
|
||||
err.stderr = stderr;
|
||||
err.stdout = stdout;
|
||||
return reject(err);
|
||||
}
|
||||
resolve({ stdout, stderr });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy directory recursively.
|
||||
*/
|
||||
@@ -1125,18 +1147,17 @@ async function buildGoServer(preferredGoBinPath = null) {
|
||||
const start = Date.now();
|
||||
const goBin = goCheck.binPath || 'go';
|
||||
const buildEnv = buildEnvWithGo(goBin);
|
||||
// Quote when path contains spaces (Windows "Program Files")
|
||||
const goCmd = /\s/.test(goBin) ? `"${goBin}"` : goBin;
|
||||
|
||||
try {
|
||||
await execPromise(`${goCmd} mod download`, {
|
||||
await spawnPromise(goBin, ['mod', 'download'], {
|
||||
cwd: serverDir,
|
||||
timeout: 120000,
|
||||
env: buildEnv
|
||||
});
|
||||
|
||||
await execPromise(
|
||||
`${goCmd} build -trimpath -ldflags="-s -w" -o "${binaryName}" .`,
|
||||
await spawnPromise(
|
||||
goBin,
|
||||
['build', '-trimpath', '-ldflags=-s -w', '-o', binaryName, '.'],
|
||||
{ cwd: serverDir, timeout: 600000, env: buildEnv }
|
||||
);
|
||||
|
||||
@@ -1380,7 +1401,7 @@ async function _installGoToolchainBody(onProgress, opts = {}) {
|
||||
{ timeout: 300000 }
|
||||
);
|
||||
} else {
|
||||
await execPromise(`tar -xzf "${archivePath}" -C "${GO_TOOLCHAIN_DIR}"`, { timeout: 300000 });
|
||||
await spawnPromise('tar', ['-xzf', archivePath, '-C', GO_TOOLCHAIN_DIR], { timeout: 300000 });
|
||||
}
|
||||
|
||||
try { fs.unlinkSync(archivePath); } catch (_e) { /* ignore */ }
|
||||
@@ -2578,6 +2599,14 @@ function isValidBackupName(name) {
|
||||
return typeof name === 'string' && /^pre-update-[\d\-T]+$/.test(name);
|
||||
}
|
||||
|
||||
function isValidManifestRelativePath(relPath) {
|
||||
if (typeof relPath !== 'string' || relPath.length === 0 || relPath.includes('\0')) return false;
|
||||
const normalized = relPath.replace(/\\/g, '/');
|
||||
if (path.isAbsolute(normalized) || normalized.startsWith('/') || normalized.startsWith('..')) return false;
|
||||
if (normalized.split('/').some((seg) => seg === '..')) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively delete a directory. Refuses to delete anything outside
|
||||
* BACKUP_DIR to defend against path-traversal bugs upstream.
|
||||
@@ -2635,6 +2664,9 @@ function restoreFromBackup(backupName) {
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
let restored = 0;
|
||||
for (const filePath of (manifest.files || [])) {
|
||||
if (!isValidManifestRelativePath(filePath)) {
|
||||
throw new Error(`Invalid path in backup manifest: ${filePath}`);
|
||||
}
|
||||
const src = resolvePathUnderRoot(backupPath, filePath);
|
||||
const dest = resolvePathUnderRoot(ROOT_DIR, filePath);
|
||||
if (fs.existsSync(src)) {
|
||||
|
||||
Reference in New Issue
Block a user