Files
twenty/.github/workflows
Félix Malfait 3d8207af0f ci(preview-env): replace bore.pub with Cloudflare quick tunnel (#20459)
## 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>
2026-05-11 21:39:22 +02:00
..