User Story
As the relay operator, I want the binary to terminate TLS itself via Let's Encrypt (golang.org/x/crypto/acme/autocert) when --domain is set, so that production deployment is one command (pyrycode-relay --domain relay.example.com) without a separate reverse proxy.
Context
The scaffold's cmd/pyrycode-relay/main.go currently refuses to start without --insecure-listen ("autocert TLS path not yet implemented"). This ticket fills that in. See pyrycode/pyrycode/docs/protocol-mobile.md § TLS.
Acceptance Criteria
Technical Notes
autocert requires the relay to be reachable on port 80 from the public internet for the http-01 challenge. Document this in README.
- Cache dir at
0700 is critical — TLS keys live there. Test verifies permissions.
--cert-cache flag default: $HOME/.pyrycode-relay/certs. Resolved via os.UserHomeDir() at startup.
- Out of scope:
- Wildcard certs (would require dns-01 challenge, much more complex).
- Multiple-domain support (
autocert.HostWhitelist accepts multiple, but UX implications and ops story aren't worth solving until a second domain is needed).
- Cert renewal monitoring / metrics — autocert handles renewal silently; first-pass deploy doesn't need observability for it.
- Falling back to a self-signed cert in dev (use
--insecure-listen for dev, that's its purpose).
Size Estimate
S — ~80 LOC + ~50 LOC tests + go.mod update.
Depends on
- None (independent of routing logic).
User Story
As the relay operator, I want the binary to terminate TLS itself via Let's Encrypt (
golang.org/x/crypto/acme/autocert) when--domainis set, so that production deployment is one command (pyrycode-relay --domain relay.example.com) without a separate reverse proxy.Context
The scaffold's
cmd/pyrycode-relay/main.gocurrently refuses to start without--insecure-listen("autocert TLS path not yet implemented"). This ticket fills that in. Seepyrycode/pyrycode/docs/protocol-mobile.md§ TLS.Acceptance Criteria
cmd/pyrycode-relay/main.go, when--domainis set (and--insecure-listenis empty):autocert.ManagerwithHostPolicy: autocert.HostWhitelist(*domain)(only the configured domain accepted; any other Host header → certificate request rejected).*certCacheflag (default~/.pyrycode-relay/certs). Create it if missing with0700permissions.:443for HTTPS viamanager.TLSConfig().:80for the ACME http-01 challenge viamanager.HTTPHandler(nil). Thenilargument means non-challenge HTTP requests get a 404 (we do NOT redirect to HTTPS — we want explicit-failure semantics for misconfigured clients).&http.Server{}with the same timeouts as the existing insecure path (ReadHeaderTimeout 10s, ReadTimeout 60s, WriteTimeout 60s, IdleTimeout 120s).*domainget421 Misdirected Requestper the spec.golang.org/x/crypto/acme/autocerttogo.mod.README.md:--domainexample to the primary "Run" section (was previously after--insecure-listen).cmd/pyrycode-relay/main_test.go(orinternal/relay/tls_test.go):0700; existing dir → no-op.manager.HostPolicy).Technical Notes
autocertrequires the relay to be reachable on port 80 from the public internet for the http-01 challenge. Document this in README.0700is critical — TLS keys live there. Test verifies permissions.--cert-cacheflag default:$HOME/.pyrycode-relay/certs. Resolved viaos.UserHomeDir()at startup.autocert.HostWhitelistaccepts multiple, but UX implications and ops story aren't worth solving until a second domain is needed).--insecure-listenfor dev, that's its purpose).Size Estimate
S — ~80 LOC + ~50 LOC tests + go.mod update.
Depends on