feat: self-hosted remote support over VNC
Browser-based remote control (noVNC) with invite links, per-user access control, garagedoor SSO and a persisted client list. The hub proxies RFB rather than pointing the browser at a VNC server. That is what lets it authenticate upstream with a stored password the browser never sees, and enforce view-only by dropping input messages on the client->server stream instead of hiding buttons. Machines are reachable two ways: direct TCP for LAN hosts, or an outbound agent tunnel for anything behind NAT. Node 22's global WebSocket keeps the agent dependency-free, and node:sqlite keeps the image free of native builds. Ships with an end-to-end suite that boots the real server against a fake VNC server and a fake auth service (72 assertions), plus Gitea Actions CI/CD to Portainer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
function bool(v, dflt) {
|
||||
if (v === undefined || v === '') return dflt;
|
||||
return /^(1|true|yes|on)$/i.test(String(v));
|
||||
}
|
||||
|
||||
function list(v) {
|
||||
return String(v || '')
|
||||
.split(',')
|
||||
.map((s) => s.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
const config = {
|
||||
port: Number(process.env.PORT || 8080),
|
||||
host: process.env.HOST || '0.0.0.0',
|
||||
|
||||
// garagedoor-node-ws central auth
|
||||
authUrl: (process.env.AUTH_URL || 'http://192.168.4.208:8000').replace(/\/$/, ''),
|
||||
|
||||
// Admins: username allowlist, or garagedoor `level` at/above this threshold.
|
||||
adminUsers: list(process.env.ADMIN_USERS),
|
||||
adminLevel: process.env.ADMIN_LEVEL === '' || process.env.ADMIN_LEVEL === undefined
|
||||
? null
|
||||
: Number(process.env.ADMIN_LEVEL),
|
||||
|
||||
dbPath: process.env.DB_PATH || path.join(__dirname, '..', 'data', 'rcs.db'),
|
||||
|
||||
// Key material for AES-256-GCM at-rest encryption of VNC passwords / agent keys.
|
||||
encryptionKey: process.env.ENCRYPTION_KEY || '',
|
||||
|
||||
// Public base URL, used when rendering invite links. Falls back to the request host.
|
||||
publicUrl: (process.env.PUBLIC_URL || '').replace(/\/$/, ''),
|
||||
|
||||
ticketTtlMs: Number(process.env.TICKET_TTL_MS || 30_000),
|
||||
inviteDefaultTtlMs: Number(process.env.INVITE_TTL_MS || 24 * 60 * 60 * 1000),
|
||||
agentOfflineAfterMs: Number(process.env.AGENT_OFFLINE_AFTER_MS || 90_000),
|
||||
consentTimeoutMs: Number(process.env.CONSENT_TIMEOUT_MS || 45_000),
|
||||
|
||||
// Session invites let unauthenticated people connect. Off by default is safer,
|
||||
// but the whole point of this app is handing a link to someone, so: on.
|
||||
allowSessionInvites: bool(process.env.ALLOW_SESSION_INVITES, true),
|
||||
|
||||
trustProxy: bool(process.env.TRUST_PROXY, true),
|
||||
logLevel: process.env.LOG_LEVEL || 'info',
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
Reference in New Issue
Block a user