Skip to content

release: v1.20.2#351

Merged
geodro merged 1 commit into
mainfrom
release-v1.20.2
May 14, 2026
Merged

release: v1.20.2#351
geodro merged 1 commit into
mainfrom
release-v1.20.2

Conversation

@geodro
Copy link
Copy Markdown
Owner

@geodro geodro commented May 14, 2026

Summary

A lerd lan:share follow-up that fixes the bugs uncovered while sharing a real Laravel + Vite + RustFS site over the LAN.

The tunnel share proxy was passing gzipped responses through untouched, so on most frameworks the page loaded but every asset URL still pointed at the local .test domain and the share rendered white. The LAN share proxy was setting X-Forwarded-Host but not X-Forwarded-Port, so Symfony's Request::getSchemeAndHttpHost() and Ziggy's URL helper composed asset URLs out of the proxy's host plus nginx's $server_port=443, leaking http://<lan-ip>:443/... into the page. Behind that was an nginx-side bug: every site's vhost did fastcgi_param HTTP_X_FORWARDED_PORT $server_port and unconditionally overwrote whatever the upstream proxy sent. A new shared _forwarded.conf map ($real_forwarded_port) reads the upstream header when present and falls back to $server_port only when the request was direct, so direct browser access at https://app.test/ keeps working unchanged.

Vite dev-server URLs (http://[::1]:5173/...) and RustFS / S3 / other loopback URLs (http://localhost:9000/...) were also leaking into LAN-shared pages and failing on the client device, including from the JSON-escaped form Inertia.js emits in data-page attributes (http:\/\/localhost:9000\/...). The share proxy now rewrites those references in HTML/JS/CSS/JSON to a /__lerd_vite__/<port>/ prefix and forwards the prefixed paths through to the local loopback service, with active-port tracking so transitive imports past the first hop keep working. Vite HMR's WebSocket upgrade routes to the same listener so hot reload works for the device viewing the share without per-project vite.config.js changes.

Toggling TLS on or off used to leave running share listeners pointing at the old backend port until a manual restart. And under the hood, lerd secure / lerd unsecure had three drifting copies (CLI, dashboard, MCP); MCP was missing both the Stripe listener restart and the LAN share refresh, and any new step added today was at risk of being forgotten in two of the three places. The consolidation lands siteops.SetSecured as the single source of truth for the TLS toggle flow: every entry-point now calls one function with no per-caller variation, and all post-toggle work (Stripe webhook restart, LAN share rebind) goes through HTTP notifications to the daemon so adding a new step happens in exactly one place.

The Referer-based Vite routing the proxy used briefly was deliberately dropped: the share listens on 0.0.0.0, so trusting a Referer for the target port would let any device on the network craft Referer: http://host/__lerd_vite__/22/anything and steer the proxy at SSH or the database. Active-port fallback covers every legitimate case anyway.

Full prose and per-bullet detail in docs/changelog.md. New siteops.SetSecured covered by five unit tests; the share proxy got fifteen new test cases covering gzip handling, the JSON-escaped slash rewriter, CSS url() terminator, WebSocket upgrade dispatch, active-port poisoning by non-Vite services, and the SSRF guard.

LAN/tunnel share fixes plus a siteops.SetSecured consolidation.

Tunnel share now decompresses gzip so asset URLs in the HTML actually get
rewritten. LAN share forwards X-Forwarded-Port and the nginx vhost template
stops overriding it with $server_port, so Ziggy and Symfony URL builders
stop emitting http://<lan-ip>:443/... when a site is shared over the LAN.
Vite dev URLs and other loopback services (RustFS, Mailpit, ...) are now
proxied through the share via a /__lerd_vite__/<port>/ prefix, with active
port tracking, WebSocket upgrade routing, and a JSON-escaped-slash pass for
the form Inertia.js puts in data-page payloads.

Toggling TLS now refreshes any running LAN share and Stripe listener so
they re-bind to the new backend port. Under the hood the secure/unsecure
flow had three drifting copies (CLI, dashboard, MCP); they now all funnel
through siteops.SetSecured, with the post-toggle work (Stripe restart, LAN
share rebind) done via daemon HTTP notifications so adding a new step
happens in exactly one place.
@geodro geodro merged commit 521f32b into main May 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant