Commit Graph

6 Commits

Author SHA1 Message Date
Julien Gautier 9aaf9f1f45 fix(infra): correct Jaeger image tag
CI / commits (pull_request) Successful in 1m11s
CI / scan (pull_request) Successful in 1m20s
CI / check (pull_request) Successful in 1m22s
CI / a11y (pull_request) Successful in 1m14s
CI / perf (pull_request) Successful in 2m41s
`jaegertracing/all-in-one:1.62` does not exist on Docker Hub —
Jaeger 1.x publishes only full-semver tags (1.X.Y), not rolling
minor tags. Activating the `observability` profile failed at pull
time:

    Error response from daemon: failed to resolve reference
    "docker.io/jaegertracing/all-in-one:1.62": ... not found

Pin to `1.76.0`, the most recent stable in the 1.x line. Renovate's
docker-compose manager will surface future bumps automatically
(gated behind dashboard approval for majors per renovate.json).
2026-05-08 20:03:38 +02:00
julien 0f00d6d93f feat(infra): add local-dev Docker Compose stack (#57)
CI / commits (push) Has been skipped
CI / check (push) Successful in 1m12s
CI / scan (push) Successful in 1m20s
CI / a11y (push) Successful in 41s
CI / perf (push) Successful in 2m9s
## Summary
Bring up Postgres + Redis + OTel Collector in one command so contributors can run the BFF end-to-end without manually wiring each service. Replaces the throwaway `docker run postgres:17-alpine` one-liner that was in `docs/development.md` §3.

### What lands
- **`infra/local/dev.compose.yml`** — three core services (`postgres:17.2-alpine`, `redis:7.4-alpine`, `otel/opentelemetry-collector-contrib:0.115.0`) plus two viewers gated behind Compose profiles:
  - `--profile dbtools` → `sosedoff/pgweb:0.16.2` (Postgres GUI on port 8081)
  - `--profile observability` → `jaegertracing/all-in-one:1.62` (Jaeger UI on 16686)
  - All ports overridable via `.env`. State in named volumes. Healthchecks on data services.
- **`infra/local/.env.example`** — credentials + ports template. `POSTGRES_PASSWORD` and `REDIS_PASSWORD` are mandatory (compose refuses to boot without them); other keys default sensibly.
- **`infra/local/init/postgres/01-init.sql`** — bootstrap SQL per **ADR-0013**: `audit_owner` / `audit_writer` / `audit_reader` / `audit_archiver` roles + `audit` schema. Default privileges encode the append-only contract (INSERT to writer, SELECT to reader, DELETE to archiver, no UPDATE/TRUNCATE to anyone). Applied on first Postgres boot only; documented re-run procedure.
- **`infra/local/otel-collector.yaml`** — pipeline: OTLP gRPC/HTTP → batch → debug exporter (always) + forward to `jaeger:4317`. When the observability profile is off, the Jaeger export logs warn-level retries but doesn't block the debug pipeline.

### Surrounding doc updates
- **`infra/README.md`** — new "Local-dev stack" section: service inventory, port table, first-time setup walkthrough, persistence/bootstrap-replay tips. The previous `local/` placeholder line is removed.
- **`docs/development.md`** §3 — rewritten to walk through the compose-based setup; cross-links to `infra/README.md` for the full reference. Roadmap entry for "Local infra recipe" removed from §8 (now implemented); "Observability dev-loop" line adjusted to point at the new Jaeger profile.

### Out of scope
- **Production parity** — HA Postgres, Redis Sentinel, real OTel backend (Tempo / Loki / etc.) — defer to the on-prem infrastructure ADR (phase 3b). The dev-only nature of this stack is called out explicitly in `infra/README.md`.
- **Wiring the BFF** to actually use these endpoints (NestJS config, Prisma datasource URL, OTel SDK init) — that's the **B — Observability foundations** chantier, next up.

## Test plan
- [ ] `cd infra/local && cp .env.example .env && docker compose -f dev.compose.yml up -d` → all three core services come up healthy; verify with `docker compose ps`.
- [ ] `psql postgres://portal:<pwd>@localhost:5432/portal_dev -c "\dn"` shows the `audit` schema; `\dg` shows the four audit roles.
- [ ] `redis-cli -a <pwd> PING` → `PONG`.
- [ ] Send a fake OTLP trace via grpcurl → see it printed by `docker compose logs otel-collector`.
- [ ] `--profile dbtools up -d` → http://localhost:8081 shows pgweb UI, can navigate to the audit schema.
- [ ] `--profile observability up -d` → http://localhost:16686 shows Jaeger UI; collector logs no longer report Jaeger export retries.
- [ ] `docker compose down -v` cleanly removes everything; next `up -d` re-runs the bootstrap SQL.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #57
2026-05-08 19:23:43 +02:00
julien efa660abab chore(infra): pin act_runner image pull policy (#10)
CI / check (push) Successful in 1m39s
CI / commits (push) Has been skipped
CI / scan (push) Failing after 52s
CI / a11y (push) Successful in 51s
CI / perf (push) Successful in 1m53s
## Summary
`act_runner`'s default `container.force_pull: true` re-issues a `docker pull` at the start of every job, adding 10–30 s of registry round-trip even when every layer is already locally cached. With job images pinned to specific tags (`catthehacker/ubuntu:act-22.04` and `:full-22.04`), the implicit pull is pure overhead and contradicts the deliberate-upgrade policy ADR-0015 spells out for the runner image.

- Add `infra/runner-config.yaml` with `container.force_pull: false`.
- Mount it read-only into all three runners and point each at it via `CONFIG_FILE=/etc/runner/config.yaml`.
- Document the pre-pull procedure and image-upgrade playbook in `infra/README.md` → "Job image pinning and pre-pull".
- Fold the pre-pull into the "First-time registration" walkthrough so a fresh setup is correct end-to-end.

The trade-off: the runner host must hold the images locally before the runner is asked to use them. Documented.

## Roll-out (manual, on the runner host)
```bash
cd infra/

# 1. Pre-pull the job images (one-shot — pays the cold cost once).
docker pull catthehacker/ubuntu:act-22.04
docker pull catthehacker/ubuntu:full-22.04

# 2. Recreate the runners so the new mount + env var take effect.
docker compose -f ci-runners.compose.yml up -d --force-recreate

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #10
2026-05-04 15:55:17 +02:00
julien f2440fbf24 fix(ci): disable broken pnpm cache on actions/setup-node (#8)
CI / a11y (push) Successful in 45s
CI / perf (push) Successful in 2m40s
CI / commits (push) Failing after 12m17s
CI / scan (push) Failing after 12m18s
CI / check (push) Failing after 12m19s
## Summary
`act_runner`'s built-in GitHub-Actions-cache server binds inside the runner container on the compose-defined `apf-portal-act-runners` bridge. Jobs spawned via the mounted Docker socket land on Docker's default `bridge` network and can't reach it. Every job opting into `cache: 'pnpm'` ate ~2 min `ETIMEDOUT` on restore + another ~2 min on save — across the 5 jobs, ~20 min wasted per CI run for zero cache hits.

Drop `cache: 'pnpm'` everywhere. `pnpm install --frozen-lockfile` is fast on the warm store inside the job container, so removing the cache layer is a net gain today.

The proper fix (cross-container networking / fixed-port cache binding) is documented in `infra/README.md` → "Cache server (deferred)" so it can be picked up as an isolated infra spike later.

## Test plan
- [ ] CI run on this PR: every job's `Set up Node.js` step finishes in seconds (no ETIMEDOUT warning).
- [ ] `Complete job` step also finishes promptly (no `reserveCache failed` warning).
- [ ] Total wall-clock time for the run should drop by ~15-20 min vs. previous runs.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #8
2026-05-04 15:33:04 +02:00
julien 02bdd43fa1 fix(ci): pin act_runner job image to catthehacker/ubuntu:act-22.04 (#4)
CI / commits (push) Has been skipped
CI / check (push) Failing after 3s
CI / scan (push) Failing after 9s
CI / a11y (push) Failing after 4s
CI / perf (push) Failing after 4s
## Summary

- Pin `act_runner`'s job container image to `catthehacker/ubuntu:act-22.04` via the `<label>:docker://<image>` format on the runner registration labels.
- Update `infra/ci-runners.compose.yml`'s `GITEA_RUNNER_LABELS` for all three runner services, with an inline comment explaining the format requirement.
- Amend ADR-0015 §"Runners" to specify the chosen image and explain the docker-suffix syntax trap.

## Motivation

The first real PR test of the CI pipeline failed at the very first step:

```
Run actions/checkout@v4
0s
Cannot find: node in PATH
 Failure - Main actions/checkout@v4
```

Root cause: `act_runner` registers labels (`self-hosted`, `on-prem`) without a `docker://` image suffix. Without that suffix, act spawns jobs in a minimal default container that has no Node. Every JavaScript action (`actions/checkout`, `actions/setup-node`, `nrwl/nx-set-shas`, the Trivy/gitleaks actions) crashes during the action's launch step. None of the five CI gates can run.

`catthehacker/ubuntu:act-22.04` is the de facto image used by `act` upstream and the standard recommendation for self-hosted Gitea Actions runners. It bundles Node, Python, git, common build tools, and the Docker CLI — exactly the assumed environment for GitHub Actions-compatible workflows.

## Implementation notes

- The fix lives in `GITEA_RUNNER_LABELS` because that's what `act_runner` reads at registration time. The label-to-image mapping is then persisted in the runner's `data/runner-N/.runner` credential.
- For runners **already registered** (i.e. the three currently-running `apf-portal-runner-N`), the persisted credential ignores the new env var. Their labels must be updated through Gitea's UI (Site Administration → Actions → Runners → each runner → edit `Labels`). This is documented in the commit message and is an operational follow-up to merging this PR.
- The compose-file change applies the next time a runner is re-registered (e.g. when `data/runner-N/` is wiped or a new fourth runner is added).
- ADR-0015 amendment is in-place, status remains `accepted`. The runner-image choice is an implementation detail under the existing decision; no new ADR.

## Verification

- [ ] `pnpm ci:check` — n/a, this PR only changes infra and ADR docs, no code paths.
- [ ] **Manual:** after merge + Gitea label updates, the next PR's `actions/checkout@v4` step runs without `Cannot find: node in PATH`.

## Related

- [ADR-0015 — CI/CD pipeline](docs/decisions/0015-cicd-gitea-actions.md). Amended in §"Runners".
- [`infra/README.md`](infra/README.md) — operational doc for the runners; mentions the registration workflow but predates this format requirement. A subsequent docs touch could mirror the new label format there too; deferred to keep this PR scoped to the actual fix.

---------

Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #4
2026-05-04 11:06:56 +02:00
Julien Gautier 644d3603d2 chore: add docker compose for self-hosted Gitea act_runners
CI / check (pull_request) Failing after 6s
CI / commits (pull_request) Failing after 2s
CI / scan (pull_request) Failing after 6s
CI / a11y (pull_request) Failing after 2s
CI / perf (pull_request) Failing after 3s
Set up the infrastructure-as-code recipe to bring up the three
self-hosted Gitea Actions runners required by ADR-0015. The compose
file launches three act_runner instances pinned to 0.2.13, registered
with the project's Gitea organisation, labelled self-hosted + on-prem
to match the runs-on selector in every job under .gitea/workflows/*.

Layout:
- infra/                         new top-level folder for IaC
- infra/README.md                explains the folder, registration
                                 flow, security implications, future
                                 placeholders (local/, prod/, runbooks/)
- infra/ci-runners.compose.yml   three act_runner services, networked
                                 together, persisting credentials to
                                 ./data/runner-N
- infra/.env.example             GITEA_INSTANCE_URL +
                                 GITEA_RUNNER_REGISTRATION_TOKEN; .env
                                 itself stays git-ignored (root rule)
- infra/data/.gitignore          tracks the dir, ignores runtime state

Security posture (documented in infra/README.md): mounting
/var/run/docker.sock gives the runner root-equivalent access to the
host Docker daemon. Mitigations rely on (a) repo-scope of the runner
in Gitea, (b) running the runner host outside the production trust
boundary, (c) no extra host filesystem mounts. Future hardening
(rootless Docker, DinD sidecar) is flagged as deferred.

The compose pins the runner image (0.2.13). Bumps go through a
dedicated chore(deps) PR per the convention; image upgrades roll one
runner at a time so CI is never starved (procedure documented in the
README).

Doc indexing (docs/README.md) deliberately not touched here to avoid
a conflict with the pending docs/architecture-diagrams branch which
also modifies that file. A small follow-up PR will add an index entry
once that branch is merged.
2026-05-01 00:00:28 +02:00