Files
TX/MIGRATION_REPORT.md
T
riyadhafraa 143ad9a29d Task #526: Off-Replit migration & GitHub-ready cleanup
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.
2026-05-13 14:08:11 +00:00

7.6 KiB
Raw Blame History

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:

  1. 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.
  2. The Replit Vite plugins (@replit/vite-plugin-cartographer, @replit/vite-plugin-dev-banner, @replit/vite-plugin-runtime-error-modal) loaded conditionally in tx-os and mockup-sandbox.
  3. 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 StoredObject interface in lib/objectStorage.ts so the route layer is driver-agnostic.
  • Vite plugins are gone from both vite.config.ts files and from pnpm-workspace.yaml's catalog.
  • A Dockerfile (5 build targets — deps / build / api / web / migrate) and a docker-compose.yml (db + minio + minio-init + api + web + one-shot migrate) bring the entire stack up with docker compose up -d. A new .env.example template 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 + /api and /api/socket.io reverse proxy to the api container.
  • .env.example — exhaustive, commented configuration template.
  • README.md — replaces replit.md as 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 the STORAGE_DRIVER env var (defaulting to s3 if S3_ENDPOINT is set, else local). Public API surface — ObjectStorageService.{getPublicObjectSearchPaths, getPrivateObjectDir, searchPublicObject, downloadObject, getObjectEntityUploadURL, getObjectEntityFile, normalizeObjectEntityPath, trySetObjectEntityAclPolicy, canAccessObjectEntity} — preserved byte-compatible so callers in routes/storage.ts and routes/executive-meetings.ts did not need to change.
  • artifacts/api-server/src/lib/objectAcl.ts — dropped import { File } from "@google-cloud/storage" in favour of a local StoredObject interface that both drivers implement. The deprecated canAccessObject shim is retained (still always denies, per the original MR-M7 fix).
  • artifacts/tx-os/vite.config.ts and artifacts/mockup-sandbox/vite.config.ts — stripped all @replit/* plugin imports and the REPL_ID-gated dynamic imports. The tx-os config now also reads its API proxy target from VITE_API_PROXY_TARGET for 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 emptied minimumReleaseAgeExclude.
  • artifacts/api-server/package.json — dropped @google-cloud/storage and google-auth-library; added @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner.
  • artifacts/tx-os/package.json and artifacts/mockup-sandbox/package.json — removed @replit/* plugin dependencies.
  • scripts/src/seed.ts — admin/user passwords now read from SEED_ADMIN_PASSWORD / SEED_USER_PASSWORD; the script throws in NODE_ENV=production if either is unset, and the dev-only fallback is the prior literals so a fresh pnpm seed against 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 by README.md. (Note: .replit and replit.nix are 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 -69 packages (S3 SDK in, GCS + Replit plugins out).
  • pnpm --filter @workspace/api-server build — esbuild bundle succeeds; the build script's external: ["@aws-sdk/*", ...] list already covered the new packages.
  • API-server test suite — all 12 storage / object-authz tests (storage-object-authz.test.mjs cases 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-notifications socket fan-out, executive-meetings-row-color × 2) are pre-existing flakes documented in the task plan.

Operational notes for first-time deploy

  1. cp .env.example .env and fill in every value.
  2. docker compose build
  3. docker compose up -d db minio minio-init — Postgres + MinIO + create buckets.
  4. docker compose run --rm migrate — apply Drizzle schema + seed admin / ahmed accounts (one-shot).
  5. docker compose up -d api web — boot the API and the SPA.
  6. 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.