diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..61b1db9 --- /dev/null +++ b/.env.example @@ -0,0 +1,31 @@ +# Copy to `.env` for local docker-compose runs, OR enter these values in +# Portainer's Stack → Environment variables UI. +# +# All variables have defaults baked into compose.yaml — this file is the +# documentation of what's configurable, not a hard requirement. + +# --------------------------------------------------------------------- +# tcp-ingestion +# --------------------------------------------------------------------- + +# Image tag to pull. `main` auto-tracks the latest commit on the main branch. +# In production, pin to a specific commit SHA for reproducibility. +# Example: TCP_INGESTION_TAG=af06973 +TCP_INGESTION_TAG=main + +# Instance identifier — must be stable across the lifetime of the process. +# Phase 2's connection registry depends on this; keep it unique per deployed +# instance (e.g. `stage-1`, `stage-2`, `prod-eu-1`). +TCP_INGESTION_INSTANCE_ID=stage-1 + +# Host port that GPS devices connect to. The container always listens on 5027 +# internally; this maps it to a host port. If multiple stacks run on one host, +# give each a distinct host port (e.g. 5028, 5029). +TCP_INGESTION_PORT=5027 + +# --------------------------------------------------------------------- +# Shared +# --------------------------------------------------------------------- + +# pino log level: fatal | error | warn | info | debug | trace +LOG_LEVEL=info diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4d4b03a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +.env.local +.env.*.local +*.log diff --git a/README.md b/README.md index e69de29..8c702f2 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,90 @@ +# TRM Deploy + +Deployment configuration for the TRM platform. This repo holds the multi-service `compose.yaml` and per-environment overrides; service code lives in sibling repos under `git.dev.microservices.al/trm/`. + +## Layout + +``` +deploy/ +├── compose.yaml ← Portainer's Compose path +├── .env.example ← documented variables; copy to .env locally +├── .gitignore +└── README.md +``` + +## Services in the stack + +Currently: + +- **redis** — telemetry queue + future Phase 2 connection registry. Internal-only, persisted via named volume. +- **tcp-ingestion** — Teltonika telemetry TCP server. Image built by [`trm/tcp-ingestion`](https://git.dev.microservices.al/trm/tcp-ingestion)'s Gitea workflow. + +Planned (will be added as they land): + +- **processor** — consumes telemetry from Redis, writes to PostgreSQL/Timescale. +- **postgres** — with TimescaleDB extension. +- **directus** — business-plane API and admin UI. +- **react-spa** — front-end SPA (static bundle, served via reverse proxy). + +See `../docs/wiki/` for the full architecture. + +## Deploy via Portainer (Repository Stack) + +1. **Stack → Add stack → Repository** in Portainer. +2. Repository URL: `https://git.dev.microservices.al/trm/deploy` +3. Branch: `main` +4. Compose path: `compose.yaml` +5. Environment variables: leave empty for defaults, or set per `.env.example`. +6. (Optional) Enable **Automatic updates** with a webhook. Portainer will poll or accept a webhook to redeploy when this repo's `main` changes. + +Before the first deploy, the Portainer host must be authenticated to the Gitea registry: + +```bash +docker login git.dev.microservices.al +``` + +(Or configure registry credentials in Portainer's **Registries** UI — preferred.) + +## Deploy without Portainer (manual) + +```bash +git clone https://git.dev.microservices.al/trm/deploy +cd deploy +cp .env.example .env +# edit .env if you want to override defaults +docker compose pull +docker compose up -d +``` + +## Updating + +When a service publishes a new image (Gitea workflow on push to `main`): + +```bash +docker compose pull +docker compose up -d +``` + +Portainer with automatic updates does this automatically. + +To pin a specific build for production, set the relevant `*_TAG` variable in `.env` (or in Portainer's stack environment) to a commit SHA — e.g. `TCP_INGESTION_TAG=af06973`. + +## Network model + +- One internal Compose network (`trm_default`). +- Redis is **not** bound to a host port — only reachable from other services in the stack via service-name DNS (`redis://redis:6379`). +- tcp-ingestion's TCP port (5027 by default) is bound to the host so devices can reach it. +- Other Redis instances on the same host can keep using port 6379 freely; this stack does not collide with them. + +## Environment variables + +See `.env.example` for the documented set with defaults and explanations. + +## Why a separate repo + +Compose covers multiple services; placing it inside any one service repo creates ownership ambiguity (which service "owns" the Postgres definition? the Redis volume?). Keeping deploy config in its own repo means: + +- Compose changes are versioned independently of any service's code. +- Portainer's Repository stack tracks one source of truth. +- Per-environment overrides (e.g. `compose.stage.yaml`, `compose.prod.yaml`) can be added cleanly later. +- Adding a new service is a one-file change here, not a coordinated edit across repos. diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..88b363a --- /dev/null +++ b/compose.yaml @@ -0,0 +1,69 @@ +# TRM platform — deployment stack +# +# Deployed via Portainer Repository Stack: +# Repository: git.dev.microservices.al/trm/deploy +# Compose path: compose.yaml +# Branch: main +# +# Images are built and pushed by each service's own Gitea workflow. +# This file references them by tag and runs them as a coordinated stack. +# +# Before first deploy on the host: `docker login git.dev.microservices.al` +# (Portainer can store registry credentials in its UI; configure once.) +# +# Environment variables are populated from Portainer's stack environment +# config (or a `.env` file alongside this compose for non-Portainer hosts). +# Defaults are provided via `${VAR:-default}` so the stack starts with no +# explicit configuration on a fresh deploy. + +name: trm + +services: + # ------------------------------------------------------------------- + # Redis — telemetry queue + (future) connection registry for Phase 2. + # Internal-only; no host port mapping. + # ------------------------------------------------------------------- + redis: + image: redis:7-alpine + expose: + - '6379' + volumes: + - redis-data:/data + restart: unless-stopped + healthcheck: + test: ['CMD', 'redis-cli', 'ping'] + interval: 10s + timeout: 3s + retries: 5 + + # ------------------------------------------------------------------- + # tcp-ingestion — Teltonika telemetry TCP server. + # Built by git.dev.microservices.al/trm/tcp-ingestion's Gitea workflow. + # ------------------------------------------------------------------- + tcp-ingestion: + image: git.dev.microservices.al/trm/tcp-ingestion:${TCP_INGESTION_TAG:-main} + depends_on: + redis: + condition: service_healthy + ports: + # Devices connect to this port. Use `${HOST_BIND_IP:-0.0.0.0}:5027:5027` + # if you want to restrict which host interface accepts connections. + - '${TCP_INGESTION_PORT:-5027}:5027' + environment: + NODE_ENV: production + INSTANCE_ID: ${TCP_INGESTION_INSTANCE_ID:-stage-1} + REDIS_URL: redis://redis:6379 + LOG_LEVEL: ${LOG_LEVEL:-info} + restart: unless-stopped + + # ------------------------------------------------------------------- + # Future services land here: + # - processor: consumes telemetry from Redis, writes to DB + # - postgres: PostgreSQL with TimescaleDB extension + # - directus: business-plane API + admin UI + # - react-spa: front-end (static, served via nginx or Caddy) + # See ../docs/wiki/ for the platform architecture. + # ------------------------------------------------------------------- + +volumes: + redis-data: