feat: kiqr share — expose your local site via a Cloudflare quick tunnel - #28
Merged
Merged
Conversation
…nels `kiqr share` opens an instant, account-less Cloudflare quick tunnel (via the external `cloudflared` binary) so a developer can hand a public URL to a collaborator while their local WordPress site keeps running. The kiqr agent's Traefik proxy on :5477 routes purely by Host header, so the tunnel is pointed at Traefik with `--http-host-header <project host>` to force a matching route. cloudflared still forwards the real public host as `X-Forwarded-Host` (and the scheme as `X-Forwarded-Proto`), so WORDPRESS_CONFIG_EXTRA now derives WP_HOME/WP_SITEURL from those headers when present, falling back to HTTP_HOST over http for normal local requests -- keeping local behavior identical. The PHP `$$` compose escaping is preserved. - src/lib/share.ts: pure, injectable helpers (isCloudflaredInstalled, buildCloudflaredArgs, parseTunnelUrl, cloudflaredInstallHint, TRAEFIK_BASE_URL) with unit tests. - src/commands/share.tsx: thin Ink command that validates config and a running wordpress container, checks for cloudflared, spawns the tunnel, prints the public URL, and kills the child on Ctrl+C/unmount. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this adds
kiqr shareexposes a developer's running local WordPress site at a public URL through a Cloudflare quick tunnel — an instant, no-account, no-server tunnel. This is the MVP of the collaboration story: runkiqr up, thenkiqr share, and hand the printedhttps://<sub>.trycloudflare.comURL to a collaborator.The command:
kiqr.yaml); tells you to runkiqr init/kiqr upif not initialized.wordpresscontainer is running (reusesisContainerRunning+containerNameFor); otherwise asks you to runkiqr up.cloudflaredbinary; if missing, prints a per-OS install hint and exits.cloudflared, parses its output for the public URL, prints it boldly, and stays attached. Ctrl+C (unmount) kills the child tunnel process.The architecture (the crux)
The kiqr agent's Traefik proxy listens on
:5477and routes purely byHostheader — the project router matchesHost(<project hostname>). A naivecloudflared --url http://localhost:5477forwardsHost: <random>.trycloudflare.com, which matches no project router and falls through to the splash/404 page. So:Force the host header Traefik expects. We run:
cloudflared tunnel --no-autoupdate --url http://localhost:5477 --http-host-header <project hostname>Now Traefik routes the tunneled request to the right project.
But WordPress must generate links with the public host, not the internal Traefik hostname, or every asset/link breaks for the remote visitor. cloudflared still forwards the original public host as
X-Forwarded-Host(andX-Forwarded-Proto: https). SoWORDPRESS_CONFIG_EXTRAinBitnamiRuntimeProvidernow derivesWP_HOME/WP_SITEURLfromX-Forwarded-Hostwhen present, falling back toHTTP_HOST, and treats the request ashttpswhenX-Forwarded-Protoishttps:Local behavior is unchanged: with no
X-Forwarded-Host, it usesHTTP_HOSToverhttp, exactly as before. The$→$$compose escaping is preserved (the string is embedded in the docker-compose YAML).Requires a restart to take effect
The
WORDPRESS_CONFIG_EXTRAchange only lands in a container's environment when the compose file is regenerated and the container recreated. On an existing project, runkiqr restart(or a freshkiqr up) beforekiqr shareso the X-Forwarded-Host-aware config is applied.External dependency
cloudflaredis an external tool the user installs themselves (Homebrew on macOS, winget on Windows, or the Cloudflare download page on Linux).kiqr sharedetects its absence and prints the right hint per platform.Tests / gates
All four gates green locally:
tsc --noEmit) ✅vitest run) ✅ — 207 passedtsup) ✅biome check .) ✅New tests cover
parseTunnelUrl(clean line, box-drawing/ANSI noise, no-url, http vs https),buildCloudflaredArgs,isCloudflaredInstalled(mocked exec found/not-found), and the per-platform install hint. TheBitnamiRuntimeProvidertest now asserts the new X-Forwarded-Host-aware string keeps theHTTP_HOSTfallback, thehttpsproto check, both URL constants, and the$$escaping.NOT verified here
This was implemented and validated without Docker, cloudflared, or a display — only pure logic via tests + typecheck/build/lint. The live end-to-end tunnel was not run. Specifically un-verified:
cloudflaredquick tunnel routes through Traefik with the forced host header and reaches the project (not the splash page).https://*.trycloudflare.comURLs for assets/links/redirects viaX-Forwarded-Host/X-Forwarded-Proto.@kjellberg — please smoke-test on a real machine:
kiqr restartan existing project (or freshkiqr up), thenkiqr share, and confirm the public URL loads the site with working assets.Docs
Not touching
README.mdto avoid conflicting with the open README rewrite (#26); docs forsharewill follow in a separate PR.🤖 Generated with Claude Code