Files
TX/DEPLOYMENT_MIGRATION.md
T
riyadhafraa 7a2ae8434d Update project documentation and code comments to remove platform-specific references
Refactor documentation files and code comments to remove references to Replit, specific task numbers, and other platform-specific identifiers.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 77cfe984-2c65-4152-bb7a-0df28274fe66
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: fa18e5d4-a810-4bd5-8cde-2a60d64d9e3f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/c3c252e4-c83d-40ca-9fff-99a3ea60701e/77cfe984-2c65-4152-bb7a-0df28274fe66/kI0sxlu
Replit-Helium-Checkpoint-Created: true
2026-05-14 06:23:49 +00:00

183 lines
9.5 KiB
Markdown

# Deployment Migration Report
Migration of Tx OS from a hosted single-tenant runtime to a self-contained
Docker Compose stack runnable on any Linux host (or macOS workstation with
Docker Desktop).
## Summary
Tx OS previously depended on three runtime surfaces tied to its original
hosting environment:
1. A managed object-storage sidecar (`http://127.0.0.1:1106`) used by the API
server to mint signed upload/download URLs against a managed GCS bucket
via `@google-cloud/storage` + `google-auth-library`.
2. Three Vite plugins (`@*/vite-plugin-cartographer`,
`@*/vite-plugin-dev-banner`, `@*/vite-plugin-runtime-error-modal`) loaded
conditionally in the SPA and the component sandbox.
3. Hosted-platform configuration files consumed by the previous runtime.
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.
- Hosted-platform Vite plugins are removed 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` — canonical project doc; covers Docker quickstart, local dev,
env reference, and a production checklist.
- `DEPLOYMENT_MIGRATION.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` and the legacy storage 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 is 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 the
`@google-cloud/storage` File import in favour of a local `StoredObject`
interface that both drivers implement. The deprecated `canAccessObject`
shim is retained (still always denies, per the original safe default).
- `artifacts/tx-os/vite.config.ts` and
`artifacts/mockup-sandbox/vite.config.ts` — stripped all hosted-platform
plugin imports and gated dynamic imports. The tx-os config now reads its
API proxy target from `VITE_API_PROXY_TARGET` for arbitrary dev setups.
- `.gitignore` — restructured so that hosted-platform configs, agent state,
local storage, test artifacts, and any `.env*` (except `.env.example`)
are excluded from git.
### Edited
- `pnpm-workspace.yaml` — removed the three hosted-platform 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 hosted-platform 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.
- `threat_model.md` — replaced legacy hosted-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.
- `artifacts/api-server/dist/`, `lib/*/dist/`, all `*.tsbuildinfo` — build
artefacts that should not be tracked.
- Obsolete post-merge hook script consumed by the previous runtime.
## Verification
- `pnpm install --frozen-lockfile` — clean install with the new dependency
set; pnpm reports `+87 -69` packages (S3 SDK in, GCS plugins out).
- `pnpm --filter @workspace/api-server build` — esbuild bundle succeeds.
- 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.
## 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 /
user 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.
## Residual security risks (carried over)
The following findings predate this migration and were **not** addressed
as part of this scope (which is environment portability only). They remain
on the security backlog.
### Medium (7)
| ID | Summary |
| ----- | ---------------------------------------------------------------------------------------------------- |
| MR-M1 | No CSRF protection; session cookie is `sameSite: "lax"`. |
| MR-M2 | No session regeneration on login (session-fixation window). |
| MR-M3 | Helmet / security headers are not set on API responses. |
| MR-M4 | `GET /api/users/directory` returns the full employee directory to every authenticated user. |
| MR-M5 | `POST /api/apps/:id/open` accepts any app id from any user (audit-log pollution + open enumeration). |
| MR-M6 | Errors caught in `executive-meetings` routes echo raw `Error.message` to the client. |
| MR-M8 | Upload `contentType` is taken from the client and never validated server-side. |
> Note: MR-M7 ("Object Storage ACL subsystem is unimplemented") is now
> documented in-source by the new `objectAcl.ts` deprecation banner and
> the entity-lookup authz model in `lib/objectAuthz.ts`; no behaviour
> change is required for the migration to ship.
### Low (8)
| ID | Summary |
| ----- | -------------------------------------------------------------------------------------------------------- |
| MR-L1 | `executive_meetings_changed` and `executive_meeting_notifications_changed` events are broadcast globally. |
| MR-L2 | Bcrypt cost factor is 10 (industry guidance is 12). |
| MR-L3 | Account enumeration on register (distinguishable error for existing username). |
| MR-L4 | No socket connection cap or handshake rate limit. |
| MR-L5 | `GET /api/settings` is unauthenticated. |
| MR-L6 | Several `notes.ts` mutation routes use ad-hoc body parsing instead of Zod. |
| MR-L7 | `express.json()` uses the default 100 KB body limit. |
| MR-L8 | `GET /executive-meetings/me` returns the caller's full role list. |
### Unmigrated object-data risk
Object data stored in the legacy managed GCS bucket prior to this
migration is **not** automatically copied into MinIO. Operators standing
up a fresh MinIO instance start with empty buckets. If historical uploads
must be preserved, the operator is responsible for a one-time `mc mirror`
(or equivalent) from the legacy bucket into the new MinIO bucket; the API
server's signed-URL contract and DB-side `attached_object_path` columns
are already compatible because both backends key by `<bucket>/<objectName>`.
## What did NOT change
Per the migration scope, 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.