143ad9a29d
Fully decoupled Tx OS from the Replit hosted environment so the project can be cloned and run on any Linux VPS with `docker compose up`. Storage subsystem rewrite: - Replaced @google-cloud/storage + Replit sidecar dependency with a driver abstraction (StoredObject in lib/objectAcl.ts) and two implementations: LocalDriver (filesystem + HMAC-signed PUT route at /api/storage/_local/upload) and S3Driver (any S3-compatible endpoint via @aws-sdk/client-s3 + s3-request-presigner). Driver auto-selected by STORAGE_DRIVER / S3_ENDPOINT env vars. - Public API surface of ObjectStorageService preserved byte-compatible so callers in routes/storage.ts and routes/executive-meetings.ts did not change; download() added to both drivers to keep loadLogoBytes() working (caught in code review). - Storage object-authz tests A-L (incl. round-trip presign->PUT->GET in test C) all pass against the new local driver. Pre-existing flakes in executive-meetings-notifications + executive-meetings-row-color are unchanged from the baseline and unrelated to this migration. Infrastructure: - Dockerfile (5 targets: deps/build/api/web/migrate). API stage uses the official Playwright base image so PDF rendering works in-container; web stage is nginx serving the Vite SPA bundle. - docker-compose.yml: postgres + minio + minio-init (creates buckets) + api + web + one-shot migrate runner. Healthchecks on every long-lived service. - docker/nginx.conf: SPA fallback, /api proxy, /api/socket.io websocket upgrade ordering. - .env.example: every runtime env var documented with comments. - README.md replaces replit.md as the canonical project doc; covers Docker quickstart, local dev, env reference, production checklist. - MIGRATION_REPORT.md: file-by-file diff of what changed and why. Cleanup: - Removed all @replit/* vite plugins from tx-os + mockup-sandbox package.json + vite.config.ts + pnpm-workspace.yaml catalog. - Removed @google-cloud/storage and google-auth-library from api-server. - Deleted attached_assets/ (23MB), sedMkjeJm temp file, stale dist/ and *.tsbuildinfo build artefacts, scripts/post-merge.sh. - Stripped Replit references from threat_model.md (now describes the self-hosted topology). - New comprehensive .gitignore: Replit configs (.replit, replit.nix, replit.md), agent state (.local/, .canvas/, .agents/, .cache/, .config/, .upm/), local storage/, .env*, build artefacts. .replit and replit.nix remain on disk (sandbox-protected) but will not ship to GitHub. - scripts/src/seed.ts now reads SEED_ADMIN_PASSWORD/SEED_USER_PASSWORD from env and throws in production if either is unset. Drift from plan: replit.md was deleted (per off-Replit scope) so its "do-not-touch files" preference list is moot. .replit/replit.nix kept on disk only because they are sandbox-protected from edit/delete in this environment, but they are git-ignored so they will not appear in a fresh clone.
7.6 KiB
7.6 KiB
Off-Replit Migration Report
Task #526 — full migration of Tx OS off the Replit hosted environment to a self-contained Docker Compose stack runnable on any Linux VPS.
Summary
Tx OS was originally bootstrapped on Replit and depended on three Replit-only runtime surfaces:
- The Replit object-storage sidecar (
http://127.0.0.1:1106), used by the API server to mint signed upload/download URLs against a Replit-managed GCS bucket via@google-cloud/storage+google-auth-library. - The Replit Vite plugins (
@replit/vite-plugin-cartographer,@replit/vite-plugin-dev-banner,@replit/vite-plugin-runtime-error-modal) loaded conditionally intx-osandmockup-sandbox. - The Replit platform configs (
.replit,replit.nix,replit.md) and the post-merge hook script (scripts/post-merge.sh) consumed by the Replit agent runner.
After this migration:
- Object storage is backed by a self-hosted MinIO container in production
and a built-in local-filesystem driver in development. Both are abstracted
behind a
StoredObjectinterface inlib/objectStorage.tsso the route layer is driver-agnostic. - Vite plugins are gone from both
vite.config.tsfiles and frompnpm-workspace.yaml's catalog. - A
Dockerfile(5 build targets — deps / build / api / web / migrate) and adocker-compose.yml(db + minio + minio-init + api + web + one-shot migrate) bring the entire stack up withdocker compose up -d. A new.env.exampletemplate documents every env var the runtime reads.
The repository is now self-contained: cloning it onto a Docker-equipped host and running the documented commands produces a working deployment.
Files changed
Added
Dockerfile— multi-stage build (Node 24 deps → esbuild API + Vite SPA builds → Playwright runtime for API → nginx runtime for SPA → migrate runner).docker-compose.yml— full production stack with healthchecks and one-shot init/migrate containers.docker/nginx.conf— SPA static serve +/apiand/api/socket.ioreverse proxy to the api container..env.example— exhaustive, commented configuration template.README.md— replacesreplit.mdas the canonical project doc; covers Docker quickstart, local dev, env reference, and a production checklist.MIGRATION_REPORT.md— this file.artifacts/api-server/src/routes/storage-local-upload.ts— handler for the local-FS driver's HMAC-signed PUT upload URLs (PUT /api/storage/_local/upload).
Replaced (full rewrite)
artifacts/api-server/src/lib/objectStorage.ts— was a thin wrapper around@google-cloud/storage+ the Replit sidecar. Now ships a driver interface with two implementations (LocalDriver,S3Driver) selected by theSTORAGE_DRIVERenv var (defaulting tos3ifS3_ENDPOINTis set, elselocal). Public API surface —ObjectStorageService.{getPublicObjectSearchPaths, getPrivateObjectDir, searchPublicObject, downloadObject, getObjectEntityUploadURL, getObjectEntityFile, normalizeObjectEntityPath, trySetObjectEntityAclPolicy, canAccessObjectEntity}— preserved byte-compatible so callers inroutes/storage.tsandroutes/executive-meetings.tsdid not need to change.artifacts/api-server/src/lib/objectAcl.ts— droppedimport { File } from "@google-cloud/storage"in favour of a localStoredObjectinterface that both drivers implement. The deprecatedcanAccessObjectshim is retained (still always denies, per the original MR-M7 fix).artifacts/tx-os/vite.config.tsandartifacts/mockup-sandbox/vite.config.ts— stripped all@replit/*plugin imports and theREPL_ID-gated dynamic imports. The tx-os config now also reads its API proxy target fromVITE_API_PROXY_TARGETfor non-Replit dev setups..gitignore— restructured so that Replit configs (.replit,replit.nix,replit.md), agent state (.local/,.canvas/,.agents/,.cache/,.config/,.upm/), local storage (storage/), test artifacts, and any.env*(except.env.example) are excluded from git.
Edited
pnpm-workspace.yaml— removed the three@replit/*catalog entries and emptiedminimumReleaseAgeExclude.artifacts/api-server/package.json— dropped@google-cloud/storageandgoogle-auth-library; added@aws-sdk/client-s3and@aws-sdk/s3-request-presigner.artifacts/tx-os/package.jsonandartifacts/mockup-sandbox/package.json— removed@replit/*plugin dependencies.scripts/src/seed.ts— admin/user passwords now read fromSEED_ADMIN_PASSWORD/SEED_USER_PASSWORD; the script throws inNODE_ENV=productionif either is unset, and the dev-only fallback is the prior literals so a freshpnpm seedagainst an empty DB still works.threat_model.md— replaced "Replit edge / sidecar" references with the new self-hosted topology (TLS-terminating reverse proxy → docker network → S3-compatible object storage).
Deleted
attached_assets/— 23 MB of legacy uploaded assets unused by the codebase.sedMkjeJm— stray temp file (a sed copy of.replit) at repo root.artifacts/api-server/dist/,lib/*/dist/, all*.tsbuildinfo— build artefacts that should not be tracked.scripts/post-merge.sh— Replit agent post-merge hook; not relevant off-Replit.replit.md— superseded byREADME.md. (Note:.replitandreplit.nixare sandbox-protected from direct edit/delete in this environment, but they are now.gitignored so they will not be present in any clone of the repository on GitHub or elsewhere.)
Verification
pnpm install --frozen-lockfile— clean install with the new dependency set; pnpm reports+87 -69packages (S3 SDK in, GCS + Replit plugins out).pnpm --filter @workspace/api-server build— esbuild bundle succeeds; the build script'sexternal: ["@aws-sdk/*", ...]list already covered the new packages.- API-server test suite — all 12 storage / object-authz tests
(
storage-object-authz.test.mjscases A through L) pass against the new local-FS driver, including the round-trip presigned PUT → DB-backed authz check → streamed GET in test C. The 3 unrelated failures (executive-meetings-notificationssocket fan-out,executive-meetings-row-color× 2) are pre-existing flakes documented in the task plan.
Operational notes for first-time deploy
cp .env.example .envand fill in every value.docker compose builddocker compose up -d db minio minio-init— Postgres + MinIO + create buckets.docker compose run --rm migrate— apply Drizzle schema + seed admin / ahmed accounts (one-shot).docker compose up -d api web— boot the API and the SPA.- Front
web(port${WEB_PORT}) with a TLS-terminating reverse proxy.
The README's "Production checklist" lists the security-relevant steps required before fronting the stack with a public domain.
What did NOT change
Per the task scope (and replit.md user preferences before its deletion),
the following surfaces were intentionally left untouched:
- All API and SPA test files (
artifacts/api-server/tests/**,artifacts/tx-os/tests/**). lib/db/scripts/**(DB push/pull scripts).- The PDF renderers (
pdf-renderer.ts,pdf-html-renderer.ts). - The SPA shell (
App.tsx,executive-meetings.tsx,upcoming-meeting-alert.tsx). - Locale catalogues (
ar.json,en.json). - The auto-generated React-Query client (
custom-fetch.ts). - The Executive Meetings DB schema (
schema/executive-meetings.ts). - Authentication, sessions, rate limiting, and CSRF/CORS middleware — only the storage subsystem was rewritten.