mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Merge branch 'alpha' of https://github.com/Tria-plc/edr-platform into alpha
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
**/node_modules
|
||||
**/dist
|
||||
**/.turbo
|
||||
**/.git
|
||||
**/.github
|
||||
**/.vscode
|
||||
**/.git
|
||||
**/.idea
|
||||
**/.env
|
||||
.env
|
||||
**/.env.*
|
||||
!**/.env.example
|
||||
**/coverage
|
||||
**/*.tsbuildinfo
|
||||
**/*.log
|
||||
.DS_Store
|
||||
|
||||
96
.github/workflows/deploy.yaml
vendored
96
.github/workflows/deploy.yaml
vendored
@@ -1,96 +0,0 @@
|
||||
name: Automatic Deployment
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
environment:
|
||||
name: 🌍 Setup Environment
|
||||
runs-on: [self-hosted]
|
||||
outputs:
|
||||
target: ${{ steps.dev.outputs.target || steps.staging.outputs.target }}
|
||||
steps:
|
||||
- name: Verify NPM Token
|
||||
env:
|
||||
# We map the secret here to check its existence
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
if [ -z "$NPM_TOKEN" ]; then
|
||||
echo "::error::The NPM_TOKEN secret is missing or empty. Please add it to your GitHub Secrets."
|
||||
exit 1
|
||||
fi
|
||||
echo "NPM_TOKEN is present, proceeding with build..."
|
||||
|
||||
- name: 🛠️ Set Development Environment
|
||||
id: dev
|
||||
if: ${{github.ref_name == 'dev'}}
|
||||
run: |
|
||||
echo "target=dev" >> $GITHUB_OUTPUT
|
||||
- name: 🚀 Set Staging Environment
|
||||
id: staging
|
||||
if: ${{github.ref_name == 'staging'}}
|
||||
run: |
|
||||
echo "target=staging" >> $GITHUB_OUTPUT
|
||||
|
||||
build-base-image:
|
||||
name: 🏗️ Build Base Image
|
||||
runs-on: [self-hosted, dev]
|
||||
needs: [environment]
|
||||
steps:
|
||||
- name: 🔍 Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🐳 Build Docker Image
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Create the multi-line file
|
||||
cat <<EOF > .npmrc_temp
|
||||
@tria-plc:registry=https://npm.pkg.github.com
|
||||
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
|
||||
always-auth=true
|
||||
EOF
|
||||
|
||||
# Build using the file
|
||||
docker build --secret id=npmrc,src=.npmrc_temp -t edr-${{needs.environment.outputs.target}} .
|
||||
docker build --secret id=npmrc,src=.npmrc_temp --target passenger-migration -t edr-passenger-migration-${{needs.environment.outputs.target}} .
|
||||
|
||||
# Shred/Remove the sensitive file
|
||||
rm .npmrc_temp
|
||||
|
||||
deploy-service:
|
||||
name: ${{ matrix.display_name }}
|
||||
runs-on: [self-hosted, dev]
|
||||
needs: [build-base-image, environment]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- service: freight-api
|
||||
env_file: .env.freight-api
|
||||
display_name: 🚚 Deploy Freight API Service
|
||||
- service: passenger-api
|
||||
env_file: .env.passenger-api
|
||||
display_name: 🧑🦲 Deploy Passenger API Service
|
||||
|
||||
steps:
|
||||
- name: 🔍 Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 📋 Copy ${{ matrix.service }} Environment
|
||||
run: cp ~/environment/edr/${{needs.environment.outputs.target}}/${{ matrix.env_file }} .env
|
||||
|
||||
- name: 🧪 Run Passenger API migrations
|
||||
if: ${{ matrix.service == 'passenger-api' }}
|
||||
run: |
|
||||
docker run --rm --env-file .env edr-passenger-migration-${{needs.environment.outputs.target}}
|
||||
|
||||
- name: 🚀 Start ${{ matrix.service }} Service
|
||||
run: docker compose --project-name="edr-${{needs.environment.outputs.target}}" up -d --force-recreate ${{ matrix.service }} --build
|
||||
92
.github/workflows/deploy.yml
vendored
Normal file
92
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
name: Deploy Stacks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- staging
|
||||
paths:
|
||||
- "apps/edr-freight-api/**"
|
||||
- "apps/edr-freight-web/**"
|
||||
- "apps/edr-passenger-api/**"
|
||||
- "apps/edr-passenger-web/**"
|
||||
- "packages/**"
|
||||
- "infrastructure/docker/Dockerfile.web"
|
||||
- "infrastructure/nginx/**"
|
||||
- "docker-compose.yaml"
|
||||
- "pnpm-lock.yaml"
|
||||
- "scripts/deploy/**"
|
||||
- ".github/workflows/deploy.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: deploy-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy ${{ matrix.service }}
|
||||
runs-on: self-hosted
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- project: edr-freight
|
||||
build_env_file: freight-web.build.env
|
||||
service: freight-api
|
||||
- project: edr-freight
|
||||
build_env_file: freight-web.build.env
|
||||
service: freight-portal
|
||||
- project: edr-freight
|
||||
build_env_file: freight-web.build.env
|
||||
service: freight-backoffice
|
||||
- project: edr-passenger
|
||||
build_env_file: passenger-web.build.env
|
||||
service: passenger-api
|
||||
- project: edr-passenger
|
||||
build_env_file: passenger-web.build.env
|
||||
service: passenger-portal
|
||||
- project: edr-passenger
|
||||
build_env_file: passenger-web.build.env
|
||||
service: passenger-backoffice
|
||||
env:
|
||||
PROJECT: ${{ matrix.project }}
|
||||
BRANCH: ${{ github.ref_name }}
|
||||
DEPLOY_USER: tria
|
||||
BUILD_ENV_FILE: ${{ matrix.build_env_file }}
|
||||
DOCKER_BUILDKIT: "1"
|
||||
COMPOSE_DOCKER_CLI_BUILD: "1"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Sync environment from server
|
||||
run: |
|
||||
chmod +x scripts/deploy/*.sh
|
||||
./scripts/deploy/sync-env-from-server.sh "${{ matrix.service }}"
|
||||
|
||||
- name: Set compose project name
|
||||
run: |
|
||||
set -euo pipefail
|
||||
branch_slug=$(echo "${BRANCH}" | tr "[:upper:]" "[:lower:]" | sed -E "s/[^a-z0-9]+/-/g; s/^-+//; s/-+$//")
|
||||
echo "COMPOSE_PROJECT_NAME=${PROJECT}-${branch_slug}" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Configure npm auth for Docker builds
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
run: ./scripts/deploy/create-npmrc.sh
|
||||
|
||||
- name: Build ${{ matrix.service }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker compose --project-name "${COMPOSE_PROJECT_NAME}" build "${{ matrix.service }}"
|
||||
|
||||
- name: Deploy ${{ matrix.service }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker compose --project-name "${COMPOSE_PROJECT_NAME}" up -d "${{ matrix.service }}"
|
||||
|
||||
- name: Remove npm credentials from workspace
|
||||
if: always()
|
||||
run: rm -f .npmrc .npmrc_temp
|
||||
187
DEPLOYMENT.md
Normal file
187
DEPLOYMENT.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# Deployment Runbook
|
||||
|
||||
This document explains how deployments work for the EDR platform using Docker, GitHub Actions, and self-hosted runners.
|
||||
|
||||
## Overview
|
||||
|
||||
- Monorepo contains 6 deployable services:
|
||||
- `freight-api`
|
||||
- `freight-portal`
|
||||
- `freight-backoffice`
|
||||
- `passenger-api`
|
||||
- `passenger-portal`
|
||||
- `passenger-backoffice`
|
||||
- Deployments run through one workflow: `.github/workflows/deploy.yml`
|
||||
- Each service is built/deployed independently in parallel (matrix jobs).
|
||||
- Docker Compose project names are branch-aware to avoid environment collisions on the same host.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker Engine with Compose plugin on the self-hosted runner.
|
||||
- GitHub self-hosted runner registered for this repository.
|
||||
- Repository secret configured:
|
||||
- `NPM_TOKEN` (for private `@tria-plc/*` package install during Docker build)
|
||||
- Server-side env files created for each branch/environment.
|
||||
|
||||
## Server Environment Files
|
||||
|
||||
`sync-env-from-server.sh` reads env files from:
|
||||
|
||||
`/home/<DEPLOY_USER>/environment/edr/<branch-slug>/<project>/`
|
||||
|
||||
Where:
|
||||
|
||||
- `<DEPLOY_USER>` defaults to `tria` (overridable by `DEPLOY_USER`)
|
||||
- `<branch-slug>` is derived from Git branch (lowercase, non-alphanumeric replaced with `-`)
|
||||
- `<project>` is `edr-freight` or `edr-passenger`
|
||||
|
||||
### Required files per project
|
||||
|
||||
For `edr-freight`:
|
||||
|
||||
- `freight-api.env`
|
||||
- `freight-portal.env`
|
||||
- `freight-backoffice.env`
|
||||
- optional: `freight-web.build.env`
|
||||
|
||||
For `edr-passenger`:
|
||||
|
||||
- `passenger-api.env`
|
||||
- `passenger-portal.env`
|
||||
- `passenger-backoffice.env`
|
||||
- optional: `passenger-web.build.env`
|
||||
|
||||
### Required env key
|
||||
|
||||
Each service env file must contain:
|
||||
|
||||
- `PORT=<number>`
|
||||
|
||||
The sync script validates this and fails if missing.
|
||||
|
||||
### Build env files (optional)
|
||||
|
||||
Used for build-time variables (example: Vite API URLs), with `export` syntax:
|
||||
|
||||
```bash
|
||||
export FREIGHT_VITE_API_URL=https://freight-api.example.com/api
|
||||
export PASSENGER_VITE_API_URL=https://passenger-api.example.com
|
||||
```
|
||||
|
||||
These are injected into `GITHUB_ENV` during workflow execution.
|
||||
|
||||
## Docker Compose Port Mapping
|
||||
|
||||
`docker-compose.yaml` uses per-service env variables for host/container port mappings:
|
||||
|
||||
- `FREIGHT_API_PORT`
|
||||
- `PASSENGER_API_PORT`
|
||||
- `FREIGHT_PORTAL_PORT`
|
||||
- `FREIGHT_BACKOFFICE_PORT`
|
||||
- `PASSENGER_PORTAL_PORT`
|
||||
- `PASSENGER_BACKOFFICE_PORT`
|
||||
|
||||
`scripts/deploy/sync-env-from-server.sh` extracts `PORT` from each synced `.env` and exports the corresponding `*_PORT` variable to `GITHUB_ENV`.
|
||||
|
||||
## GitHub Actions Deployment Flow
|
||||
|
||||
Workflow file: `.github/workflows/deploy.yml`
|
||||
|
||||
### 1) `prepare` job
|
||||
|
||||
- Checks out repository once.
|
||||
- Creates workspace artifact (`workspace.tgz`) and uploads it.
|
||||
|
||||
### 2) `deploy` matrix job (parallel)
|
||||
|
||||
For each service:
|
||||
|
||||
- Downloads and extracts workspace artifact.
|
||||
- Syncs that service env file from server path.
|
||||
- Computes branch slug and sets:
|
||||
- `COMPOSE_PROJECT_NAME=<project>-<branch-slug>`
|
||||
- Creates `.npmrc`/`.npmrc_temp` from `NPM_TOKEN`.
|
||||
- Runs:
|
||||
- `docker compose --project-name "$COMPOSE_PROJECT_NAME" build <service>`
|
||||
- `docker compose --project-name "$COMPOSE_PROJECT_NAME" up -d <service>`
|
||||
- Cleans `.npmrc`/`.npmrc_temp`.
|
||||
|
||||
## Branch/Environment Isolation
|
||||
|
||||
Compose project name is generated as:
|
||||
|
||||
`<project>-<branch-slug>`
|
||||
|
||||
Examples:
|
||||
|
||||
- `edr-freight-main`
|
||||
- `edr-freight-staging`
|
||||
- `edr-passenger-dev`
|
||||
|
||||
This prevents container/network/volume name collisions between branches.
|
||||
|
||||
## Local Manual Deployment (Optional)
|
||||
|
||||
From repo root:
|
||||
|
||||
```bash
|
||||
DOCKER_BUILDKIT=1 docker compose build <service>
|
||||
docker compose up -d <service>
|
||||
```
|
||||
|
||||
If private packages are required locally, create `.npmrc`:
|
||||
|
||||
```bash
|
||||
cat <<EOF > .npmrc
|
||||
@tria-plc:registry=https://npm.pkg.github.com
|
||||
//npm.pkg.github.com/:_authToken=<YOUR_TOKEN>
|
||||
always-auth=true
|
||||
EOF
|
||||
```
|
||||
|
||||
## Passenger API Startup Behavior
|
||||
|
||||
Passenger container entrypoint runs on startup:
|
||||
|
||||
1. `npm run prisma:generate`
|
||||
2. `npm run prisma:migrate` (deploy mode)
|
||||
3. `npm run prisma:seed`
|
||||
4. starts API process
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Missing env file
|
||||
|
||||
Error:
|
||||
|
||||
- `Missing env file: ...`
|
||||
|
||||
Fix:
|
||||
|
||||
- Create the required file in the server env directory for that project/branch slug.
|
||||
|
||||
### Missing PORT in env file
|
||||
|
||||
Error:
|
||||
|
||||
- `Missing required PORT in env file: ...`
|
||||
|
||||
Fix:
|
||||
|
||||
- Add `PORT=<number>` to that service env file.
|
||||
|
||||
### Private package install fails
|
||||
|
||||
Check:
|
||||
|
||||
- `NPM_TOKEN` exists in repo secrets.
|
||||
- Workflow created `.npmrc` successfully.
|
||||
|
||||
### Prisma seed/migrate failures (passenger)
|
||||
|
||||
Check:
|
||||
|
||||
- `DATABASE_URL` in `passenger-api.env`
|
||||
- DB reachability from runner host/container network
|
||||
- migration history consistency
|
||||
|
||||
84
Dockerfile
84
Dockerfile
@@ -1,84 +0,0 @@
|
||||
FROM node:24.15.0 AS base
|
||||
RUN corepack enable && corepack prepare pnpm@latest-11 --activate
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS deps
|
||||
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||
|
||||
COPY apps/edr-freight-api/package.json ./apps/edr-freight-api/
|
||||
COPY apps/edr-passenger-api/package.json ./apps/edr-passenger-api/
|
||||
|
||||
COPY apps/edr-freight-web/backoffice/package.json ./apps/edr-freight-web/backoffice/
|
||||
COPY apps/edr-freight-web/portal/package.json ./apps/edr-freight-web/portal/
|
||||
|
||||
COPY apps/edr-passenger-web/backoffice/package.json ./apps/edr-passenger-web/backoffice/
|
||||
COPY apps/edr-passenger-web/portal/package.json ./apps/edr-passenger-web/portal/
|
||||
|
||||
|
||||
COPY packages/api-common/package.json packages/api-common/
|
||||
|
||||
COPY packages/config/eslint-config/package.json packages/config/eslint-config/
|
||||
COPY packages/config/prettier-config/package.json packages/config/prettier-config/
|
||||
COPY packages/config/tsconfig/package.json packages/config/tsconfig/
|
||||
|
||||
COPY packages/types/package.json packages/types/
|
||||
COPY packages/ui-common/package.json packages/ui-common/
|
||||
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store\
|
||||
--mount=type=secret,id=npmrc,target=./.npmrc \
|
||||
pnpm install --frozen-lockfile
|
||||
FROM deps AS build
|
||||
COPY . .
|
||||
|
||||
RUN pnpm run build --filter=\!"@edr/passenger-portal"
|
||||
|
||||
FROM base AS freight-api
|
||||
# RUN corepack enable && corepack prepare pnpm@9.12.0 --activate
|
||||
WORKDIR /app/apps/edr-freight-api
|
||||
ENV NODE_ENV=production
|
||||
|
||||
COPY --from=deps /app/node_modules ./../../node_modules
|
||||
COPY --from=deps /app/apps/edr-freight-api/node_modules ./node_modules
|
||||
COPY --from=build /app/apps/edr-freight-api/dist ./dist
|
||||
COPY --from=build /app/apps/edr-freight-api/package.json ./package.json
|
||||
COPY --from=build /app/packages ./../../packages
|
||||
|
||||
EXPOSE 3001
|
||||
CMD ["node", "dist/main.js"]
|
||||
|
||||
|
||||
FROM base AS passenger-api
|
||||
RUN apt-get update -y && apt-get install -y openssl
|
||||
# RUN corepack enable && corepack prepare pnpm@9.12.0 --activate
|
||||
WORKDIR /app/apps/edr-passenger-api
|
||||
ENV NODE_ENV=production
|
||||
|
||||
# Use build-stage node_modules (not deps): `pnpm run build` runs `prisma generate`, which
|
||||
# writes the real @prisma/client (enums, types). deps never runs generate, so @IsEnum(ServiceClass)
|
||||
# and similar would see undefined at runtime if we copied deps only.
|
||||
COPY --from=build /app/node_modules ./../../node_modules
|
||||
COPY --from=build /app/apps/edr-passenger-api/node_modules ./node_modules
|
||||
COPY --from=build /app/apps/edr-passenger-api/dist ./dist
|
||||
COPY --from=build /app/apps/edr-passenger-api/package.json ./package.json
|
||||
COPY --from=build /app/packages ./../../packages
|
||||
|
||||
EXPOSE 3001
|
||||
CMD ["node", "dist/main.js"]
|
||||
|
||||
FROM build as passenger-migration
|
||||
WORKDIR /app/apps/edr-passenger-api
|
||||
CMD pnpm run prisma:migrate && pnpm run prisma:seed
|
||||
|
||||
|
||||
|
||||
FROM nginx:1.27-alpine AS freight-web-portal
|
||||
COPY --from=build /app/apps/edr-freight-web/portal/dist /usr/share/nginx/html
|
||||
EXPOSE 5173
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
|
||||
FROM nginx:1.27-alpine AS freight-web-backoffice
|
||||
COPY --from=build /app/apps/edr-freight-web/backoffice/dist /usr/share/nginx/html
|
||||
EXPOSE 5173
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
120
README.md
120
README.md
@@ -170,7 +170,7 @@ pnpm --filter @edr/passenger-api run prisma:generate
|
||||
|
||||
#### Run Migrations
|
||||
```bash
|
||||
pnpm --filter @edr/passenger-api run prisma:migrate
|
||||
pnpm --filter @edr/passenger-api run prisma:migrate:dev
|
||||
```
|
||||
|
||||
#### Seed Database
|
||||
@@ -579,59 +579,103 @@ pnpm --filter @edr/passenger-api run type-check # TypeScript check
|
||||
|
||||
# Database
|
||||
pnpm --filter @edr/passenger-api run prisma:generate # Generate Prisma client
|
||||
pnpm --filter @edr/passenger-api run prisma:migrate # Run migrations
|
||||
pnpm --filter @edr/passenger-api run prisma:migrate:dev # Run migrations (local dev)
|
||||
pnpm --filter @edr/passenger-api run prisma:seed # Seed database
|
||||
```
|
||||
|
||||
## 🐳 Docker Deployment
|
||||
|
||||
### Build Image
|
||||
All six apps build from Dockerfiles: each API has its own (`apps/edr-freight-api/Dockerfile`, `apps/edr-passenger-api/Dockerfile`); Vite frontends share `infrastructure/docker/Dockerfile.web` and are served with **nginx**. APIs run on **Node 22**.
|
||||
|
||||
**Prerequisites**
|
||||
|
||||
- Docker with BuildKit enabled
|
||||
- A local [`.npmrc`](.gitignore) with GitHub Packages auth for `@tria-plc/*` (required for **freight** API and web images)
|
||||
- External Postgres for each API (compose does **not** include databases)
|
||||
- Copy `apps/edr-freight-api/.env.example` → `.env` and `apps/edr-passenger-api/.env.example` → `.env` with real connection strings
|
||||
|
||||
### Build and run (all apps)
|
||||
|
||||
```bash
|
||||
# From monorepo root
|
||||
docker build -f apps/edr-passenger-api/Dockerfile -t edr-passenger-api .
|
||||
DOCKER_BUILDKIT=1 pnpm docker:build
|
||||
pnpm docker:up
|
||||
```
|
||||
|
||||
### Run Container
|
||||
Or without pnpm scripts:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name edr-api \
|
||||
-p 4000:4000 \
|
||||
--env-file apps/edr-passenger-api/.env \
|
||||
edr-passenger-api
|
||||
DOCKER_BUILDKIT=1 docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
### Docker Compose (Recommended)
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_USER: edr
|
||||
POSTGRES_PASSWORD: edr_secret
|
||||
POSTGRES_DB: edr_passenger
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
| Service | URL (default) |
|
||||
|---------|----------------|
|
||||
| Freight API | http://localhost:3001 |
|
||||
| Passenger API | http://localhost:4000 |
|
||||
| Freight portal | http://localhost:5173 |
|
||||
| Freight backoffice | http://localhost:5183 |
|
||||
| Passenger portal | http://localhost:5174 |
|
||||
| Passenger backoffice | http://localhost:5184 |
|
||||
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/edr-passenger-api/Dockerfile
|
||||
ports:
|
||||
- "4000:4000"
|
||||
environment:
|
||||
DATABASE_URL: postgresql://edr:edr_secret@postgres:5432/edr_passenger
|
||||
JWT_SECRET: your-secret-key
|
||||
PORT: 4000
|
||||
depends_on:
|
||||
- postgres
|
||||
### Build a single service
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
```bash
|
||||
docker compose build freight-api
|
||||
docker compose build passenger-portal
|
||||
```
|
||||
|
||||
Freight images mount `.npmrc` as a BuildKit secret during `pnpm install`. Passenger web images do not require private packages.
|
||||
|
||||
### `VITE_API_URL` (frontends)
|
||||
|
||||
API URLs are **baked in at image build time** (`import.meta.env.VITE_API_URL`). Defaults in [`docker-compose.yaml`](docker-compose.yaml) use `http://localhost:3001/api` (freight) and `http://localhost:4000` (passenger) for local smoke tests. Override build args for production, e.g.:
|
||||
|
||||
```bash
|
||||
docker compose build freight-portal \
|
||||
--build-arg VITE_API_URL=https://freight-api.example.com/api
|
||||
```
|
||||
|
||||
### Migrations
|
||||
|
||||
- **Freight API:** TypeORM migrations are not run on container startup — apply them separately before deploy.
|
||||
- **Passenger API:** On each container start, the entrypoint runs `npm run prisma:migrate` and `npm run prisma:seed` (same `package.json` scripts as `pnpm run`) before starting the server. Ensure `DATABASE_URL` in `.env` points at a reachable Postgres instance.
|
||||
|
||||
For local development, use `pnpm --filter @edr/passenger-api run prisma:migrate:dev` instead of `prisma:migrate`.
|
||||
|
||||
### GitHub Actions (self-hosted runner)
|
||||
|
||||
Two workflows deploy independently on push to `main`, `develop`, or `staging`:
|
||||
|
||||
| Workflow | Services | Server env root |
|
||||
|----------|----------|-----------------|
|
||||
| [`.github/workflows/deploy-freight.yml`](.github/workflows/deploy-freight.yml) | freight-api, freight-portal, freight-backoffice | `/home/user/environmen/edr-freight/<branch>/` |
|
||||
| [`.github/workflows/deploy-passenger.yml`](.github/workflows/deploy-passenger.yml) | passenger-api, passenger-portal, passenger-backoffice | `/home/user/environmen/edr-passenger/<branch>/` |
|
||||
|
||||
**On the runner**, place env files before the first deploy (example for branch `main`):
|
||||
|
||||
```text
|
||||
/home/user/environmen/edr-freight/main/
|
||||
freight-api.env
|
||||
freight-portal.env # optional runtime env for Vite/nginx
|
||||
freight-backoffice.env
|
||||
freight-web.build.env # exports FREIGHT_VITE_API_URL=...
|
||||
|
||||
/home/user/environmen/edr-passenger/main/
|
||||
passenger-api.env
|
||||
passenger-portal.env
|
||||
passenger-backoffice.env
|
||||
passenger-web.build.env # exports PASSENGER_VITE_API_URL=...
|
||||
```
|
||||
|
||||
Example `freight-web.build.env`:
|
||||
|
||||
```bash
|
||||
export FREIGHT_VITE_API_URL=https://freight-api.example.com/api
|
||||
```
|
||||
|
||||
The workflow copies `*.env` into each app directory, creates `.npmrc` from the `NPM_TOKEN` repository secret, then runs `docker compose build` and `docker compose up -d` for that stack.
|
||||
|
||||
## 🔒 Security Best Practices
|
||||
|
||||
1. **Environment Variables** - Never commit `.env` files. Use secrets management in production.
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
# App
|
||||
NODE_ENV=development
|
||||
# Copy to .env for local/docker compose (not committed).
|
||||
PORT=3001
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5433
|
||||
DB_NAME=edr_freight
|
||||
DB_USER=postgres
|
||||
DB_PASSWORD=
|
||||
|
||||
# JWT (provided by external auth package — placeholder only)
|
||||
JWT_SECRET=
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
DB_NAME=edr_freight
|
||||
|
||||
37
apps/edr-freight-api/Dockerfile
Normal file
37
apps/edr-freight-api/Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Build from monorepo root: docker build -f apps/edr-freight-api/Dockerfile .
|
||||
|
||||
FROM node:24.15.0-alpine AS base
|
||||
RUN apk add --no-cache libc6-compat
|
||||
RUN corepack enable
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS pruner
|
||||
COPY . .
|
||||
RUN pnpm dlx turbo prune "@edr/freight-api" --docker
|
||||
|
||||
FROM base AS installer
|
||||
COPY --from=pruner /app/out/json/ .
|
||||
COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
RUN --mount=type=secret,id=npmrc,target=./.npmrc,required=false \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
FROM base AS builder
|
||||
COPY --from=installer /app/ .
|
||||
COPY --from=pruner /app/out/full/ .
|
||||
RUN pnpm turbo build --filter="@edr/freight-api..."
|
||||
|
||||
FROM base AS deployer
|
||||
COPY --from=builder /app/ .
|
||||
RUN pnpm deploy --filter="@edr/freight-api" --prod --legacy /deploy
|
||||
|
||||
FROM node:24.15.0-alpine AS runner
|
||||
RUN apk add --no-cache libc6-compat
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
RUN addgroup --system --gid 1001 nodejs \
|
||||
&& adduser --system --uid 1001 --ingroup nodejs nestjs
|
||||
COPY --from=deployer --chown=nestjs:nodejs /deploy .
|
||||
USER nestjs
|
||||
EXPOSE 3001
|
||||
CMD ["node", "dist/main.js"]
|
||||
51
apps/edr-passenger-api/Dockerfile
Normal file
51
apps/edr-passenger-api/Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
# Build from monorepo root: docker build -f apps/edr-passenger-api/Dockerfile .
|
||||
# On start: runs prisma migrate deploy + seed, then the API.
|
||||
|
||||
FROM node:24.15.0-alpine AS base
|
||||
RUN apk add --no-cache libc6-compat
|
||||
RUN corepack enable
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS pruner
|
||||
COPY . .
|
||||
RUN pnpm dlx turbo prune "@edr/passenger-api" --docker
|
||||
|
||||
FROM base AS installer
|
||||
COPY --from=pruner /app/out/json/ .
|
||||
COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
RUN --mount=type=secret,id=npmrc,target=./.npmrc,required=false \
|
||||
--mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
FROM base AS builder
|
||||
COPY --from=installer /app/ .
|
||||
COPY --from=pruner /app/out/full/ .
|
||||
RUN pnpm --filter "@edr/passenger-api" exec prisma generate
|
||||
RUN pnpm turbo build --filter="@edr/passenger-api..."
|
||||
|
||||
FROM base AS deployer
|
||||
COPY --from=builder /app/ .
|
||||
RUN pnpm deploy --filter="@edr/passenger-api" --legacy /deploy
|
||||
RUN if [ -d node_modules/.prisma ]; then \
|
||||
mkdir -p /deploy/node_modules && \
|
||||
cp -r node_modules/.prisma /deploy/node_modules/.prisma; \
|
||||
fi
|
||||
|
||||
FROM node:24.15.0-alpine AS runner
|
||||
RUN apk add --no-cache libc6-compat
|
||||
RUN corepack enable && corepack prepare pnpm@11.1.1 --activate
|
||||
ENV NODE_ENV=production
|
||||
WORKDIR /app
|
||||
RUN addgroup --system --gid 1001 nodejs \
|
||||
&& adduser --system --uid 1001 --ingroup nodejs nestjs
|
||||
COPY --from=deployer /deploy .
|
||||
COPY apps/edr-passenger-api/docker-entrypoint.sh /docker-entrypoint.sh
|
||||
RUN chmod +x /docker-entrypoint.sh \
|
||||
&& chown -R nestjs:nodejs /app
|
||||
USER nestjs
|
||||
ENV CI=true
|
||||
ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0
|
||||
EXPOSE 4000
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
CMD ["node", "dist/main.js"]
|
||||
11
apps/edr-passenger-api/docker-entrypoint.sh
Normal file
11
apps/edr-passenger-api/docker-entrypoint.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
cd /app
|
||||
|
||||
# npm run executes the same package.json scripts as pnpm run (pnpm reinstalls in deploy layout)
|
||||
npm run prisma:generate
|
||||
npm run prisma:migrate
|
||||
npm run prisma:seed
|
||||
|
||||
exec "$@"
|
||||
@@ -44,7 +44,8 @@
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"swagger-ui-express": "^5.0.0",
|
||||
"tsconfig-paths": "^4.2.0"
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"@prisma/client": "^6.19.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@edr/eslint-config": "workspace:*",
|
||||
@@ -52,7 +53,6 @@
|
||||
"@nestjs/cli": "^11.0.21",
|
||||
"@nestjs/schematics": "^11.1.0",
|
||||
"@nestjs/testing": "^11.1.19",
|
||||
"@prisma/client": "^6.19.3",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/node": "^20.10.6",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PrismaClient, SeatKind } from '@prisma/client';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
@@ -136,11 +136,11 @@ async function seedCoachesAndSeats(seatClasses: any[]) {
|
||||
col,
|
||||
label: `${row}${col}`,
|
||||
seatNumber: `${config.label}${row}${col}`,
|
||||
kind: (row === 1 && col === 'A' ? 'ACCESSIBLE' : 'STANDARD') as SeatKind,
|
||||
kind: row === 1 && col === 'A' ? 'ACCESSIBLE' : 'STANDARD',
|
||||
});
|
||||
}
|
||||
}
|
||||
await prisma.seat.createMany({ data: seats });
|
||||
await prisma.seat.createMany({ data: seats as any });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ async function seedSchedules(trains: any[], stations: any[], routes: any[]) {
|
||||
const existingScheduleIds = (await prisma.trainSchedule.findMany({
|
||||
where: { trainId: { in: [train301.id, train302.id, train303.id] } },
|
||||
select: { id: true },
|
||||
})).map((s) => s.id);
|
||||
})).map((s: { id: string }) => s.id);
|
||||
|
||||
if (existingScheduleIds.length > 0) {
|
||||
// Delete in correct order to avoid foreign key constraints
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { IStation } from "../types";
|
||||
// import type { IStation } from "../types";
|
||||
import { api } from "../utils/api";
|
||||
import type { Passenger } from "@edr/types";
|
||||
|
||||
export const stationsService = {
|
||||
list: async (): Promise<IStation[]> => {
|
||||
list: async (): Promise<Passenger.IStation[]> => {
|
||||
const { data } = await api.get("/stations");
|
||||
return data.data;
|
||||
},
|
||||
get: async (id: string): Promise<IStation> => {
|
||||
get: async (id: string): Promise<Passenger.IStation> => {
|
||||
const { data } = await api.get(`/stations/${id}`);
|
||||
return data.data;
|
||||
},
|
||||
|
||||
63
checkpoint.md
Normal file
63
checkpoint.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Checkpoint
|
||||
|
||||
## Major Tasks Completed
|
||||
|
||||
1. Docker deployment scaffolded for all 6 apps in the monorepo.
|
||||
2. Split API images into app-specific Dockerfiles:
|
||||
- `apps/edr-freight-api/Dockerfile`
|
||||
- `apps/edr-passenger-api/Dockerfile`
|
||||
3. Kept shared Vite/nginx image:
|
||||
- `infrastructure/docker/Dockerfile.web`
|
||||
- `infrastructure/nginx/spa.conf`
|
||||
4. Updated `docker-compose.yaml` to run all 6 services (apps only, no Postgres service in compose).
|
||||
5. Added/updated deployment scripts:
|
||||
- `scripts/deploy/create-npmrc.sh`
|
||||
- `scripts/deploy/sync-env-from-server.sh`
|
||||
6. Added self-hosted GitHub Actions deployment workflow:
|
||||
- Consolidated into one file: `.github/workflows/deploy.yml`
|
||||
7. Deployment workflow now:
|
||||
- uses a single checkout (`prepare` job),
|
||||
- deploys services via parallel matrix,
|
||||
- sets compose project names per branch/environment,
|
||||
- passes explicit `docker compose --project-name`.
|
||||
8. Environment sync script now:
|
||||
- supports branch slug paths,
|
||||
- validates each service env file exists,
|
||||
- requires `PORT` in each env file,
|
||||
- exports per-service port vars to `GITHUB_ENV`.
|
||||
9. `docker-compose.yaml` now reads per-service ports via variables exported from env sync.
|
||||
10. Passenger startup flow fixed to run:
|
||||
- `prisma:generate`,
|
||||
- `prisma:migrate`,
|
||||
- `prisma:seed`,
|
||||
before API startup.
|
||||
11. Passenger seed TypeScript issues fixed in `apps/edr-passenger-api/prisma/seed.ts` so it compiles under strict checks.
|
||||
12. Added deployment runbook:
|
||||
- `DEPLOYMENT.md`
|
||||
|
||||
## Key Files to Review
|
||||
|
||||
- `.github/workflows/deploy.yml`
|
||||
- `docker-compose.yaml`
|
||||
- `scripts/deploy/sync-env-from-server.sh`
|
||||
- `scripts/deploy/create-npmrc.sh`
|
||||
- `apps/edr-passenger-api/docker-entrypoint.sh`
|
||||
- `apps/edr-passenger-api/prisma/seed.ts`
|
||||
- `DEPLOYMENT.md`
|
||||
|
||||
## Next Actions
|
||||
|
||||
1. Run full CI on all target branches (`main`, `dev`, `staging`) and verify matrix job behavior.
|
||||
2. Validate server env directory layout matches script expectations:
|
||||
- `/home/<deploy_user>/environment/edr/<branch-slug>/<project>/...`
|
||||
3. Confirm each service env file includes valid `PORT` and service-specific runtime vars.
|
||||
4. Verify branch-specific compose project names produce isolated containers/networks/volumes on runner.
|
||||
5. Smoke test all 6 deployed services behind real environment URLs.
|
||||
|
||||
## Open Risks / Notes
|
||||
|
||||
1. Passenger seed runs on every container start; confirm this is desired for production-like environments.
|
||||
2. Prisma warns about `package.json#prisma` deprecation (Prisma 7 migration pending).
|
||||
3. Matrix parallelism increases runner load; ensure self-hosted runner capacity is sufficient.
|
||||
4. Port collisions are prevented by env-driven mapping, but bad env values can still cause runtime conflicts.
|
||||
|
||||
@@ -1,21 +1,80 @@
|
||||
# EDR Platform — application containers only (no Postgres).
|
||||
# Requires a local .npmrc with GitHub Packages auth for @tria-plc (freight API + freight web).
|
||||
# Copy apps/*/env.example to .env and set real values before `docker compose up`.
|
||||
#
|
||||
# Build: DOCKER_BUILDKIT=1 docker compose build
|
||||
# Run: docker compose up -d
|
||||
|
||||
services:
|
||||
freight-api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
target: freight-api
|
||||
env_file:
|
||||
- .env
|
||||
dockerfile: apps/edr-freight-api/Dockerfile
|
||||
secrets:
|
||||
- npmrc
|
||||
ports:
|
||||
- ${PORT}:${PORT}
|
||||
restart: unless-stopped
|
||||
- "${FREIGHT_API_PORT:-3001}:${FREIGHT_API_PORT:-3001}"
|
||||
env_file:
|
||||
- apps/edr-freight-api/.env
|
||||
|
||||
passenger-api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
target: passenger-api
|
||||
env_file:
|
||||
- .env
|
||||
dockerfile: apps/edr-passenger-api/Dockerfile
|
||||
ports:
|
||||
- ${PORT}:${PORT}
|
||||
restart: unless-stopped
|
||||
- "${PASSENGER_API_PORT:-4000}:${PASSENGER_API_PORT:-4000}"
|
||||
env_file:
|
||||
- apps/edr-passenger-api/.env
|
||||
|
||||
freight-portal:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: infrastructure/docker/Dockerfile.web
|
||||
args:
|
||||
TURBO_FILTER: "@edr/freight-portal"
|
||||
APP_PATH: apps/edr-freight-web/portal
|
||||
# Browser-reachable URL; override for production deployments
|
||||
VITE_API_URL: ${FREIGHT_VITE_API_URL:-http://localhost:3001/api}
|
||||
secrets:
|
||||
- npmrc
|
||||
ports:
|
||||
- "${FREIGHT_PORTAL_PORT:-5173}:80"
|
||||
|
||||
freight-backoffice:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: infrastructure/docker/Dockerfile.web
|
||||
args:
|
||||
TURBO_FILTER: "@edr/freight-backoffice"
|
||||
APP_PATH: apps/edr-freight-web/backoffice
|
||||
VITE_API_URL: ${FREIGHT_VITE_API_URL:-http://localhost:3001/api}
|
||||
secrets:
|
||||
- npmrc
|
||||
ports:
|
||||
- "${FREIGHT_BACKOFFICE_PORT:-5183}:80"
|
||||
|
||||
passenger-portal:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: infrastructure/docker/Dockerfile.web
|
||||
args:
|
||||
TURBO_FILTER: "@edr/passenger-portal"
|
||||
APP_PATH: apps/edr-passenger-web/portal
|
||||
VITE_API_URL: ${PASSENGER_VITE_API_URL:-http://localhost:4000}
|
||||
ports:
|
||||
- "${PASSENGER_PORTAL_PORT:-5174}:80"
|
||||
|
||||
passenger-backoffice:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: infrastructure/docker/Dockerfile.web
|
||||
args:
|
||||
TURBO_FILTER: "@edr/passenger-backoffice"
|
||||
APP_PATH: apps/edr-passenger-web/backoffice
|
||||
VITE_API_URL: ${PASSENGER_VITE_API_URL:-http://localhost:4000}
|
||||
ports:
|
||||
- "${PASSENGER_BACKOFFICE_PORT:-5184}:80"
|
||||
|
||||
secrets:
|
||||
npmrc:
|
||||
file: .npmrc
|
||||
|
||||
38
infrastructure/docker/Dockerfile.web
Normal file
38
infrastructure/docker/Dockerfile.web
Normal file
@@ -0,0 +1,38 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG TURBO_FILTER=@edr/freight-portal
|
||||
ARG APP_PATH=apps/edr-freight-web/portal
|
||||
ARG VITE_API_URL=http://localhost:3001/api
|
||||
|
||||
FROM node:24.15.0-alpine AS base
|
||||
RUN apk add --no-cache libc6-compat
|
||||
RUN corepack enable
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS pruner
|
||||
ARG TURBO_FILTER
|
||||
COPY . .
|
||||
RUN pnpm dlx turbo prune "${TURBO_FILTER}" --docker
|
||||
|
||||
FROM base AS installer
|
||||
COPY --from=pruner /app/out/json/ .
|
||||
COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
RUN --mount=type=secret,id=npmrc,target=./.npmrc,required=false \
|
||||
--mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
FROM base AS builder
|
||||
ARG TURBO_FILTER
|
||||
ARG APP_PATH
|
||||
ARG VITE_API_URL
|
||||
ENV VITE_API_URL=${VITE_API_URL}
|
||||
COPY --from=installer /app/ .
|
||||
COPY --from=pruner /app/out/full/ .
|
||||
RUN pnpm turbo build --filter="${TURBO_FILTER}..."
|
||||
|
||||
FROM nginx:alpine AS runner
|
||||
ARG APP_PATH
|
||||
COPY infrastructure/nginx/spa.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=builder /app/${APP_PATH}/dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
@@ -1,126 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
networks:
|
||||
edr-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
postgres-freight-data:
|
||||
postgres-passenger-data:
|
||||
redis-data:
|
||||
|
||||
services:
|
||||
postgres-freight:
|
||||
image: postgres:17-alpine
|
||||
container_name: edr-postgres-freight
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: edr_freight
|
||||
ports:
|
||||
- "5433:5432"
|
||||
volumes:
|
||||
- postgres-freight-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
postgres-passenger:
|
||||
image: postgres:17-alpine
|
||||
container_name: edr-postgres-passenger
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: edr_passenger
|
||||
ports:
|
||||
- "5434:5432"
|
||||
volumes:
|
||||
- postgres-passenger-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: edr-redis
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
edr-freight-api:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: apps/edr-freight-api/Dockerfile
|
||||
container_name: edr-freight-api
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- postgres-freight
|
||||
- redis
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
PORT: 3001
|
||||
DB_HOST: postgres-freight
|
||||
DB_PORT: 5432
|
||||
DB_NAME: edr_freight
|
||||
DB_USER: postgres
|
||||
DB_PASSWORD: postgres
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
ports:
|
||||
- "3001:3001"
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
edr-freight-web:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: apps/edr-freight-web/Dockerfile
|
||||
container_name: edr-freight-web
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- edr-freight-api
|
||||
ports:
|
||||
- "5173:5173"
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
edr-passenger-api:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: apps/edr-passenger-api/Dockerfile
|
||||
container_name: edr-passenger-api
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- postgres-passenger
|
||||
- redis
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
PORT: 3002
|
||||
DB_HOST: postgres-passenger
|
||||
DB_PORT: 5432
|
||||
DB_NAME: edr_passenger
|
||||
DB_USER: postgres
|
||||
DB_PASSWORD: postgres
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: 6379
|
||||
ports:
|
||||
- "3002:3002"
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
edr-passenger-web:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: apps/edr-passenger-web/Dockerfile
|
||||
container_name: edr-passenger-web
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- edr-passenger-api
|
||||
ports:
|
||||
- "5174:5174"
|
||||
networks:
|
||||
- edr-network
|
||||
@@ -1,134 +0,0 @@
|
||||
version: "3.9"
|
||||
|
||||
networks:
|
||||
edr-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
postgres-freight-data:
|
||||
postgres-passenger-data:
|
||||
redis-data:
|
||||
|
||||
services:
|
||||
postgres-freight:
|
||||
image: postgres:17-alpine
|
||||
container_name: edr-postgres-freight
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_DB: ${DB_NAME_FREIGHT}
|
||||
volumes:
|
||||
- postgres-freight-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
postgres-passenger:
|
||||
image: postgres:17-alpine
|
||||
container_name: edr-postgres-passenger
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||
POSTGRES_DB: ${DB_NAME_PASSENGER}
|
||||
volumes:
|
||||
- postgres-passenger-data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: edr-redis
|
||||
restart: always
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
edr-freight-api:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: apps/edr-freight-api/Dockerfile
|
||||
container_name: edr-freight-api
|
||||
restart: always
|
||||
depends_on:
|
||||
- postgres-freight
|
||||
- redis
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3001
|
||||
DB_HOST: postgres-freight
|
||||
DB_PORT: 5432
|
||||
DB_NAME: ${DB_NAME_FREIGHT}
|
||||
DB_USER: ${DB_USER}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
REDIS_HOST: redis
|
||||
expose:
|
||||
- "3001"
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
edr-freight-web:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: apps/edr-freight-web/Dockerfile
|
||||
container_name: edr-freight-web
|
||||
restart: always
|
||||
depends_on:
|
||||
- edr-freight-api
|
||||
expose:
|
||||
- "5173"
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
edr-passenger-api:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: apps/edr-passenger-api/Dockerfile
|
||||
container_name: edr-passenger-api
|
||||
restart: always
|
||||
depends_on:
|
||||
- postgres-passenger
|
||||
- redis
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
PORT: 3002
|
||||
DB_HOST: postgres-passenger
|
||||
DB_PORT: 5432
|
||||
DB_NAME: ${DB_NAME_PASSENGER}
|
||||
DB_USER: ${DB_USER}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
REDIS_HOST: redis
|
||||
expose:
|
||||
- "3002"
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
edr-passenger-web:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: apps/edr-passenger-web/Dockerfile
|
||||
container_name: edr-passenger-web
|
||||
restart: always
|
||||
depends_on:
|
||||
- edr-passenger-api
|
||||
expose:
|
||||
- "5174"
|
||||
networks:
|
||||
- edr-network
|
||||
|
||||
nginx:
|
||||
image: nginx:1.27-alpine
|
||||
container_name: edr-nginx
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- edr-freight-web
|
||||
- edr-freight-api
|
||||
- edr-passenger-web
|
||||
- edr-passenger-api
|
||||
volumes:
|
||||
- ../../infrastructure/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
networks:
|
||||
- edr-network
|
||||
@@ -1,63 +0,0 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
server_tokens off;
|
||||
|
||||
upstream edr_freight_api {
|
||||
server edr-freight-api:3001;
|
||||
}
|
||||
upstream edr_freight_web {
|
||||
server edr-freight-web:5173;
|
||||
}
|
||||
upstream edr_passenger_api {
|
||||
server edr-passenger-api:4000;
|
||||
}
|
||||
upstream edr_passenger_web {
|
||||
server edr-passenger-web:5174;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name freight.edr.local;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://edr_freight_api/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://edr_freight_web/;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name passenger.edr.local;
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://edr_passenger_api/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://edr_passenger_web/;
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
infrastructure/nginx/spa.conf
Normal file
13
infrastructure/nginx/spa.conf
Normal file
@@ -0,0 +1,13 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
"lint": "turbo run lint",
|
||||
"type-check": "turbo run type-check",
|
||||
"format": "prettier --write \"**/*.{ts,tsx,json,md}\"",
|
||||
"docker:build": "docker compose build",
|
||||
"docker:up": "docker compose up -d",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
5
pnpm-lock.yaml
generated
5
pnpm-lock.yaml
generated
@@ -62,6 +62,9 @@ importers:
|
||||
'@nestjs/swagger':
|
||||
specifier: ^7.4.0
|
||||
version: 7.4.2(@nestjs/common@11.1.23(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.23)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)
|
||||
'@prisma/client':
|
||||
specifier: ^6.19.3
|
||||
version: 6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@sendgrid/mail':
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.6
|
||||
@@ -116,7 +119,7 @@ importers:
|
||||
version: 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3)
|
||||
'@nestjs/testing':
|
||||
specifier: ^11.1.19
|
||||
version: 11.1.23(@nestjs/common@11.1.23(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.23)(@nestjs/platform-express@11.1.23)
|
||||
version: 11.1.23(@nestjs/common@11.1.23(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.23)(@nestjs/microservices@11.1.23)(@nestjs/platform-express@11.1.23)
|
||||
'@prisma/client':
|
||||
specifier: ^6.19.3
|
||||
version: 6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3)
|
||||
|
||||
19
scripts/deploy/create-npmrc.sh
Normal file
19
scripts/deploy/create-npmrc.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create .npmrc_temp and .npmrc for Docker BuildKit / compose secrets.
|
||||
# Requires NPM_TOKEN in the environment.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ -z "${NPM_TOKEN:-}" ]]; then
|
||||
echo "NPM_TOKEN is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cat <<EOF > .npmrc_temp
|
||||
@tria-plc:registry=https://npm.pkg.github.com
|
||||
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
|
||||
always-auth=true
|
||||
EOF
|
||||
|
||||
cp .npmrc_temp .npmrc
|
||||
echo "Created .npmrc_temp and .npmrc for private @tria-plc packages"
|
||||
82
scripts/deploy/sync-env-from-server.sh
Normal file
82
scripts/deploy/sync-env-from-server.sh
Normal file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
# Sync .env files from the self-hosted runner filesystem into the repo.
|
||||
#
|
||||
# Usage:
|
||||
# PROJECT=edr-freight BRANCH=main ./scripts/deploy/sync-env-from-server.sh freight-api freight-portal freight-backoffice
|
||||
#
|
||||
# Server layout (one file per service):
|
||||
# /home/user/environmen/<project>/<branch-slug>/freight-api.env
|
||||
# /home/user/environmen/<project>/<branch-slug>/freight-portal.env
|
||||
# /home/user/environmen/<project>/<branch-slug>/freight-web.build.env (optional, exports VITE_API_URL etc.)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DEPLOY_USER="${DEPLOY_USER:-tria}"
|
||||
BRANCH="${BRANCH:?BRANCH is required}"
|
||||
BRANCH_SLUG="${BRANCH_SLUG:-$(echo "${BRANCH}" | tr "[:upper:]" "[:lower:]" | sed -E "s/[^a-z0-9]+/-/g; s/^-+//; s/-+$//")}"
|
||||
ENV_ROOT="${ENV_ROOT:-/home/${DEPLOY_USER}/environment/edr/${BRANCH_SLUG}/${PROJECT:?PROJECT is required}}"
|
||||
|
||||
if [[ ! -d "${ENV_ROOT}" ]]; then
|
||||
echo "Environment directory not found: ${ENV_ROOT}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Using environment directory: ${ENV_ROOT}"
|
||||
|
||||
declare -A SERVICE_ENV_TARGET=(
|
||||
["freight-api"]="apps/edr-freight-api/.env"
|
||||
["freight-portal"]="apps/edr-freight-web/portal/.env"
|
||||
["freight-backoffice"]="apps/edr-freight-web/backoffice/.env"
|
||||
["passenger-api"]="apps/edr-passenger-api/.env"
|
||||
["passenger-portal"]="apps/edr-passenger-web/portal/.env"
|
||||
["passenger-backoffice"]="apps/edr-passenger-web/backoffice/.env"
|
||||
)
|
||||
|
||||
for service in "$@"; do
|
||||
src="${ENV_ROOT}/${service}.env"
|
||||
dest="${SERVICE_ENV_TARGET[${service}]:-}"
|
||||
|
||||
if [[ -z "${dest}" ]]; then
|
||||
echo "Unknown service: ${service}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "${src}" ]]; then
|
||||
echo "Missing env file: ${src}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "${dest}")"
|
||||
cp "${src}" "${dest}"
|
||||
echo "Synced ${src} -> ${dest}"
|
||||
|
||||
port_value=$(sed -n -E 's/^[[:space:]]*PORT[[:space:]]*=[[:space:]]*"?([^"#]+)"?[[:space:]]*(#.*)?$/\1/p' "${src}" | head -n1 | tr -d '[:space:]')
|
||||
if [[ -z "${port_value}" ]]; then
|
||||
echo "Missing required PORT in env file: ${src}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "${GITHUB_ENV:-}" ]]; then
|
||||
service_var=$(echo "${service}" | tr '[:lower:]-' '[:upper:]_')
|
||||
echo "${service_var}_PORT=${port_value}" >> "${GITHUB_ENV}"
|
||||
echo "Exported ${service_var}_PORT from ${src}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Optional build-time variables (VITE_API_URL, etc.)
|
||||
# Set BUILD_ENV_FILE=freight-web.build.env or passenger-web.build.env per workflow.
|
||||
build_env_file="${BUILD_ENV_FILE:-web.build.env}"
|
||||
build_env="${ENV_ROOT}/${build_env_file}"
|
||||
if [[ -f "${build_env}" ]]; then
|
||||
echo "Loading build variables from ${build_env}"
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "${build_env}"
|
||||
set +a
|
||||
|
||||
if [[ -n "${GITHUB_ENV:-}" ]]; then
|
||||
grep -E '^[[:space:]]*export[[:space:]]+[A-Za-z_][A-Za-z0-9_]*=' "${build_env}" \
|
||||
| sed -E 's/^[[:space:]]*export[[:space:]]+//' >> "${GITHUB_ENV}"
|
||||
echo "Wrote build variables to GITHUB_ENV"
|
||||
fi
|
||||
fi
|
||||
Reference in New Issue
Block a user