3d8207af0f
## Summary `bore.pub`'s public server has been increasingly unreliable: tunnels register fine on the runner side (our `Create Tunnel` step always succeeds), but the bore.pub side later stops accepting inbound traffic, leaving the preview environment unreachable for the rest of the 5h keep-alive window with no signal back to the runner. Recent symptom: `curl http://bore.pub:50422` → `Couldn't connect to server`, while the corresponding action keeps sleeping. This PR replaces the `codetalkio/expose-tunnel` action with a direct invocation of `cloudflared` running an account-less [Cloudflare quick tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/). The tunnel is served from Cloudflare's edge so reliability is materially better, and the URL is HTTPS by default (`https://*.trycloudflare.com`), which also eliminates the mixed-content issues we'd hit when `SERVER_URL` was `http://bore.pub:port`. ## What changes - `Create Tunnel` step now: - Downloads a pinned `cloudflared` binary (`2026.3.0`) - Starts `cloudflared tunnel --url http://localhost:3000` in the background, logging to `$RUNNER_TEMP/cloudflared.log` - Polls the log for `https://<name>.trycloudflare.com` (up to 2 minutes), failing fast if the process exits - Writes the URL to the `tunnel-url` step output — same name as before, so no downstream changes needed - `Cleanup` step kills the `cloudflared` process for hygiene ## What stays the same - `SERVER_URL` plumbing through `.env` → `docker compose up` - `tunnel-url` artifact - `$GITHUB_STEP_SUMMARY` formatting - PR-comment dispatch (`twentyhq/ci-privileged`) - 5h keep-alive sleep ## Trade-offs - Quick tunnels are explicitly labelled by Cloudflare for "testing/development" use without an SLA. For our preview-env use case (ephemeral, per-PR) that fits, but if we ever need stable URLs on a custom domain we'd move to *named* tunnels — same `cloudflared` binary, plus a free Cloudflare account + delegated domain + a service token stored as a repo secret. Strictly additive when we want it. - `cloudflared` is pinned to `2026.3.0` to avoid surprise breakage from upstream releases. Bumping is a one-line change. ## Testing **Locally (macOS) — verified end-to-end:** - `cloudflared tunnel --url http://localhost:18080` against a `python3 -m http.server` - Regex `https://[a-zA-Z0-9-]+\.trycloudflare\.com` correctly extracts the URL from the log - `curl $URL/` returns the upstream server's response (HTTP 200, ~0.5s) - Process supervision: if `cloudflared` dies mid-wait, the step fails fast instead of hitting the 2-min timeout **Validation:** - `actionlint` passes (the remaining shellcheck warnings are in pre-existing steps, not my changes) - `shellcheck` on the new Create Tunnel script: clean **What's not testable from a PR (and why):** - The full keep-alive workflow runs on `repository_dispatch`, which always uses the workflow file from `main`. So the cloudflared logic only runs against PR contents *after* merge. - I'll trigger a one-off Ubuntu-runner test of just the install + URL extraction logic via a throwaway branch (`workflow_dispatch`-only) and link the run here before this merges. ## Test plan - [ ] Throwaway run validates: cloudflared installs on `ubuntu-latest`, prints the URL, regex matches, tunnel is reachable from outside the runner. - [ ] After merge, the next PR's preview environment uses `*.trycloudflare.com` instead of `bore.pub:port`, and the URL stays reachable for the full 5h window. - [ ] PR-comment bot still posts the preview URL correctly (link should now be `https://*.trycloudflare.com`). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>