Skip to content

One-click deploy (Render + Fly.io) — and the four first-run fixes it surfaced - #471

Open
SecurID wants to merge 5 commits into
mainfrom
feat/one-click-deploy
Open

One-click deploy (Render + Fly.io) — and the four first-run fixes it surfaced#471
SecurID wants to merge 5 commits into
mainfrom
feat/one-click-deploy

Conversation

@SecurID

@SecurID SecurID commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes omadia one-click deployable to a visitor's own cloud account, and fixes everything the live smoke test surfaced along the way. The full flow was verified end to end on a real Fly.io org: deploy → login → readiness banner → key in → agents/channels/skills live → restart → routines live.

1. Deploy paths (new)

  • render.yaml — backs a Deploy to Render button: middleware + admin UI from the published GHCR images, managed Postgres 17 with pgvector, persistent /data disk, VAULT_KEY generated by Render (generateValue), region frankfurt. Zero pasted secrets: the /setup wizard collects the LLM key at runtime. The web-ui reaches the middleware via fromService/envVarKey: RENDER_EXTERNAL_URL.
  • fly/deploy.sh + three machine configs — Fly has no blueprint-style button, so the equivalent is one command. Provisions three apps: a private-only pgvector/pgvector:pg17 (Fly's unmanaged postgres-flex ships without pgvector; Managed Postgres gates vector behind a dashboard toggle at a much higher price), middleware with a /data volume, and the web-ui — plus generated secrets. ~$10/month.
  • README Deployment section documents both.

2. MIDDLEWARE_URL was frozen at image build time (fix)

Next evaluates rewrites() at build time; the published web-ui image had http://middleware:8080 baked into its routes manifest, so every browser call through /bot-api failed with ENOTFOUND on any non-compose host while runtime MIDDLEWARE_URL was silently ignored. /bot-api/* and /p/* are now catch-all route handlers (app/_lib/middlewareProxy.ts) resolving the env per request — streaming both ways (SSE, ZIP uploads), multi-Set-Cookie preserved, redirects passed through. Build ARG removed from the Dockerfile, CI workflows, and the compose build overlay; desktop-apps.yml stops pretending to bake the kernel URL (the supervisor injects it at spawn — this also fixes the latent wrong-port proxy there). 6 regression tests.

3. Fresh installs now say what's missing (new)

Without an LLM key the orchestrator publishes nothing and every operator surface 503s — the UI showed raw GET … failed: 503 strings. RuntimeReadinessBanner probes one operator route and, on the structured multi_orchestrator_unavailable, shows a card naming the cause with a link to /admin/settings; it clears itself once the key lands. EN/DE, 6 tests.

4. 17 broken t.rich placeholders (fix, regression from #447)

Messages used ICU argument syntax ({toolName}) where components pass chunk functions — next-intl splices the raw function into the output and React hard-crashes the page (RSC: "Functions cannot be passed to Client Components"). Affected: routines (crashed on a live instance), login no-providers, onboarding modal, builder preview, and four admin pages. All 12 keys converted to tag syntax in both locales; a sweep audit now finds zero. The locale-parity check couldn't catch this class (both locales consistently wrong) — a follow-up issue could add an argument-vs-tag cross-check against component usage.

5. next dev was broken on main (fix)

Tailwind v4 scans comments as class candidates; two test-file comments containing a literal bracket-class with var(...) compiled into invalid CSS and 500'd every page in dev (prod unaffected). Comments reworded.

Verification

  • Live Fly smoke instance: full first-run arc verified (proxy 500→200, login, banner, key save, routines after restart)
  • web-ui: 198/198 tests, tsc --noEmit, ESLint, i18n:check clean
  • Build paths: local dev ✓, local next build ✓, standalone server with runtime MIDDLEWARE_URL ✓ (proxy + routines verified against a stub middleware), Docker image ✓ (built 3× on Fly's remote builder, live-verified), compose ✓ (sets the env at runtime), desktop ✓ (supervisor sets it at runtime)
  • Remaining gate: this PR's own CI run (workflows verified by parse + review only)

Notes for review

  • Release ordering: the deploy button/script pull :latest GHCR images — they only work for real visitors once a release containing the proxy fix is published. The omadia.ai /docs deploy section (separate PR in omadia-homepage) should merge after that release.
  • Follow-up candidates (intentionally not in this PR): hot-mount routines on key save (only feature still needing a restart); i18n validator rule for the t.rich mismatch class.

🤖 Generated with Claude Code

SecurID and others added 4 commits July 9, 2026 11:00
render.yaml backs a 'Deploy to Render' button: middleware + admin UI from
the published GHCR images, managed Postgres 17 (pgvector), a persistent
/data disk, and a generated VAULT_KEY — the /setup wizard collects the
LLM key at runtime, so a deploy needs zero pasted secrets. MIDDLEWARE_URL
reaches the web-ui via fromService/envVarKey RENDER_EXTERNAL_URL.

Fly has no blueprint-style button, so fly/deploy.sh is the one-command
equivalent: three apps (pgvector/pgvector:pg17 private-only, middleware
with /data volume, web-ui), volumes, and generated secrets. Fly's own
Postgres offerings don't fit (postgres-flex ships without pgvector,
Managed Postgres gates the vector extension behind a dashboard toggle),
hence the same pgvector image the compose stack uses. Smoke-tested end
to end on a live Fly org.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…replace baked rewrites

Next evaluates rewrites() at build time and freezes the destinations into
routes-manifest.json. The published web-ui image was built with
ARG MIDDLEWARE_URL=http://middleware:8080, so every browser call through
/bot-api proxied to a hostname that only exists on the compose network —
on Fly/Render/any other host the proxy died with ENOTFOUND and every
operator page showed 'GET … failed: 500'. Runtime MIDDLEWARE_URL was
silently ignored (only server-side fetches honored it, which is why page
shells still rendered).

/bot-api/* and /p/* are now catch-all route handlers backed by
app/_lib/middlewareProxy.ts, resolving MIDDLEWARE_URL per request:
streaming both directions (SSE, plugin-ZIP uploads), multiple Set-Cookie
preserved, redirects passed through, hop-by-hop headers stripped. One
image now runs anywhere; the compose default still works because compose
sets the same value as runtime env.

Consequences swept: Dockerfile build ARG removed (with a warning against
reintroducing it), CI workflows no longer pass the inert build-arg, the
compose build overlay drops it, and desktop-apps.yml stops pretending to
bake the kernel URL — the desktop supervisor already injects it at spawn
(this also fixes the latent wrong-port proxy when the kernel doesn't get
its preferred port).

Verified: 6 regression tests (core assertion: same handler, changed env,
changed target), plus a live Fly deployment where /bot-api/v1/auth/providers
went 500 → 200 and login started working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…olders; unbreak next dev

Three first-run findings from smoke-testing the one-click deploy:

1. Readiness banner. A fresh install has no LLM key, so the orchestrator
   never publishes chatAgent@1/orchestratorRegistry@1 and every operator
   surface answers 503 multi_orchestrator_unavailable — the UI showed raw
   'GET … failed: 503' strings with no hint at the cause.
   RuntimeReadinessBanner (mounted next to SessionWatcher) probes one
   operator route, and on the structured 503 shows a card naming the
   cause with a link to /admin/settings. Re-probes on focus + heartbeat
   while visible, so it clears itself the moment the key is saved.

2. t.rich placeholder syntax (regression from #447). 17 placeholders
   across 12 message keys used ICU argument syntax ({toolName}) while
   the component passes chunk functions — next-intl splices the raw
   function into the render output and React throws ('Functions cannot
   be passed to Client Components'), hard-crashing the routines page,
   login no-providers state, onboarding modal, builder preview, and four
   admin pages whenever those paths render. The locale-parity check
   couldn't catch it because both locales were consistently wrong. All
   converted to tag syntax (<toolName></toolName>) in en+de; audited to
   zero via a t.rich/message cross-check sweep.

3. next dev was 500ing on every page: Tailwind v4 scans comments as
   class candidates, and two test-file comments containing a literal
   bracket-class with var(...) compiled into invalid CSS (dev-only;
   prod builds tolerate it). Reworded the comments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@SecurID
SecurID marked this pull request as ready for review July 9, 2026 09:40
@SecurID

SecurID commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

Important Extension for future PR: routines is the only feature that can't hot-enable on key save (it registers at boot, hence a restart is needed), and the middleware log even hints at it. Making it late-mount like chat does would remove the last manual step from first-run

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