Files
MarekWoandClaude Opus 5 05ec5fa13e feat(https): optional HTTPS front end via Nginx Proxy Manager
Adds an opt-in HTTPS layer without touching anything for existing installs.

The proxy is a service in docker-compose.yml behind the "https" Compose
profile, activated with COMPOSE_PROFILES=https in .env. That keeps the whole
setup inside files git owns while the switch lives in a file it ignores --
important because scripts/update.sh runs `git pull`, so any user edit to a
tracked compose file would break the next update with a merge conflict.
mcupdate needs no change: compose reads COMPOSE_PROFILES from .env itself.

Both services share the project's default network, so the proxy forwards to
http://mc-webui:5000 internally; MC_BIND_ADDRESS lets the plain-HTTP port be
restricted to loopback once HTTPS works, and MC_TRUST_PROXY turns on ProxyFix
so the app sees the real client address and scheme.

Also fixes copying over plain HTTP. navigator.clipboard only exists in a
secure context, and 9 call sites across 5 entry points used it with no
fallback, so copy buttons silently did nothing on a LAN address. They now
share clipboard-utils.js, loaded via _head_i18n.html (the one include every
entry point already has), which falls back to execCommand.

Verified locally against the real stack: self-signed cert uploaded to NPM,
proxy host created, app served over HTTPS with socket.io upgrading to a real
wss:// WebSocket (transport "websocket"), and the clipboard fallback checked
end-to-end by pasting back what it copied over http://.

Two findings from that run are documented in docs/https-setup.md:
NPM 2.15 dropped the default admin@example.com account for a setup wizard,
and its default 443 server now refuses connections without SNI -- which is
every connection made to a bare IP address. docker/npm-default-site.conf is
the opt-in way around that, mounted writable (a :ro mount aborts s6 init and
leaves nginx not listening at all).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 17:58:03 +02:00

49 lines
1.9 KiB
JavaScript

/**
* Clipboard helper shared by every entry point.
*
* navigator.clipboard exists only in a secure context — HTTPS, or http://localhost.
* Opening mc-webui over plain HTTP on a LAN address (http://192.168.1.50:5000) is not
* one, so the API is simply undefined there and every copy button silently does
* nothing. This falls back to the pre-Clipboard-API textarea trick, which still works.
*
* Returns a Promise, so it is a drop-in replacement for navigator.clipboard.writeText().
*/
function copyTextToClipboard(text) {
if (window.isSecureContext && navigator.clipboard && navigator.clipboard.writeText) {
// Even in a secure context this can reject (permissions, unfocused document),
// so keep the fallback on that path too.
return navigator.clipboard.writeText(text).catch(() => legacyClipboardWrite(text));
}
return legacyClipboardWrite(text);
}
/**
* Copy via a temporary textarea and document.execCommand('copy').
* Deprecated, but the only option outside a secure context.
*/
function legacyClipboardWrite(text) {
return new Promise((resolve, reject) => {
const textArea = document.createElement('textarea');
textArea.value = text;
// Off-screen but still selectable — display:none would make select() a no-op.
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
textArea.setAttribute('readonly', '');
document.body.appendChild(textArea);
try {
textArea.select();
textArea.setSelectionRange(0, textArea.value.length); // iOS needs the range
if (document.execCommand('copy')) {
resolve();
} else {
reject(new Error('execCommand("copy") returned false'));
}
} catch (err) {
reject(err);
} finally {
document.body.removeChild(textArea);
}
});
}