Context
All critical system initialization currently runs from the auth middleware on the first SSR request, at commit 3531916:
src/middleware.ts:57-69 — runInitialization() sequentially runs: ensureDatabaseReady(), ensureEffectQueueWorkerStarted(), ensureDoceSharedNetwork(), ensureGlobalPnpmVolume(), ensureGlobalOpencodeStarted(), startImagePrewarm(), startDefaultsBootstrap(), ensureTailscaleStarted().
- It's triggered via
ensureInitialized() inside getSetupNeeded(), which the auth middleware awaits on every request (with a 5s retry cooldown on failure).
src/middleware.ts:99-109 — a session-cleanup setInterval is registered at module-eval time.
- Module-scope side effects at import time:
initConfig(), shutdown-handler installation (src/middleware.ts:27-37).
Problems: the first request (and every request during a failed-startup window) pays for Docker/OpenCode/Tailscale orchestration; a failure in any subsystem turns into request-path errors; boot order is untestable; and the middleware file mixes two unrelated concerns (boot + auth).
What to do
- Extract
runInitialization() and the cleanup interval into a dedicated server bootstrap module (e.g. src/server/bootstrap.ts), keeping the same ordering and the existing retry-cooldown semantics.
- Trigger it from the Node entrypoint instead of per-request. With
@astrojs/node standalone, the conventional hook is Astro's server entry / astro:server:start in dev — make sure BOTH pnpm dev and the production node ./dist/server/entry.mjs path initialize. (If a per-request await ensureInitialized() guard must remain as a fallback for edge cases, it should be a cheap no-op after first success — it already is — but it should no longer be the primary trigger.)
- Keep
src/middleware.ts responsible only for security headers + auth/setup redirects.
- Preserve current behavior: setup detection (
getSetupNeeded) must still work on a fresh DB, and startup failures must still be retried with the cooldown rather than crashing the process.
Escape hatch
If Astro 6 / @astrojs/node offers no clean pre-request server hook for both dev and prod, document that constraint in the PR and instead reduce the middleware's role to calling a single ensureBootstrapped() from the new module — the extraction is still worth it for testability and separation.
Acceptance criteria
- Fresh-clone first run: setup flow at
/setup still works; after setup, projects/queue/OpenCode/Tailscale come up as before.
src/middleware.ts contains no subsystem imports (docker/opencode/tailscale/queue/defaults).
- App restart with existing data: first request does not block on Docker/OpenCode startup (verify via logs/timing).
Dependency
Land the verification baseline (separate issue) first — this refactor touches the boot path and needs pnpm check as a safety net.
Context
All critical system initialization currently runs from the auth middleware on the first SSR request, at commit
3531916:src/middleware.ts:57-69—runInitialization()sequentially runs:ensureDatabaseReady(),ensureEffectQueueWorkerStarted(),ensureDoceSharedNetwork(),ensureGlobalPnpmVolume(),ensureGlobalOpencodeStarted(),startImagePrewarm(),startDefaultsBootstrap(),ensureTailscaleStarted().ensureInitialized()insidegetSetupNeeded(), which the auth middleware awaits on every request (with a 5s retry cooldown on failure).src/middleware.ts:99-109— a session-cleanupsetIntervalis registered at module-eval time.initConfig(), shutdown-handler installation (src/middleware.ts:27-37).Problems: the first request (and every request during a failed-startup window) pays for Docker/OpenCode/Tailscale orchestration; a failure in any subsystem turns into request-path errors; boot order is untestable; and the middleware file mixes two unrelated concerns (boot + auth).
What to do
runInitialization()and the cleanup interval into a dedicated server bootstrap module (e.g.src/server/bootstrap.ts), keeping the same ordering and the existing retry-cooldown semantics.@astrojs/nodestandalone, the conventional hook is Astro's server entry /astro:server:startin dev — make sure BOTHpnpm devand the productionnode ./dist/server/entry.mjspath initialize. (If a per-requestawait ensureInitialized()guard must remain as a fallback for edge cases, it should be a cheap no-op after first success — it already is — but it should no longer be the primary trigger.)src/middleware.tsresponsible only for security headers + auth/setup redirects.getSetupNeeded) must still work on a fresh DB, and startup failures must still be retried with the cooldown rather than crashing the process.Escape hatch
If Astro 6 / @astrojs/node offers no clean pre-request server hook for both dev and prod, document that constraint in the PR and instead reduce the middleware's role to calling a single
ensureBootstrapped()from the new module — the extraction is still worth it for testability and separation.Acceptance criteria
/setupstill works; after setup, projects/queue/OpenCode/Tailscale come up as before.src/middleware.tscontains no subsystem imports (docker/opencode/tailscale/queue/defaults).Dependency
Land the verification baseline (separate issue) first — this refactor touches the boot path and needs
pnpm checkas a safety net.