Hosting signup: free self-host branch with a downloadable deployment bundle - #1466
Conversation
…bundle After customizing, the signup asks where the blog will live. Managed stays the default and its flow is untouched; choosing to self-host composes the same config document through the hosting API, wraps it with the files a deployment needs and hands over a zip. That branch never calls createTenant, so no name is reserved and no payment clock starts. The archive is written in the store format by hand: there is no zip dependency in this workspace, and the files are small text.
|
Merge order: this needs #1465 merged and deployed first. The endpoint it calls only reaches production on a merge to develop, so merging this one first would ship a download button that 404s. |
Code Review by Qodo
1. useCallback missing dependencies
|
|
Warning Review limit reached
Next review available in: 97 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Summary by QodoHosting signup: add free self-host option that downloads a pinned deployment bundle
AI Description
Diagram
High-Level Assessment
Files changed (8)
|
PR Summary by QodoHosting signup: add free self-host download bundle path
AI Description
Diagram
High-Level Assessment
Files changed (8)
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: bfe8cd73dd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const hasDomain = !!input.domain?.trim(); | ||
| const domain = hasDomain ? input.domain!.trim() : EXAMPLE_DOMAIN; |
There was a problem hiding this comment.
Validate the optional domain before generating configs
When a user enters a common URL-shaped value such as https://blog.example.com, this raw value is inserted everywhere as a hostname, producing README URLs such as https://https://blog.example.com/rss.xml; whitespace or newline-containing input can also corrupt the generated .env and Caddyfile. Validate and normalize this field to a hostname before building the bundle, or reject it with a form error.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Valid, and the injection half of it was the serious part: fixed in 1aed805. The field now goes through normalizeDomain(), which parses the value (adding a scheme when absent), takes the hostname, lowercases it and checks it against a DNS-label pattern; anything else returns null and the bundle falls back to the placeholder rather than writing the paste into a config file. So https://blog.example.com/ becomes blog.example.com instead of producing https://https://..., and a value carrying a newline can no longer add lines to the generated Caddyfile, where every line is a directive. The picker shows an inline message when a typed value is unusable, so it is not silently ignored. Tests cover the pasted-URL forms, the rejected forms and a Caddyfile-injection attempt.
| const sha = await hostingApi | ||
| .health() | ||
| .then((h) => (typeof h.sha === "string" ? h.sha.slice(0, 7) : "")) | ||
| .catch(() => ""); | ||
| if (!sha) { |
There was a problem hiding this comment.
Reject placeholder or malformed build SHAs
When the hosting API is built without GIT_SHA, buildHealthPayload() in apps/self-hosted/hosting/api/src/utils/build-info.ts returns the nonempty value unknown; this check accepts it and generates TAG=sha-unknown, so the promised pinned deployment cannot pull its image. The same problem occurs with any short or non-hex response, so require a valid commit SHA before enabling the download.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch, thank you: fixed in 1aed805. The check was only non-empty, so GIT_SHA unset would have produced TAG=sha-unknown, a bundle that looks fine and fails on the user's first docker compose up. It now requires /^[0-9a-f]{7,40}$/ before enabling the download and refuses with the existing message otherwise. Test asserts that a health response of unknown produces no archive at all.
| describe("self-host branch", () => { | ||
| // The whole point of this branch is that it is free and reserves nothing. | ||
| // Every test here therefore asserts what was NOT called as much as what was. | ||
| async function reachCustomizeStep() { |
There was a problem hiding this comment.
Initialize self-host API mocks within the self-host suite
When these tests are run in isolation with a name filter, the composeConfig and default health mock implementations are never installed because they live in the unrelated first describe's beforeEach; the inherited renewal setup only clears calls. Consequently the download test receives undefined from composeConfig and fails before reaching its assertions. Give this describe its own mock setup so focused and reordered runs are reliable.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Valid, fixed in 1aed805: the self-host describe now installs its own composeConfig, health and templates mocks, since the enclosing beforeEach only clears calls. Verified by running the suite with -t "self-host branch", which now passes (5 tests, 23 skipped) rather than failing on an undefined response.
Code Review by Qodo
1.
|
|
All three fixed at fc86716. Community bundles named the wrong editor account. Valid, and the same class as the endpoint findings on #1465: the README said PORT and the Caddyfile could drift. Valid: A blank domain left live commands aimed at the example. Valid and the most user-visible of the three: the no-domain README told readers to replace the placeholder in the Caddyfile only, while the generator command and Verified by regenerating a community bundle with no domain: CRC-clean archive, README naming @alice as the editor, both warnings present, then extracted and run (container healthy, site 200 on the configured port). 2666 web tests, typecheck and lint clean. The bot round on this PR has also settled: four Qodo findings resolved and the useCallback one dismissed. |
Second half of #1453. Depends on #1465 (the
/v1/tools/compose-configendpoint) being merged AND deployed: until then this branch's request 404s and the user sees "Not found".The fork
After customizing, the signup asks where the blog will live. Managed is the default and its flow is untouched, so the paid path costs no extra click and every existing spec passes unchanged. Choosing to self-host swaps Continue for a download button.
The self-host branch calls
composeConfigand nothing else. It never callscreateTenant, never fetches payment instructions, and deliberately leavescreatedForRefalone, so a user who tries self-hosting and then switches back to managed still gets a proper reservation. Tests assert those absences directly, since they are the whole point of the branch being free.The bundle
A zip containing
config.json(the composed document, already stripped of managed-only markers server-side),docker-compose.yml,.env,Caddyfileand a README covering run, HTTPS, config edits, upgrade and rollback, the SEO generator and Hivesigner setup.The image tag is read from the platform's own
/healthrather than composed in the client, so a bundle pins a build that demonstrably exists; both images come from one commit in one CI run, so the tag resolves for the blog and the hosting API alike. If/healthcannot be reached the download is refused with a message rather than shipping a bundle pinned to nothing.There is no zip dependency anywhere in this workspace, and adding one costs an
allowBuildsreview plus the minimum-release-age wait, so the archive is written by hand in the STORE format (no compression to get wrong on small text files). Rather than trust that arithmetic, it is verified three ways:zipfile.testzip();docker compose upreported healthy, the site answered 200, and it served the bundle's own mountedconfig.json.A signup-level test decodes the real archive out of the Blob handed to the browser and asserts the pinned tag matches the running build.
Verified: 2657 web tests (58 in hosting-signup, 4 new plus 10 for the bundle), typecheck, both icon audits and lint clean.