feat(stream): optional per-agent HTTPS listener with hot-reloadable cert

Foundation for direct, valid-cert browser playback (agent-TLS feature) — the
cert broker + DNS are a later phase; this is inert until a certificate exists.

- StreamServer runs a second TLS listener on https_stream_port (default 11819)
  serving the SAME mux as HTTP (11818): same token + CORS gates, no new exposure.
- Certificate is read per-handshake from an atomic holder via tls.Config
  GetCertificate, so a cert issued/renewed asynchronously applies without a
  restart. SetTLSCertificate / LoadTLSCertificateFromFiles / HasTLSCertificate.
- Daemon arms HTTPS only when a cert pair exists at certs/agent.{crt,key} under
  the state dir; without it, no HTTPS port is opened and HTTP + funnel are
  unaffected. Shutdown drains the HTTPS server too.
- config: downloads.https_stream_port (default 11819, 0 = disabled).

Tests: real TLS handshake + hot-install (no-cert handshake fails, install →
200), disabled path, missing-cert load error.
This commit is contained in:
Deivid Soto 2026-06-01 13:03:35 +02:00
parent 132c88b3f0
commit 27bee8cdf4
4 changed files with 294 additions and 8 deletions

View file

@ -362,6 +362,21 @@ func runDaemonStart() error {
corsExtras := append([]string(nil), cfg.Download.CORSExtraOrigins...)
corsExtras = append(corsExtras, mirrorCORSOrigins(ctx, cfg, userAgent)...)
streamSrv.SetCORSAllowedOrigins(corsExtras)
// HTTPS stream listener (agent-TLS feature): only armed when a certificate is
// present on disk — without a valid cert there is nothing to serve over TLS,
// and the HTTP listener + funnel keep working. The future ACME broker writes
// the cert pair to certs/agent.{crt,key} under the agent state dir.
if cfg.Download.HTTPSStreamPort > 0 {
certPath := filepath.Join(config.DataDir(), "certs", "agent.crt")
keyPath := filepath.Join(config.DataDir(), "certs", "agent.key")
if err := streamSrv.LoadTLSCertificateFromFiles(certPath, keyPath); err != nil {
log.Printf("[stream] HTTPS disabled — no usable certificate at %s (%v)", certPath, err)
} else {
streamSrv.EnableTLS(cfg.Download.HTTPSStreamPort)
log.Printf("[stream] HTTPS armed on port %d with certificate %s", cfg.Download.HTTPSStreamPort, certPath)
}
}
// Reap HLS tmpdirs left over from a previous daemon run before we start
// accepting new sessions. The in-memory registry doesn't survive a
// restart, so without this disk usage grows unbounded across restarts.