Skip to content

feat(dev): cache, SSE, email, web push, Bull-board on by default in dev + clickable URL table - #67

Merged
agjs merged 7 commits into
mainfrom
feat/dev-defaults-v1
May 30, 2026
Merged

agjs merged 7 commits into
mainfrom
feat/dev-defaults-v1

Conversation

@agjs

@agjs agjs commented May 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Approaching v1.0 the operator should be able to exercise every feature locally without flipping flags. This PR turns the free defaults on in dev (the ones that don't need a third-party account), auto-generates a VAPID keypair on first boot, moves Bull-board off the universal port 3000, and prints a clickable URL table after ./setup.sh --up.

Defaults flipped in dev (prod unchanged)

  • Cache via Valkey: CACHE_ENABLED=true, CACHE_PROVIDER=valkey. Mirrors prod's cache code path; the in-process memory fallback is no longer the dev default.
  • Real-time notifications (SSE): NOTIFICATIONS_SSE_ENABLED=true. Valkey is already running in the base stack.
  • Email via Mailpit: WITH_MAILPIT defaults to 1 in dev; api-dev is pre-wired with EMAIL_PROVIDER=smtp, SMTP_HOST=mailpit, SMTP_PORT=1025. Every transactional email lands at http://localhost:8025 from first boot.
  • Web push (VAPID): new infra/compose/scripts/dev-vapid-init.sh generates a keypair inside api-dev on first detached up, writes it into compose/.env, and force-recreates api-dev + ui-dev. Idempotent; silent on subsequent runs. Pattern mirrors glitchtip-fetch-dsn.sh.
  • Bull-board: WITH_BULLMQ defaults to 1 in dev. Two-layer prod guard: dev.sh overlay-merge still requires STACK=dev, and the env validator continues to reject WITH_BULLMQ=1 in NODE_ENV=production.

Bull-board port move

Bull-board container was on the universal-default port 3000 (collides with Grafana internal, generic Node apps, React dev servers). Moved both the container PORT and the host mapping to 7332, joining the BoringStack 733x range alongside api (7330) and ui (7331). Traefik loadbalancer.server.port and the env-example comment updated to match.

Clickable service-URL table

./setup.sh --up now ends with a table:

BoringStack is up. Open these:
  UI                http://localhost:7331
  API               http://localhost:7330
  API OpenAPI       http://localhost:7330/swagger
  Mailpit           http://localhost:8025
  Bull-board        http://localhost:7332
  Grafana           http://localhost:3010
  Prometheus        http://localhost:9090
  Alertmanager      http://localhost:9093
  GlitchTip         http://glitchtip.localhost

URLs use OSC 8 hyperlink escape sequences so they're clickable in iTerm2, Terminal.app, Ghostty, WezTerm, VS Code, GNOME Terminal, Konsole. Rows are conditional on the active overlays read from compose/.env.

Bug fixes folded into the same PR

  • VAPID partial-default tripped the env validator's all-or-nothing invariant on first boot; defaults are now all three empty so api boots clean before the init script seeds them.
  • recipes/add-background-job.mdx linked to the non-existent bull-board.localhost; corrected to bullmq.localhost (and the new http://localhost:7332).

Out of scope (separate decision)

  • OAuth (Google, GitHub, LinkedIn) dev experience.
  • Stripe billing dev experience.
  • AI provider dev experience.

These need real third-party accounts; the stub-vs-real-credentials philosophy is a separate v1.0 conversation.

Test plan

  • STACK=dev infra/compose/compose/dev.sh config --services includes bullmq-dashboard + mailpit by default.
  • STACK=prod ... WITH_BULLMQ=1 WITH_MAILPIT=1 dev.sh config --services does NOT include them (dev.sh STACK=dev guard).
  • bash -n on dev.sh + dev-vapid-init.sh + setup.sh.
  • bun run build clean for the docs site.
  • bun run test in apps/api: 997 pass / 0 fail.
  • Manual: fresh ./setup.sh --up, verify VAPID keys are auto-generated and api-dev boots clean.
  • Manual: confirm Bull-board reachable at both http://localhost:7332 and http://bullmq.localhost in dev.

agjs added 7 commits May 30, 2026 17:37
…ault in dev

Approaching v1.0 the operator should be able to exercise every feature
locally without flipping flags. Five surfaces were off in dev purely
because nobody had flipped the default; this commit flips them, with
prod behavior unchanged.

What changes in `STACK=dev ./dev.sh up -d`:

- **Cache (Valkey)**: api-dev now boots with CACHE_ENABLED=true,
  CACHE_PROVIDER=valkey. Mirrors prod's cache code path so dev tests
  the real thing instead of the in-process memory fallback.
- **Real-time notifications (SSE)**: NOTIFICATIONS_SSE_ENABLED=true.
  Valkey is already running in the base stack, so this is free.
- **Email via Mailpit**: WITH_MAILPIT defaults to 1 in dev, and the
  api-dev container is pre-wired with EMAIL_PROVIDER=smtp,
  SMTP_HOST=mailpit, SMTP_PORT=1025. Every transactional email is
  captured at http://localhost:8025 from the first boot.
- **Web push (VAPID)**: new `infra/compose/scripts/dev-vapid-init.sh`
  generates a keypair inside api-dev on first detached `up`, writes
  it into compose/.env, and recreates api-dev + ui-dev so both pick
  up the keys. Pattern mirrors `glitchtip-fetch-dsn.sh`. Idempotent;
  silent on subsequent runs.
- **Bull-board**: WITH_BULLMQ defaults to 1 in dev. Two-layer prod
  guard preserved: the dev.sh overlay-merge block still checks
  `STACK=dev`, and the env validator continues to reject
  WITH_BULLMQ=1 in NODE_ENV=production.

Out of scope (separate decision):

- OAuth (Google, GitHub, LinkedIn) — needs real provider apps.
- Stripe billing — needs real test account.
- AI provider — needs real key or local Ollama overlay.
…d off port 3000

Two boot-blockers + one doc typo from the dev-defaults pass.

- VAPID: docker-compose.yml defaulted WEB_PUSH_VAPID_SUBJECT to a
  placeholder while leaving _PUBLIC + _PRIVATE empty, which the env
  validator's all-or-nothing invariant rejects. Default all three
  empty so api-dev boots clean; dev-vapid-init.sh then writes all
  three together into compose/.env and recreates the container.
- Bull-board port: container was on the universal-default 3000
  (collides with too much other tooling: Grafana internal, generic
  Node apps, React dev servers). Move both container PORT and host
  mapping to 7332, joining the BoringStack 733x range used by api
  (7330) and ui (7331). Traefik loadbalancer.server.port updated to
  match. Comment in .env.example bumped to the new host port.
- recipes/add-background-job.mdx pointed at the non-existent host
  `bull-board.localhost`. Real Traefik route is `bullmq.localhost`.
After ./setup.sh --up returns, emit a service-name + URL table for
every service the operator can open in a browser. URLs use OSC 8
hyperlink escape sequences so they're clickable in iTerm2, Terminal.app,
Ghostty, WezTerm, VS Code, GNOME Terminal, Konsole — falling back to
plain text on terminals without OSC 8 support (most still URL-detect
http:// strings, so the link stays reachable).

Rows are conditional on the active overlays:
- UI, API, API OpenAPI always shown (base stack).
- Mailpit + Bull-board shown unless WITH_MAILPIT=0 / WITH_BULLMQ=0.
- Grafana + Prometheus + Alertmanager shown unless WITH_OBSERVABILITY=0.
- GlitchTip shown unless WITH_GLITCHTIP=0.

Detection reads compose/.env directly so the table reflects the
operator's actual config, not just the documented defaults.
…ch warning

`deadly0/bull-board` publishes amd64-only images. On arm64 Macs the
boot-time warning ("platform does not match the detected host
platform") is noisy and the container runs under Rosetta emulation
anyway. Pinning `platform: linux/amd64` explicitly silences the
warning without changing behaviour.

Bull-board is a dev-only, low-traffic tool, so the emulation cost is
acceptable. The real fix is to drop this container entirely and mount
bull-board's npm library as an admin route on the api itself, which
would also remove the platform constraint by construction. Tracked as
a follow-up.
api-dev runs in both the dev and smoke profiles. The previous pass
set EMAIL_PROVIDER=smtp + SMTP_HOST=mailpit unconditionally in the
container env, which broke the verify-email Playwright tests in
smoke: mailpit isn't in the smoke stack, so transactional email
sends to a non-existent target.

Move the override out of docker-compose.yml and into dev.sh, gated
behind the same `STACK=dev && WITH_MAILPIT=1` block that already
brings up the Mailpit overlay. Smoke stays on its previous email
behaviour (empty EMAIL_PROVIDER → noop); dev still gets Mailpit
auto-wired without operator action.
`glitchtip.localhost` (the URL the GlitchTip startup banner prints)
requires Traefik to route, but Traefik is profiles: [prod] only —
so in dev that URL resolves to nothing reachable.

New overlay `docker-compose.glitchtip-dev-ports.yml` adds
`ports: ["8055:8000"]` to glitchtip-web. dev.sh merges it when
STACK=dev && WITH_GLITCHTIP=1 (right where the prod-labels overlay
is merged for STACK=prod). Prod stays Traefik-gated as before.

setup.sh's URL table now points GlitchTip at http://localhost:8055
instead of the unreachable .localhost name.
…pi boots

Smoke profile shares api-dev but doesn't run mailpit, so the previous
empty-string defaults for EMAIL_PROVIDER and SMTP_PORT made smoke's
api fail to boot ("EMAIL_PROVIDER: Expected union value"). Fall back
to schema-valid values (cloudflare / "" / 25) so the env validator
passes; dev.sh still exports the mailpit-shaped overrides when
STACK=dev so dev behaviour is unchanged.
@agjs
agjs merged commit da59261 into main May 30, 2026
25 checks passed
@agjs
agjs deleted the feat/dev-defaults-v1 branch May 30, 2026 17:54
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