Add Phase 1 and Phase 2 planning documents

ROADMAP plus granular task files per phase. Phase 1 (12 tasks + 1.13
device authority) covers Codec 8/8E/16 telemetry ingestion; Phase 2
(6 tasks) covers Codec 12/14 outbound commands; Phase 3 enumerates
deferred items.
This commit is contained in:
2026-04-30 15:47:06 +02:00
parent 95e60a2c75
commit c8a5f4cd68
23 changed files with 2508 additions and 0 deletions
@@ -0,0 +1,175 @@
# Task 1.11 — Dockerfile & Gitea workflow
**Phase:** 1 — Inbound telemetry
**Status:** ⬜ Not started
**Depends on:** 1.8 (so the service actually does something), 1.10 (metrics endpoint for healthcheck)
**Wiki refs:** `docs/wiki/sources/gps-tracking-architecture.md` § 7.3 Deployment topology
## Goal
Produce a multi-stage Docker image and a Gitea Actions workflow that builds and pushes the image to the project's Gitea Container Registry on every push to `main` and every tag.
## Deliverables
- `Dockerfile` — multi-stage build (deps → build → runtime).
- `.dockerignore` — already created in task 1.1; verify it excludes `.planning/`, `test/`, `dist/` (rebuilt in image).
- `.gitea/workflows/build.yml` — Gitea Actions workflow.
- `compose.yaml` (alongside Dockerfile) — example local stack with Redis for `pnpm docker:dev`. Useful for local testing of the full pipeline.
- Documentation updates in `README.md` covering: build, run locally, run via compose, CI behavior, image registry path.
## Specification
### Dockerfile
```dockerfile
# syntax=docker/dockerfile:1.7
# ---- deps stage: install with cache-friendly pnpm fetch ----
FROM node:22-alpine AS deps
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@latest-9 --activate
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm fetch
# ---- build stage: compile TypeScript ----
FROM deps AS build
COPY . .
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
pnpm install --frozen-lockfile --offline
RUN pnpm build
RUN pnpm prune --prod
# ---- runtime: slim, non-root ----
FROM node:22-alpine AS runtime
WORKDIR /app
RUN addgroup -S app && adduser -S -G app app
COPY --from=build --chown=app:app /app/node_modules ./node_modules
COPY --from=build --chown=app:app /app/dist ./dist
COPY --from=build --chown=app:app /app/package.json ./package.json
USER app
EXPOSE 5027 9090
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:9090/readyz || exit 1
CMD ["node", "dist/main.js"]
```
Notes:
- `node:22-alpine` is small (~100MB final image). If musl-related issues arise (rare with pure JS), fall back to `node:22-slim`.
- BuildKit cache mounts (`--mount=type=cache`) speed up rebuilds significantly; the Gitea runner must support BuildKit (it does by default with modern docker).
- `pnpm prune --prod` strips dev dependencies before the runtime copy.
- Healthcheck hits `/readyz` so the container reports unhealthy if Redis is unreachable.
### Gitea workflow
`.gitea/workflows/build.yml`:
```yaml
name: build
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
container: node:22-alpine
services:
redis:
image: redis:7-alpine
steps:
- uses: actions/checkout@v4
- run: corepack enable && corepack prepare pnpm@latest-9 --activate
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck
- run: pnpm lint
- run: pnpm test --coverage
env:
REDIS_URL: redis://redis:6379
build-and-push:
needs: test
if: gitea.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: git.dev.microservices.al
username: ${{ gitea.actor }}
password: ${{ secrets.GITEA_TOKEN }}
- id: meta
uses: docker/metadata-action@v5
with:
images: git.dev.microservices.al/trm/tcp-ingestion
tags: |
type=ref,event=branch
type=sha,prefix=,format=short
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
- uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=git.dev.microservices.al/trm/tcp-ingestion:buildcache
cache-to: type=registry,ref=git.dev.microservices.al/trm/tcp-ingestion:buildcache,mode=max
```
Tags produced:
- On push to `main`: `main`, `<short-sha>`, `latest`.
- On tag `v1.2.3`: `v1.2.3`, `1.2.3`, `1.2`, `latest` (because tag push is on default branch context-dependent — verify with the Gitea Actions semantics in your runner version; adjust if necessary).
- On PR: tests run, no push.
`GITEA_TOKEN` is provided by Gitea Actions automatically (similar to `GITHUB_TOKEN` in GitHub Actions). It must have package-write scope; configure once in repo settings if the default scope is read-only.
### compose.yaml (local dev)
```yaml
services:
redis:
image: redis:7-alpine
ports: ['6379:6379']
ingestion:
build: .
depends_on: [redis]
ports:
- '5027:5027' # Teltonika TCP
- '9090:9090' # metrics
environment:
NODE_ENV: production
INSTANCE_ID: local-1
REDIS_URL: redis://redis:6379
LOG_LEVEL: debug
restart: unless-stopped
```
### Deployment
Out of scope for this task: how the image is consumed in production (compose pull + restart? K8s? Watchtower?). Recommend a follow-up task once Phase 1 is functional, since the deployment substrate may not be fully decided yet. For now, the image is built and published; humans pull and run it manually.
## Acceptance criteria
- [ ] `docker build .` succeeds locally and produces an image under 200MB.
- [ ] `docker compose up` starts both Redis and the ingestion service; the service's `/healthz` and `/readyz` return 200.
- [ ] On push to `main`, the Gitea workflow runs tests, builds the image, and publishes it to the registry. The image is visible in the Gitea Packages UI.
- [ ] On a tag push, the image is also tagged with the version.
- [ ] On a PR, only the test job runs (no push).
- [ ] BuildKit cache reduces a rebuild-with-no-changes to under 30 seconds.
## Risks / open questions
- The exact Gitea Actions feature parity with GitHub Actions varies by runner version. If `docker/metadata-action@v5` doesn't work as expected, fall back to a hand-rolled tag generator using `git rev-parse --short HEAD`.
- `GITEA_TOKEN` permissions: confirm the default token can push to the registry. If not, switch to a dedicated `secrets.REGISTRY_TOKEN`.
- Architecture: build only `linux/amd64` for now. Multi-arch (`linux/arm64`) is a follow-up if anyone needs it for Apple Silicon dev.
## Done
(Fill in once complete.)