mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 09:35:39 +00:00
b787ad9996
Replace invalid prototype getter syntax in local-files.js and mesh-files.js so the file transfer modal loads in browsers. Panel updates patch betterdesk-server.service via a passwordless-sudo helper instead of interactive sudo tee. Refs #217
121 lines
3.5 KiB
JavaScript
121 lines
3.5 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execFileSync } = require('child_process');
|
|
|
|
const ALLOWED_SYSTEMD_UNITS = new Set([
|
|
'/etc/systemd/system/betterdesk-server.service',
|
|
'/etc/systemd/system/betterdesk-console.service',
|
|
]);
|
|
|
|
function resolveSystemdUnitScriptPath(consoleRoot) {
|
|
return path.join(consoleRoot, 'scripts/linux-write-systemd-unit.js');
|
|
}
|
|
|
|
function normalizeUnitPath(filePath) {
|
|
if (!filePath || typeof filePath !== 'string') return null;
|
|
const trimmed = filePath.trim();
|
|
if (!path.isAbsolute(trimmed)) return null;
|
|
return path.resolve(trimmed);
|
|
}
|
|
|
|
function isAllowedSystemdUnitPath(filePath) {
|
|
const normalized = normalizeUnitPath(filePath);
|
|
if (!normalized) return false;
|
|
try {
|
|
const real = fs.existsSync(normalized) ? fs.realpathSync(normalized) : normalized;
|
|
return ALLOWED_SYSTEMD_UNITS.has(real);
|
|
} catch (_) {
|
|
return ALLOWED_SYSTEMD_UNITS.has(normalized);
|
|
}
|
|
}
|
|
|
|
function isRoot() {
|
|
return typeof process.getuid === 'function' && process.getuid() === 0;
|
|
}
|
|
|
|
function canUsePasswordlessSudo() {
|
|
if (isRoot()) return true;
|
|
try {
|
|
execFileSync('sudo', ['-n', 'true'], { stdio: 'pipe', timeout: 3000 });
|
|
return true;
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function invokeSystemdUnitScript(payload, consoleRoot) {
|
|
const scriptPath = resolveSystemdUnitScriptPath(consoleRoot);
|
|
if (!fs.existsSync(scriptPath)) {
|
|
throw new Error('Systemd unit helper not installed');
|
|
}
|
|
|
|
const runOpts = {
|
|
input: JSON.stringify(payload),
|
|
encoding: 'utf8',
|
|
timeout: 15000,
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
};
|
|
|
|
let out;
|
|
if (isRoot()) {
|
|
out = execFileSync(process.execPath, [scriptPath], runOpts);
|
|
} else {
|
|
out = execFileSync('sudo', ['-n', process.execPath, scriptPath], runOpts);
|
|
}
|
|
|
|
const parsed = JSON.parse(String(out || '').trim() || '{}');
|
|
if (!parsed.success) {
|
|
throw new Error(parsed.error || 'Privileged systemd unit operation failed');
|
|
}
|
|
return parsed;
|
|
}
|
|
|
|
function readSystemdUnitPrivileged(filePath, consoleRoot) {
|
|
if (!isAllowedSystemdUnitPath(filePath)) {
|
|
throw new Error(`Systemd unit path not allowed: ${filePath}`);
|
|
}
|
|
const normalized = normalizeUnitPath(filePath);
|
|
|
|
try {
|
|
return fs.readFileSync(normalized, 'utf8');
|
|
} catch (err) {
|
|
if (err.code !== 'EACCES' && err.code !== 'EPERM') throw err;
|
|
}
|
|
|
|
const result = invokeSystemdUnitScript({ action: 'read', path: normalized }, consoleRoot);
|
|
return result.content || '';
|
|
}
|
|
|
|
function writeSystemdUnitPrivileged(filePath, content, consoleRoot) {
|
|
if (!isAllowedSystemdUnitPath(filePath)) {
|
|
throw new Error(`Systemd unit path not allowed: ${filePath}`);
|
|
}
|
|
const normalized = normalizeUnitPath(filePath);
|
|
|
|
try {
|
|
fs.writeFileSync(normalized, content, { encoding: 'utf8', mode: 0o644 });
|
|
return;
|
|
} catch (err) {
|
|
if (err.code !== 'EACCES' && err.code !== 'EPERM') throw err;
|
|
}
|
|
|
|
invokeSystemdUnitScript({ action: 'write', path: normalized, content }, consoleRoot);
|
|
}
|
|
|
|
function privilegedSystemdUnitHint() {
|
|
return 'Run once as root: sudo node web-nodejs/scripts/linux-ensure-console-user.js';
|
|
}
|
|
|
|
module.exports = {
|
|
ALLOWED_SYSTEMD_UNITS,
|
|
resolveSystemdUnitScriptPath,
|
|
normalizeUnitPath,
|
|
isAllowedSystemdUnitPath,
|
|
canUsePasswordlessSudo,
|
|
readSystemdUnitPrivileged,
|
|
writeSystemdUnitPrivileged,
|
|
privilegedSystemdUnitHint,
|
|
};
|