mirror of
https://github.com/UNITRONIX/BetterDesk.git
synced 2026-09-10 09:35:39 +00:00
edfd493be1
Security update H-7 runs the console as User=betterdesk, which cannot bind ports below 1024 without CAP_NET_BIND_SERVICE. Fall back to high ports at startup, add the capability to systemd when .env uses :443/:80, and handle EACCES on listen without an unhandled process crash.
138 lines
4.5 KiB
JavaScript
138 lines
4.5 KiB
JavaScript
'use strict';
|
|
|
|
const fs = require('fs');
|
|
|
|
const PRIVILEGED_PORT_MAX = 1023;
|
|
|
|
function isPrivilegedPort(port) {
|
|
const n = Number(port);
|
|
return Number.isInteger(n) && n > 0 && n <= PRIVILEGED_PORT_MAX;
|
|
}
|
|
|
|
function isRootProcess() {
|
|
return typeof process.getuid === 'function' && process.getuid() === 0;
|
|
}
|
|
|
|
/**
|
|
* Non-root processes cannot bind ports <= 1023 unless CAP_NET_BIND_SERVICE is granted.
|
|
* Fall back to a high port so the console can start after H-7 service user migration.
|
|
*/
|
|
function resolvePortForCurrentUser(configuredPort, fallbackPort, label) {
|
|
const port = Number(configuredPort);
|
|
if (!Number.isInteger(port) || port <= 0) {
|
|
return fallbackPort;
|
|
}
|
|
if (!isRootProcess() && isPrivilegedPort(port)) {
|
|
console.warn(`WARNING: ${label} port ${port} requires root or CAP_NET_BIND_SERVICE; using ${fallbackPort} instead`);
|
|
console.warn(' → Set HTTPS_PORT=5443 (or PORT=5000) in .env, use a reverse proxy on :443, or grant CAP_NET_BIND_SERVICE in the systemd unit');
|
|
return fallbackPort;
|
|
}
|
|
return port;
|
|
}
|
|
|
|
function parseEnvPortSettings(envContent) {
|
|
const get = (key, fallback) => {
|
|
const match = String(envContent || '').match(new RegExp(`^${key}=(.+)$`, 'm'));
|
|
if (!match) return fallback;
|
|
return match[1].trim().replace(/^["']|["']$/g, '');
|
|
};
|
|
return {
|
|
port: parseInt(get('PORT', '5000'), 10) || 5000,
|
|
httpsPort: parseInt(get('HTTPS_PORT', '5443'), 10) || 5443,
|
|
httpsEnabled: (get('HTTPS_ENABLED', 'false') || 'false').toLowerCase() === 'true',
|
|
httpRedirect: (get('HTTP_REDIRECT_HTTPS', 'true') || 'true').toLowerCase() === 'true',
|
|
};
|
|
}
|
|
|
|
function readConsoleEnvPortSettings(envPath) {
|
|
if (!envPath || !fs.existsSync(envPath)) {
|
|
return parseEnvPortSettings('');
|
|
}
|
|
return parseEnvPortSettings(fs.readFileSync(envPath, 'utf8'));
|
|
}
|
|
|
|
function consoleEnvUsesPrivilegedPorts(envSettings) {
|
|
const settings = envSettings || {};
|
|
if (settings.httpsEnabled) {
|
|
if (isPrivilegedPort(settings.httpsPort)) return true;
|
|
if (settings.httpRedirect && isPrivilegedPort(settings.port)) return true;
|
|
return false;
|
|
}
|
|
return isPrivilegedPort(settings.port);
|
|
}
|
|
|
|
const BIND_CAPABILITY_LINES = [
|
|
'AmbientCapabilities=CAP_NET_BIND_SERVICE',
|
|
'CapabilityBoundingSet=CAP_NET_BIND_SERVICE',
|
|
];
|
|
|
|
function serviceUnitHasBindCapability(content) {
|
|
return /^AmbientCapabilities=.*CAP_NET_BIND_SERVICE/m.test(String(content || ''));
|
|
}
|
|
|
|
/**
|
|
* Idempotently add CAP_NET_BIND_SERVICE so User=betterdesk can bind :443/:80.
|
|
*/
|
|
function ensureBindCapabilityInServiceUnit(content) {
|
|
const unit = String(content || '');
|
|
if (!unit.trim()) {
|
|
return { content: unit, changed: false };
|
|
}
|
|
if (serviceUnitHasBindCapability(unit)) {
|
|
return { content: unit, changed: false };
|
|
}
|
|
|
|
const lines = unit.split('\n');
|
|
let insertAt = -1;
|
|
for (let i = 0; i < lines.length; i += 1) {
|
|
if (lines[i].startsWith('User=')) {
|
|
insertAt = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
if (insertAt === -1) {
|
|
for (let i = 0; i < lines.length; i += 1) {
|
|
if (lines[i].trim() === '[Service]') {
|
|
insertAt = i + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (insertAt === -1) {
|
|
return { content: unit, changed: false };
|
|
}
|
|
|
|
lines.splice(insertAt, 0, ...BIND_CAPABILITY_LINES);
|
|
return { content: lines.join('\n'), changed: true };
|
|
}
|
|
|
|
function attachPrivilegedPortErrorHandler(server, { port, label }) {
|
|
if (!server || typeof server.on !== 'function') return;
|
|
server.on('error', (err) => {
|
|
if (err && err.code === 'EACCES') {
|
|
console.error(`ERROR: Cannot bind ${label} port ${port} — permission denied`);
|
|
console.error(' → Ports below 1024 require root or CAP_NET_BIND_SERVICE in the systemd unit');
|
|
console.error(' → Or set HTTPS_PORT=5443 / PORT=5000 in .env and use a reverse proxy on :443');
|
|
process.exit(1);
|
|
}
|
|
if (err && err.code === 'EADDRINUSE') {
|
|
console.error(`ERROR: ${label} port ${port} is already in use`);
|
|
process.exit(1);
|
|
}
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
PRIVILEGED_PORT_MAX,
|
|
isPrivilegedPort,
|
|
isRootProcess,
|
|
resolvePortForCurrentUser,
|
|
parseEnvPortSettings,
|
|
readConsoleEnvPortSettings,
|
|
consoleEnvUsesPrivilegedPorts,
|
|
serviceUnitHasBindCapability,
|
|
ensureBindCapabilityInServiceUnit,
|
|
attachPrivilegedPortErrorHandler,
|
|
};
|