Design for muxplex setup-tls command with auto-detection: - Tailscale cert (real LE, universally trusted) - mkcert (local CA, zero browser warnings) - Self-signed fallback (works but browser warns) Covers settings integration, CLI flags, service integration, doctor diagnostics, and error handling edge cases.
8.3 KiB
HTTPS/TLS Setup Design
Goal
Add HTTPS support to muxplex so the browser Clipboard API works on non-localhost devices, via a muxplex setup-tls command with auto-detection that chooses the best available TLS method.
Background
The browser Clipboard API (navigator.clipboard) requires a secure context — either localhost or HTTPS. When accessing muxplex from another device on the LAN (the common multi-device use case), clipboard operations silently fail because the connection is plain HTTP. This blocks copy/paste workflows that are central to terminal use.
Rather than requiring users to manually configure TLS certificates, muxplex should detect what's available on the system and set up the best option automatically.
Approach
Auto-detection with tiered strategies. A single command — muxplex setup-tls — detects what's available and does the right thing:
- Tailscale running + MagicDNS →
tailscale cert(real Let's Encrypt cert, universally trusted) - mkcert installed → local CA cert (trusted on this machine, zero browser warnings)
- Fallback → self-signed via Python (browser shows warning, but clipboard works)
No new Python package dependencies. ssl is stdlib, subprocess handles mkcert/tailscale CLI calls. uvicorn's built-in SSL support handles the serving side.
Architecture
Settings Integration
Two new keys in settings.py DEFAULT_SETTINGS:
| Key | Default | Description |
|---|---|---|
tls_cert |
"" |
Path to TLS certificate file. Empty = HTTP. |
tls_key |
"" |
Path to TLS private key file. Empty = HTTP. |
In serve(), if both are non-empty and files exist on disk, pass to uvicorn.run(ssl_certfile=tls_cert, ssl_keyfile=tls_key). The server prints https:// in its startup URL instead of http://.
If only one is set, or files don't exist at the configured paths, warn and fall back to HTTP.
CLI Flags
--tls-cert and --tls-key flags on muxplex serve, using the same None-sentinel pattern as --host / --port. These override settings.json values.
muxplex config set tls_cert /path/to/cert.pem also works for persistent configuration.
Detection Flow
muxplex setup-tls [--method=auto|tailscale|mkcert|selfsigned]
Default: --method=auto
1. Is Tailscale running + MagicDNS enabled?
→ YES: Use tailscale cert (real LE cert, universally trusted)
→ NO: continue
2. Is mkcert installed?
→ YES: Use mkcert (local CA, trusted on this machine)
→ NO: continue
3. Fallback: Generate self-signed via Python ssl module
→ Works but browser shows warning
→ Print: "For zero-warning HTTPS, install mkcert or use Tailscale"
Components
New Module: muxplex/tls.py
Contains all TLS-related logic: auto-detection, cert generation for each method, cert inspection (expiry, SANs), and status reporting.
muxplex setup-tls Subcommand
What it does:
- Generates cert + key to
~/.config/muxplex/cert.pemand~/.config/muxplex/key.pem - Writes
tls_certandtls_keypaths tosettings.json - Prints method used, hostname(s) in the cert, and expiry date
- Prints: "Restart service to apply:
muxplex service restart"
Tailscale variant:
- Auto-detects MagicDNS hostname via
tailscale status --self --json - Runs
tailscale cert --cert-file ~/.config/muxplex/cert.pem --key-file ~/.config/muxplex/key.pem - Includes the local hostname as a reminder to access via
https://spark-1.tail8f3c4e.ts.net:8088 - Notes the 90-day expiry and suggests: "Run
muxplex setup-tlsagain to renew"
mkcert variant:
- Runs
mkcert -install(may prompt for sudo/keychain access) - Generates cert for:
$(hostname),$(hostname).local,localhost,127.0.0.1,::1 - If Tailscale is detected, also adds the Tailscale IP and MagicDNS name as SANs
- Prints instructions for trusting on other LAN devices: "Copy
$(mkcert -CAROOT)/rootCA.pemto other devices"
Self-signed fallback:
- Uses Python
ssloropensslCLI to generate a basic self-signed cert - Warns: "Browsers will show a security warning. Install mkcert for trusted certs."
muxplex setup-tls --status
Shows current TLS state: method used, cert paths, expiry date, hostnames in the cert. Reuses the same inspection logic as doctor's TLS section.
Settings Changes (muxplex/settings.py)
Add tls_cert and tls_key to DEFAULT_SETTINGS with empty string defaults.
Serve Changes (muxplex/cli.py)
- Add
--tls-certand--tls-keyCLI flags toservesubcommand - In
serve(), resolve TLS paths (CLI flag → settings.json → empty) - If both paths are non-empty and files exist, pass
ssl_certfileandssl_keyfiletouvicorn.run() - Print
https://URL on startup when TLS is active
Doctor Integration
muxplex doctor shows TLS status:
- TLS enabled:
TLS: enabled (cert expires 2036-04-01) - TLS disabled:
TLS: disabled (clipboard requires HTTPS on non-localhost) - Cert expired:
TLS: WARNING — cert expired 5 days ago. Run muxplex setup-tls to renew
Service Integration
The service file (systemd/launchd) doesn't need to change — serve() already reads tls_cert and tls_key from settings.json. The service runs muxplex serve with zero extra flags.
URL detection in muxplex service install:
- TLS enabled:
Service started → https://spark-1.tail8f3c4e.ts.net:8088 - TLS disabled:
Service started → http://0.0.0.0:8088
Tailscale cert renewal (90-day expiry): setup-tls prints renewal instructions. No auto-cron job — users set that up themselves if wanted.
Error Handling
Detection Edge Cases
| Scenario | Behavior |
|---|---|
| Tailscale installed but not connected | Skip to mkcert detection |
| Tailscale connected but HTTPS certs not enabled | Print: "Enable HTTPS Certificates in your Tailscale admin console, then re-run" |
mkcert installed but mkcert -install fails (no sudo, no certutil) |
Warn and fall back to self-signed |
| Certs already exist from a previous run | Prompt: "TLS already configured (method: tailscale, expires 2026-07-03). Regenerate? [y/N]" |
Runtime Edge Cases
| Scenario | Behavior |
|---|---|
| Cert files in settings but deleted from disk | Warn "TLS cert not found at path, falling back to HTTP" and start without SSL |
| Cert expired | doctor warns with expiry info and renewal instructions |
| Port conflict with HTTPS | Existing _kill_stale_port_holder behavior handles it |
| Only one of cert/key configured | Warn about incomplete TLS config, fall back to HTTP |
WebSocket Considerations
- The frontend's
connectWebSocket()already auto-detectswss:vsws:fromlocation.protocol— no change needed - The federation proxy's
websockets.connect()to remote instances already handles bothws://andwss://sinceremote_instances[].urlcan be eitherhttp://orhttps://
Files to Modify
| File | Change |
|---|---|
muxplex/settings.py |
Add tls_cert, tls_key to DEFAULT_SETTINGS |
muxplex/cli.py |
Add setup-tls subcommand, --tls-cert/--tls-key flags, SSL pass-through in serve() |
muxplex/tls.py |
New module: auto-detection, tailscale cert, mkcert, self-signed generation, cert inspection |
README.md |
Document setup-tls command and TLS configuration |
No changes needed to muxplex/main.py — uvicorn handles SSL transparently.
Dependencies
- No new Python package dependencies —
sslis stdlib,subprocessfor mkcert/tailscale CLI - mkcert — optional external tool, detected at runtime
- Tailscale — optional external tool, detected at runtime
- uvicorn SSL — built-in support, already a dependency
Testing Strategy
- Unit tests for detection logic (mock
shutil.which,subprocess.runfor each tool) - Unit tests for cert inspection (expiry parsing, SAN extraction)
- Integration tests for
serve()with SSL cert/key paths (valid and invalid) - CLI tests for
setup-tls --statusoutput formatting - Edge case tests: missing files, partial config, expired certs
Effort Estimate
| Phase | Work | Time |
|---|---|---|
| 1 | Settings + SSL serve | ~2 hours |
| 2 | setup-tls + auto-detection + all 3 methods |
~4 hours |
| 3 | Doctor integration + service URL detection | ~1 hour |
| 4 | README + tests | ~2 hours |
| Total | ~9 hours |
Open Questions
None — all sections validated.