Phase 0 (CI/CD): fix root typecheck to cover api+worker+web; reconcile migration story into idempotent db:migrate (db:sync + db:triggers); add Gitea Actions quality/deploy/smoke workflow; rewrite README/AGENTS/DEPLOY docs; add requireWorkspaceAccess + recordActivityForEntity conventions. Phase 1 (critical fixes): calendar delete + drag/resize DnD; canvas card CRUD + bulk save + debounced autosave; logout route; graph edge workspaceId derivation; real analytics endpoints (drop Math.random); task board droppable columns + reorder persistence; Tiptap notes editor with sanitized HTML rendering; remove insecure passkey auth; domain/owner scoping (IDOR) on all by-ID routes + search/ export/realtime scoping; command palette routing + agent mention fetch; agent activity SSE handler; graph fly-to with tracked positions. Phase 2 (UX polish): login on design system; Sonner toasts app-wide; shared Loading/Empty/Error state components; working density/sidebarPos/reduce-motion settings; Inter typography; consolidated status-colors lib; unified detail routes; dashboard sort/realtime/responsive fixes; mobile responsive; a11y (radiogroups, sanitized snippets, badge labels). Phase 3 (features): daily notes timezone fix + delete + autosave + mood/energy create; active-domain store + topbar picker; graph domain picker + navigable entity links; tag assign/remove UI + server-side tag filter; real CSV export + import validation; custom fields on tasks. Phase 4 (advanced): migrate job worker into apps/worker (webhook delivery with HMAC, recurring spawn, ai_dispatch disabled); webhook queue helper + entity event enqueuing + test endpoint fix; recurring scheduledJobs pipeline; agents CRUD + permission editing + activity filters; real notifications feed; MCP polish (validation, error codes, domain scoping, dead sql leftover). Phase 5 (E2E + docs): rewrite Playwright suite for the Vite SPA (15 specs, new auth helpers, chromium-only in CI); add ephemeral-Postgres e2e CI job; rewrite docs/API.md for the real Hono API.
172 lines
6.1 KiB
Markdown
172 lines
6.1 KiB
Markdown
# Project E — Deploy Guide
|
|
|
|
Production runs as a docker-compose stack on the deploy host (`10.0.0.204`). The stack has four services: PostgreSQL, the Hono API, the Vite SPA served by Caddy, and the Bun worker. Deploys are normally driven by Gitea Actions, but every step below can be run by hand.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Internet → :3000 (SPA container, Caddy)
|
|
├── /api/* → api:3000 (Hono/Bun)
|
|
├── /mcp* → api:3000 (Hono/Bun)
|
|
└── /* → index.html (SPA fallback)
|
|
|
|
API (:3001, direct) → PostgreSQL (:5432)
|
|
Worker → PostgreSQL
|
|
```
|
|
|
|
Caddy (in the `spa` container) serves the built SPA and reverse-proxies `/api/*` and `/mcp*` to the `api` service. The API is also exposed directly on :3001 for debugging.
|
|
|
|
## Services
|
|
|
|
| Service | Container | Image / Build | Ports | Notes |
|
|
|---------|-----------|---------------|-------|-------|
|
|
| `db` | `project-e-db` | `postgres:16-alpine` | 5432 | Data in the `project-e-pg-data` volume |
|
|
| `api` | `project-e-api` | `Dockerfile.api` | 3001 → 3000 | Hono on Bun |
|
|
| `spa` | `project-e-spa` | `Dockerfile.spa` | 3000 → 80 | Built Vite SPA + Caddy |
|
|
| `worker` | `project-e-worker` | `Dockerfile.worker` | none | Bun worker |
|
|
|
|
All services share the `project-e-network` bridge and restart unless stopped.
|
|
|
|
## CI/CD pipeline (Gitea Actions)
|
|
|
|
The workflow lives at `.gitea/workflows/ci.yml` and runs on the self-hosted runner `projecte-runner`, which is colocated with the deploy host.
|
|
|
|
- **`quality`** — runs on every push and pull request: `bun install --frozen-lockfile` → `bun run typecheck` → web build (`cd apps/web && bun run build`) → `docker compose build`. A failed quality gate blocks the deploy job.
|
|
- **`deploy`** — runs on pushes to `main` and on `workflow_dispatch`. It checks out the code and runs `bash script/deploy.sh` with `DEPLOY_DIR` defaulting to `/home/projecte/ProjectE`.
|
|
- **`smoke`** — runs after `deploy` (also on deploy failure): API health at `http://localhost:3000/api/health`, SPA root returns HTML, and a login POST to `/api/auth/credentials` using `INITIAL_ADMIN_EMAIL`/`INITIAL_ADMIN_PASSWORD` from the host `.env`.
|
|
|
|
### What `script/deploy.sh` does
|
|
|
|
Idempotent, safe to re-run. It:
|
|
|
|
1. Syncs the CI checkout into `DEPLOY_DIR` (rsync, excluding `.git`, `node_modules`, build artifacts, and `.env`)
|
|
2. Loads secrets from the host `.env` (never overwrites it)
|
|
3. Installs dependencies with `bun install --frozen-lockfile`
|
|
4. Runs `bun run db:migrate`
|
|
5. Runs `docker compose build` and `docker compose up -d`
|
|
6. Waits up to 60s for `http://localhost:3000/api/health` to return HTTP 200, dumping recent API logs if it times out
|
|
|
|
## Secrets
|
|
|
|
Secrets live in the host `.env` at `/home/projecte/ProjectE/.env`. This file is gitignored; never commit it. To set it up:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
Fill in `POSTGRES_PASSWORD`, `DATABASE_URL`, `AUTH_SECRET` (or `NEXTAUTH_SECRET`), `INITIAL_ADMIN_EMAIL`, `INITIAL_ADMIN_PASSWORD`, and, as needed, `NODE_ENV`, `PUBLIC_URL`, `COOKIE_SECURE`, and `ALLOWED_HOSTS`. `deploy.sh` sources it, and docker-compose reads the `POSTGRES_PASSWORD` and `DATABASE_URL` values from it.
|
|
|
|
## Manual deploy
|
|
|
|
On the deploy host:
|
|
|
|
```bash
|
|
cd /home/projecte/ProjectE
|
|
|
|
# Pull latest
|
|
git pull origin main
|
|
|
|
# Install dependencies
|
|
bun install
|
|
|
|
# Apply schema + triggers (idempotent)
|
|
bun run db:migrate
|
|
|
|
# Build images
|
|
docker compose build
|
|
|
|
# Restart the stack
|
|
docker compose up -d
|
|
|
|
# Wait for API health
|
|
until curl -s http://localhost:3000/api/health | grep -q '"status"'; do sleep 2; done
|
|
|
|
# Check status
|
|
docker compose ps
|
|
```
|
|
|
|
## Database migrations
|
|
|
|
`bun run db:migrate` chains two idempotent steps:
|
|
|
|
- `db:sync` — `drizzle-kit push --force`, which syncs the schema in `packages/db/src/schema.ts` to the database
|
|
- `db:triggers` — `script/apply-triggers.ts`, which applies the search-vector triggers from `drizzle/0005_search_vector_trigger.sql` (`CREATE OR REPLACE FUNCTION` + `DROP TRIGGER IF EXISTS`)
|
|
|
|
Both are safe to run on every deploy. Schema changes go through `bun run db:generate` in development, then land in `drizzle/` before the next deploy.
|
|
|
|
## Rollback
|
|
|
|
Compose images are rebuilt from the checkout, so there are no pinned image tags to restore. To roll back a bad release:
|
|
|
|
1. Revert the checkout to the previous good commit: `git revert <sha>` (or `git checkout <sha>`) and push to `main`
|
|
2. Re-run the manual deploy steps (`docker compose build`, `docker compose up -d`)
|
|
|
|
The `project-e-pg-data` volume is untouched by deploys and rollbacks, so the database survives both. If a deploy failed, `deploy.sh` exits non-zero with the recent API logs; do not force it past a failing health check.
|
|
|
|
## Logs
|
|
|
|
```bash
|
|
# All services
|
|
docker compose logs --tail=50 -f
|
|
|
|
# Specific service
|
|
docker compose logs --tail=50 -f api
|
|
docker compose logs --tail=50 -f spa
|
|
docker compose logs --tail=50 -f worker
|
|
docker compose logs --tail=50 -f db
|
|
```
|
|
|
|
## Debugging
|
|
|
|
### API health (direct, :3001)
|
|
|
|
```bash
|
|
curl http://localhost:3001/api/health
|
|
```
|
|
|
|
Returns `{"status":"ok", ...}` with a database ping (`database.connected`, `database.ping_ms`).
|
|
|
|
### API health (through Caddy)
|
|
|
|
```bash
|
|
curl http://localhost:3000/api/health
|
|
```
|
|
|
|
### SPA health check
|
|
|
|
```bash
|
|
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/
|
|
```
|
|
|
|
### Login test
|
|
|
|
```bash
|
|
curl -s -X POST http://localhost:3000/api/auth/credentials \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"email":"<INITIAL_ADMIN_EMAIL>","password":"<INITIAL_ADMIN_PASSWORD>"}'
|
|
```
|
|
|
|
Expect HTTP 200 and a `session` cookie.
|
|
|
|
### Container health
|
|
|
|
```bash
|
|
docker inspect project-e-db --format '{{.State.Health.Status}}'
|
|
```
|
|
|
|
### Restart or rebuild one service
|
|
|
|
```bash
|
|
docker compose restart api
|
|
|
|
docker compose build spa
|
|
docker compose up -d --force-recreate spa
|
|
```
|
|
|
|
## Important notes
|
|
|
|
- The `project-e-pg-data` Docker volume contains the live database. **Do not delete it.** Back it up (volume snapshot or `pg_dump`) before major schema work.
|
|
- Port 3000 is the SPA (Caddy); port 3001 is the API directly (for debugging).
|
|
- The MCP endpoint requires a valid API key (separate from JWT auth).
|
|
- The root `worker/` directory is legacy. The active worker is `apps/worker`.
|