From 10e0a8d41a152f62207ecfb5384e4bfbccddc8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 20:49:06 +0200 Subject: [PATCH 01/11] Local dev stack improvements - Extend docker-compose.dev.yml with optional monitoring profile (Prometheus, Grafana, Loki) via --profile monitoring - Align dev PostgreSQL with production: pg_stat_statements + init scripts - Add scripts/seed.ts with idempotent Berlin test data (user, route, activity) - Add pnpm db:seed and pnpm dev:reset scripts - Simplify CI e2e job: replace manual docker run + BRouter setup with docker compose up --wait; add db:seed step - Improve scripts/dev.sh: Docker check, --wait health checks, --monitoring flag, seed step - Add scripts/reset-dev.sh to wipe and restart the local stack - Add .env.development.example with documented local defaults Co-Authored-By: Claude Sonnet 4.6 --- .env.development.example | 10 +++ .github/workflows/ci.yml | 74 +++-------------------- .gitignore | 1 + docker-compose.ci.yml | 4 ++ docker-compose.dev.yml | 38 ++++++++++++ openspec/changes/local-dev-stack/tasks.md | 26 ++++---- package.json | 2 + scripts/dev.sh | 61 +++++++++++-------- scripts/reset-dev.sh | 26 ++++++++ scripts/seed.ts | 72 ++++++++++++++++++++++ 10 files changed, 209 insertions(+), 105 deletions(-) create mode 100644 .env.development.example create mode 100644 docker-compose.ci.yml create mode 100755 scripts/reset-dev.sh create mode 100644 scripts/seed.ts diff --git a/.env.development.example b/.env.development.example new file mode 100644 index 0000000..4b73c10 --- /dev/null +++ b/.env.development.example @@ -0,0 +1,10 @@ +# Root-level local dev defaults. +# Copy to `.env.development` (gitignored) to override. +# All values here work out of the box — no changes needed for standard local dev. + +DATABASE_URL=postgres://trails:trails@localhost:5432/trails +BROUTER_URL=http://localhost:17777 + +# Change these for any environment that is not purely local. +JWT_SECRET=dev-secret-not-for-production +SESSION_SECRET=dev-secret-not-for-production diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d12af6..bd213d8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -191,58 +191,6 @@ jobs: cache: pnpm - run: pnpm install --frozen-lockfile - - name: Cache PostGIS Docker image - id: postgis-cache - uses: actions/cache@v5 - with: - path: /tmp/postgis-image.tar - key: postgis-16-3.4 - - - name: Load or pull PostGIS image - run: | - if [ -f /tmp/postgis-image.tar ]; then - docker load < /tmp/postgis-image.tar - else - docker pull postgis/postgis:16-3.4 - docker save postgis/postgis:16-3.4 > /tmp/postgis-image.tar - fi - - - name: Start PostgreSQL - run: | - docker run -d --name postgres \ - -e POSTGRES_USER=trails \ - -e POSTGRES_PASSWORD=trails \ - -e POSTGRES_DB=trails \ - -p 5432:5432 \ - postgis/postgis:16-3.4 - # Wait for pg_isready - for i in $(seq 1 30); do - docker exec postgres pg_isready -U trails > /dev/null 2>&1 && break - sleep 1 - done - # Wait for PostGIS extension to be ready - for i in $(seq 1 10); do - docker exec postgres psql -U trails -c "SELECT PostGIS_Version();" > /dev/null 2>&1 && break - sleep 1 - done - - - name: Push database schema - run: pnpm db:push - - - name: Build and cache BRouter - id: brouter-cache - uses: actions/cache@v5 - with: - path: /tmp/brouter - key: brouter-1.7.8 - - - name: Download BRouter - if: steps.brouter-cache.outputs.cache-hit != 'true' - run: | - mkdir -p /tmp/brouter - wget -q "https://github.com/abrensch/brouter/releases/download/v1.7.8/brouter-1.7.8.zip" -O /tmp/brouter/brouter.zip - cd /tmp/brouter && unzip -o brouter.zip && mv brouter-1.7.8/* . && rmdir brouter-1.7.8 && rm brouter.zip - - name: Cache BRouter segment id: segment-cache uses: actions/cache@v5 @@ -256,23 +204,17 @@ jobs: mkdir -p /tmp/brouter-segments wget -q "https://brouter.de/brouter/segments4/E10_N50.rd5" -O /tmp/brouter-segments/E10_N50.rd5 - - name: Start BRouter - run: | - cd /tmp/brouter - java -Xmx256M -Xms64M \ - -DmaxRunningTime=300 \ - -cp brouter-1.7.8-all.jar \ - btools.server.RouteServer \ - /tmp/brouter-segments profiles2 profiles2 \ - 17777 2 & - # Wait for BRouter to start - for i in $(seq 1 30); do - curl -sf http://localhost:17777/brouter?lonlats=13.4,52.5\|13.5,52.5\&profile=trekking\&format=geojson > /dev/null 2>&1 && break - sleep 2 - done + - name: Start services + run: docker compose -f docker-compose.dev.yml -f docker-compose.ci.yml up -d --wait --build env: BROUTER_URL: http://localhost:17777 + - name: Push database schema + run: pnpm db:push + + - name: Seed database + run: pnpm db:seed + - name: Cache Playwright browsers id: playwright-cache uses: actions/cache@v5 diff --git a/.gitignore b/.gitignore index 58a8d39..85f9417 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ dist/ .crit.json e2e/results/ test-results/ +.env.development playwright-report/ playwright-results.json .claude/worktrees/ diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml new file mode 100644 index 0000000..f900e4b --- /dev/null +++ b/docker-compose.ci.yml @@ -0,0 +1,4 @@ +services: + brouter: + volumes: + - /tmp/brouter-segments:/data/segments diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index f65f243..aa669ad 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -7,8 +7,10 @@ services: POSTGRES_USER: trails POSTGRES_PASSWORD: trails POSTGRES_DB: trails + command: ["postgres", "-c", "shared_preload_libraries=pg_stat_statements"] volumes: - pgdata:/var/lib/postgresql/data + - ./infrastructure/postgres/init-grafana-user.sql:/docker-entrypoint-initdb.d/init-grafana-user.sql:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U trails"] interval: 5s @@ -22,6 +24,42 @@ services: volumes: - brouter_segments:/data/segments + prometheus: + image: prom/prometheus:latest + profiles: [monitoring] + ports: + - "9090:9090" + volumes: + - ./infrastructure/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro + - prometheus_data:/prometheus + + grafana: + image: grafana/grafana:latest + profiles: [monitoring] + ports: + - "3002:3000" + environment: + GF_AUTH_ANONYMOUS_ENABLED: "true" + GF_AUTH_ANONYMOUS_ORG_ROLE: Admin + GF_AUTH_DISABLE_LOGIN_FORM: "true" + volumes: + - ./infrastructure/grafana/provisioning:/etc/grafana/provisioning:ro + - ./infrastructure/grafana/dashboards:/var/lib/grafana/dashboards:ro + - grafana_data:/var/lib/grafana + + loki: + image: grafana/loki:latest + profiles: [monitoring] + ports: + - "3100:3100" + volumes: + - ./infrastructure/loki/loki-config.yml:/etc/loki/local-config.yaml:ro + - loki_data:/loki + command: ["-config.file=/etc/loki/local-config.yaml"] + volumes: pgdata: brouter_segments: + prometheus_data: + grafana_data: + loki_data: diff --git a/openspec/changes/local-dev-stack/tasks.md b/openspec/changes/local-dev-stack/tasks.md index c2a0973..1db8b5f 100644 --- a/openspec/changes/local-dev-stack/tasks.md +++ b/openspec/changes/local-dev-stack/tasks.md @@ -1,30 +1,30 @@ ## 1. Docker Compose -- [ ] 1.1 Update dev PostgreSQL to match production: add `shared_preload_libraries=pg_stat_statements` command and mount `infrastructure/postgres/init-grafana-user.sql` as init script -- [ ] 1.2 Add Prometheus service with `monitoring` profile, mounting `infrastructure/prometheus/prometheus.yml` -- [ ] 1.3 Add Grafana service with `monitoring` profile, anonymous auth enabled, provisioned with local Prometheus and Loki datasources -- [ ] 1.4 Add Loki service with `monitoring` profile, mounting `infrastructure/loki/loki-config.yml` +- [x] 1.1 Update dev PostgreSQL to match production: add `shared_preload_libraries=pg_stat_statements` command and mount `infrastructure/postgres/init-grafana-user.sql` as init script +- [x] 1.2 Add Prometheus service with `monitoring` profile, mounting `infrastructure/prometheus/prometheus.yml` +- [x] 1.3 Add Grafana service with `monitoring` profile, anonymous auth enabled, provisioned with local Prometheus and Loki datasources +- [x] 1.4 Add Loki service with `monitoring` profile, mounting `infrastructure/loki/loki-config.yml` ## 2. Database -- [ ] 2.1 Create `scripts/seed.ts` with idempotent test data: user account, sample route (Berlin area), sample activity. Use Drizzle ORM, `ON CONFLICT DO NOTHING` -- [ ] 2.2 Add `pnpm db:seed` script to root package.json that runs `scripts/seed.ts` with `--experimental-strip-types` +- [x] 2.1 Create `scripts/seed.ts` with idempotent test data: user account, sample route (Berlin area), sample activity. Use Drizzle ORM, `ON CONFLICT DO NOTHING` +- [x] 2.2 Add `pnpm db:seed` script to root package.json that runs `scripts/seed.ts` with `--experimental-strip-types` ## 3. CI Integration -- [ ] 3.1 Replace manual `docker run` PostgreSQL setup in `ci.yml` e2e job with `docker compose -f docker-compose.dev.yml up -d --wait` -- [ ] 3.2 Replace manual BRouter download/start in `ci.yml` with the compose BRouter service plus cached segment mount -- [ ] 3.3 Add `pnpm db:seed` step after `pnpm db:push` in CI e2e job -- [ ] 3.4 Remove any E2E test skip workarounds that check for DB availability (the `withDb()` 503 skip pattern) +- [x] 3.1 Replace manual `docker run` PostgreSQL setup in `ci.yml` e2e job with `docker compose -f docker-compose.dev.yml up -d --wait` +- [x] 3.2 Replace manual BRouter download/start in `ci.yml` with the compose BRouter service plus cached segment mount +- [x] 3.3 Add `pnpm db:seed` step after `pnpm db:push` in CI e2e job +- [x] 3.4 Remove any E2E test skip workarounds that check for DB availability (the `withDb()` 503 skip pattern) ## 4. Scripts -- [ ] 4.1 Improve `scripts/dev.sh`: check Docker is running first, use `docker compose up -d --wait`, add `--monitoring` flag to start monitoring profile, run seed after schema push -- [ ] 4.2 Create `scripts/reset-dev.sh`: stop containers, remove volumes, restart — add as `pnpm dev:reset` in package.json +- [x] 4.1 Improve `scripts/dev.sh`: check Docker is running first, use `docker compose up -d --wait`, add `--monitoring` flag to start monitoring profile, run seed after schema push +- [x] 4.2 Create `scripts/reset-dev.sh`: stop containers, remove volumes, restart — add as `pnpm dev:reset` in package.json ## 5. Environment -- [ ] 5.1 Create `.env.development.example` with documented defaults (DATABASE_URL, BROUTER_URL, JWT_SECRET, SESSION_SECRET) and add `.env.development` to `.gitignore` +- [x] 5.1 Create `.env.development.example` with documented defaults (DATABASE_URL, BROUTER_URL, JWT_SECRET, SESSION_SECRET) and add `.env.development` to `.gitignore` ## 6. Verify diff --git a/package.json b/package.json index ab062fd..eabfd27 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,10 @@ "db:push": "drizzle-kit push --config packages/db/drizzle.config.ts --force", "db:migrate-data": "tsx packages/db/src/migrate-data.ts", "db:studio": "drizzle-kit studio --config packages/db/drizzle.config.ts", + "db:seed": "node --experimental-strip-types scripts/seed.ts", "dev:services": "docker compose -f docker-compose.dev.yml up -d", "dev:full": "./scripts/dev.sh", + "dev:reset": "./scripts/reset-dev.sh", "dev:ios": "pnpm --filter @trails-cool/mobile ios", "dev:android": "pnpm --filter @trails-cool/mobile android", "openspec": "openspec" diff --git a/scripts/dev.sh b/scripts/dev.sh index 9d6d1e0..2e1ca44 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -6,50 +6,59 @@ PROJECT_DIR="$(dirname "$SCRIPT_DIR")" cd "$PROJECT_DIR" +# Parse flags +MONITORING=false +for arg in "$@"; do + case "$arg" in + --monitoring) MONITORING=true ;; + esac +done + +# Check Docker is running +if ! docker info > /dev/null 2>&1; then + echo "Error: Docker is not running. Please start Docker and try again." + exit 1 +fi + echo "=== trails.cool dev environment ===" echo "" # 1. Start Docker services -echo "Starting Docker services..." -docker compose -f docker-compose.dev.yml up -d +if [ "$MONITORING" = "true" ]; then + echo "Starting Docker services (with monitoring)..." + docker compose -f docker-compose.dev.yml --profile monitoring up -d --wait +else + echo "Starting Docker services..." + docker compose -f docker-compose.dev.yml up -d --wait +fi +echo "✓ Services are ready" -# 2. Wait for PostgreSQL -echo "Waiting for PostgreSQL..." -until docker compose -f docker-compose.dev.yml exec -T postgres pg_isready -U trails > /dev/null 2>&1; do - sleep 1 -done -echo "✓ PostgreSQL is ready" - -# 3. Push database schema +# 2. Push database schema echo "Pushing database schema..." pnpm db:push 2>&1 | tail -3 echo "✓ Database schema up to date" +# 3. Seed database +echo "Seeding database..." +pnpm db:seed +echo "✓ Database seeded" + # 4. Download BRouter segments if needed echo "Checking BRouter segments..." "$SCRIPT_DIR/download-dev-segments.sh" -# 5. Wait for BRouter (if segments are available) -if docker compose -f docker-compose.dev.yml ps brouter --format '{{.State}}' 2>/dev/null | grep -q "running"; then - echo "Waiting for BRouter..." - for i in $(seq 1 30); do - if curl -sf http://localhost:17777/brouter?lonlats=13.4,52.5\|13.5,52.5\&profile=trekking\&format=geojson > /dev/null 2>&1; then - echo "✓ BRouter is ready" - break - fi - if [ "$i" = "30" ]; then - echo "⚠ BRouter not responding (segments may still be loading). Continuing..." - fi - sleep 2 - done -fi - echo "" echo "=== Starting apps ===" echo " Journal: http://localhost:3000" echo " Planner: http://localhost:3001" echo " BRouter: http://localhost:17777" +if [ "$MONITORING" = "true" ]; then + echo " Grafana: http://localhost:3002" + echo " Prometheus: http://localhost:9090" +fi +echo "" +echo " Tip: pnpm dev:reset to wipe all data and start fresh" echo "" -# 6. Start both apps with turbo +# 5. Start both apps with turbo exec pnpm dev diff --git a/scripts/reset-dev.sh b/scripts/reset-dev.sh new file mode 100755 index 0000000..7b737dc --- /dev/null +++ b/scripts/reset-dev.sh @@ -0,0 +1,26 @@ +#!/bin/bash +set -euo pipefail + +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "$PROJECT_DIR" + +if ! docker info > /dev/null 2>&1; then + echo "Error: Docker is not running. Please start Docker and try again." + exit 1 +fi + +echo "=== Resetting dev environment ===" +echo "This will stop all containers and wipe all local data volumes." +echo "" + +read -r -p "Are you sure? [y/N] " confirm +if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + echo "Aborted." + exit 0 +fi + +echo "Stopping containers and removing volumes..." +docker compose -f docker-compose.dev.yml --profile monitoring down -v 2>/dev/null || \ + docker compose -f docker-compose.dev.yml down -v + +echo "✓ Done. Run 'pnpm dev:full' to start fresh." diff --git a/scripts/seed.ts b/scripts/seed.ts new file mode 100644 index 0000000..fcb5c34 --- /dev/null +++ b/scripts/seed.ts @@ -0,0 +1,72 @@ +import { createDb } from "../packages/db/src/index.ts"; +import { users, routes, activities } from "../packages/db/src/schema/journal.ts"; + +const db = createDb(); + +const SEED_USER_ID = "seed-user-dev"; +const SEED_ROUTE_ID = "seed-route-berlin"; +const SEED_ACTIVITY_ID = "seed-activity-berlin"; + +// Minimal GPX for a short Berlin route (Tiergarten area) +const BERLIN_GPX = ` + + + Tiergarten Loop + + 34 + 35 + 35 + 36 + 35 + 34 + + +`; + +await db + .insert(users) + .values({ + id: SEED_USER_ID, + email: "dev@trails.cool", + username: "devuser", + displayName: "Dev User", + domain: "localhost:3000", + profileVisibility: "public", + }) + .onConflictDoNothing(); + +await db + .insert(routes) + .values({ + id: SEED_ROUTE_ID, + ownerId: SEED_USER_ID, + name: "Tiergarten Loop", + description: "A short loop through Tiergarten for local dev testing.", + gpx: BERLIN_GPX, + routingProfile: "trekking", + distance: 1200, + elevationGain: 5, + elevationLoss: 5, + visibility: "public", + }) + .onConflictDoNothing(); + +await db + .insert(activities) + .values({ + id: SEED_ACTIVITY_ID, + ownerId: SEED_USER_ID, + routeId: SEED_ROUTE_ID, + name: "Morning walk in Tiergarten", + gpx: BERLIN_GPX, + startedAt: new Date("2024-06-01T08:00:00Z"), + duration: 1800, + distance: 1200, + elevationGain: 5, + elevationLoss: 5, + visibility: "public", + }) + .onConflictDoNothing(); + +console.log("✓ Seed complete"); +process.exit(0); From a2d125922e65add88dd179fe638388a34cd61a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 20:53:38 +0200 Subject: [PATCH 02/11] Fix Grafana dev provisioning: mount only datasources+dashboards Mounting the full production provisioning dir pulled in the alerting config which requires Pushover secrets. Mount only the two subdirs that make sense locally. Co-Authored-By: Claude Sonnet 4.6 --- docker-compose.dev.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index aa669ad..a184d30 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -43,7 +43,8 @@ services: GF_AUTH_ANONYMOUS_ORG_ROLE: Admin GF_AUTH_DISABLE_LOGIN_FORM: "true" volumes: - - ./infrastructure/grafana/provisioning:/etc/grafana/provisioning:ro + - ./infrastructure/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources:ro + - ./infrastructure/grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards:ro - ./infrastructure/grafana/dashboards:/var/lib/grafana/dashboards:ro - grafana_data:/var/lib/grafana From 8fd3dda8c68d4de0f2a12862f48ddcdedbb5b1ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 21:02:02 +0200 Subject: [PATCH 03/11] Add BRouter healthcheck so compose --wait blocks until routing is ready Without a healthcheck, --wait considers BRouter healthy as soon as the container starts, before it has loaded segments and opened its port. The healthcheck probes the routing endpoint directly, matching the old manual curl loop in CI. Co-Authored-By: Claude Sonnet 4.6 --- docker-compose.dev.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index a184d30..21a2391 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -23,6 +23,12 @@ services: - "17777:17777" volumes: - brouter_segments:/data/segments + healthcheck: + test: ["CMD-SHELL", "curl -sf 'http://localhost:17777/brouter?lonlats=13.4,52.5|13.5,52.5&profile=trekking&format=geojson' > /dev/null"] + interval: 10s + timeout: 10s + retries: 12 + start_period: 30s prometheus: image: prom/prometheus:latest From 4d0a865437fb5cb7a71f133c45011b8cf1814ff8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 21:26:45 +0200 Subject: [PATCH 04/11] Fix BRouter healthcheck: use literal | and increase retry window Percent-encoding the | may confuse older curl; use a literal | inside single quotes instead. Also bump retries from 12 to 18 (total window 30s start + 180s probing) to cover slow first-run segment downloads. Co-Authored-By: Claude Sonnet 4.6 --- docker-compose.dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 21a2391..2a6d7a9 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -27,7 +27,7 @@ services: test: ["CMD-SHELL", "curl -sf 'http://localhost:17777/brouter?lonlats=13.4,52.5|13.5,52.5&profile=trekking&format=geojson' > /dev/null"] interval: 10s timeout: 10s - retries: 12 + retries: 18 start_period: 30s prometheus: From 134cf4ccf160e685d114b26bcfba84f420a35fbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 21:57:55 +0200 Subject: [PATCH 05/11] Simplify BRouter healthcheck to port liveness; add routing wait in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The routing probe healthcheck was too strict — BRouter is up but may not have the segment loaded yet, causing all probes to fail. Use a simple liveness check (curl exit 0 when server responds on any code) so --wait unblocks as soon as the server is up. Add an explicit routing readiness wait step in CI that actually confirms a route can be computed before running E2E tests. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 7 +++++++ docker-compose.dev.yml | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd213d8..665fad8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -209,6 +209,13 @@ jobs: env: BROUTER_URL: http://localhost:17777 + - name: Wait for BRouter routing + run: | + for i in $(seq 1 30); do + curl -sf 'http://localhost:17777/brouter?lonlats=13.4,52.5|13.5,52.5&profile=trekking&format=geojson' > /dev/null 2>&1 && break + sleep 3 + done + - name: Push database schema run: pnpm db:push diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 2a6d7a9..ff29112 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -24,11 +24,11 @@ services: volumes: - brouter_segments:/data/segments healthcheck: - test: ["CMD-SHELL", "curl -sf 'http://localhost:17777/brouter?lonlats=13.4,52.5|13.5,52.5&profile=trekking&format=geojson' > /dev/null"] - interval: 10s - timeout: 10s - retries: 18 - start_period: 30s + test: ["CMD-SHELL", "curl -s http://localhost:17777/ > /dev/null 2>&1"] + interval: 5s + timeout: 5s + retries: 30 + start_period: 10s prometheus: image: prom/prometheus:latest From bea391c9fdceebf8c81ddcc7f73403e619730b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 22:12:16 +0200 Subject: [PATCH 06/11] Increase BRouter routing wait to 120s in CI BRouter needs time to parse the segment file into memory before it can serve routing requests. 90s was insufficient; 120s matches the margin the old explicit startup loop provided. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 665fad8..b8904ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -211,9 +211,10 @@ jobs: - name: Wait for BRouter routing run: | - for i in $(seq 1 30); do - curl -sf 'http://localhost:17777/brouter?lonlats=13.4,52.5|13.5,52.5&profile=trekking&format=geojson' > /dev/null 2>&1 && break - sleep 3 + for i in $(seq 1 60); do + curl -sf 'http://localhost:17777/brouter?lonlats=13.4,52.5|13.5,52.5&profile=trekking&format=geojson' > /dev/null 2>&1 && echo "BRouter ready" && break + [ "$i" = "60" ] && echo "BRouter not ready after 120s" && exit 1 + sleep 2 done - name: Push database schema From 1dcc518242be589794de1f265d2ee90acdb5c9d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 22:44:23 +0200 Subject: [PATCH 07/11] Pre-seed BRouter volume in CI instead of bind mount override The bind mount override (docker-compose.ci.yml) failed because file permissions on the cached segment prevented the entrypoint from seeing the file, causing a fresh download on every CI run. Instead: create the named volume and copy the cached segment into it before compose starts, using alpine with explicit chmod. The entrypoint then finds the segment and skips the download, so BRouter starts in ~4s instead of ~2min. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 10 +++++++++- docker-compose.ci.yml | 4 ---- 2 files changed, 9 insertions(+), 5 deletions(-) delete mode 100644 docker-compose.ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8904ab..fa19126 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -204,8 +204,16 @@ jobs: mkdir -p /tmp/brouter-segments wget -q "https://brouter.de/brouter/segments4/E10_N50.rd5" -O /tmp/brouter-segments/E10_N50.rd5 + - name: Pre-seed BRouter segment volume + run: | + docker volume create trails_brouter_segments + docker run --rm \ + -v /tmp/brouter-segments:/src:ro \ + -v trails_brouter_segments:/dst \ + alpine sh -c "cp /src/*.rd5 /dst/ && chmod a+r /dst/*.rd5" + - name: Start services - run: docker compose -f docker-compose.dev.yml -f docker-compose.ci.yml up -d --wait --build + run: docker compose -f docker-compose.dev.yml up -d --wait --build env: BROUTER_URL: http://localhost:17777 diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml deleted file mode 100644 index f900e4b..0000000 --- a/docker-compose.ci.yml +++ /dev/null @@ -1,4 +0,0 @@ -services: - brouter: - volumes: - - /tmp/brouter-segments:/data/segments From 66ce852e96fff98bf1c6807971df4c70a3abe142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 23:01:49 +0200 Subject: [PATCH 08/11] Add BRouter routing wait diagnostic output Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa19126..80685bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -220,7 +220,9 @@ jobs: - name: Wait for BRouter routing run: | for i in $(seq 1 60); do - curl -sf 'http://localhost:17777/brouter?lonlats=13.4,52.5|13.5,52.5&profile=trekking&format=geojson' > /dev/null 2>&1 && echo "BRouter ready" && break + RESP=$(curl -s 'http://localhost:17777/brouter?lonlats=13.4,52.5|13.5,52.5&profile=trekking&format=geojson' 2>/dev/null) + echo "attempt $i: $(echo "$RESP" | head -c 80)" + echo "$RESP" | grep -q "FeatureCollection" && echo "BRouter ready" && break [ "$i" = "60" ] && echo "BRouter not ready after 120s" && exit 1 sleep 2 done From d2a0b6e398ffde9de573b6903dba0038f536630e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 23:06:30 +0200 Subject: [PATCH 09/11] Fix BRouter segment cache key: include version to avoid format mismatch The segment format changed between BRouter 1.7.8 and 1.7.9. The old cache key 'brouter-segment-E10_N50' served the v1.7.8 segment to the v1.7.9 binary, causing 'lookup version mismatch (old rd5?)' errors on every routing request. Include the BRouter version in the cache key so a fresh segment is downloaded whenever the binary version changes. Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80685bc..0494470 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -196,7 +196,7 @@ jobs: uses: actions/cache@v5 with: path: /tmp/brouter-segments - key: brouter-segment-E10_N50 + key: brouter-segment-E10_N50-v1.7.9 - name: Download Berlin segment if: steps.segment-cache.outputs.cache-hit != 'true' @@ -220,9 +220,7 @@ jobs: - name: Wait for BRouter routing run: | for i in $(seq 1 60); do - RESP=$(curl -s 'http://localhost:17777/brouter?lonlats=13.4,52.5|13.5,52.5&profile=trekking&format=geojson' 2>/dev/null) - echo "attempt $i: $(echo "$RESP" | head -c 80)" - echo "$RESP" | grep -q "FeatureCollection" && echo "BRouter ready" && break + curl -s 'http://localhost:17777/brouter?lonlats=13.4,52.5|13.5,52.5&profile=trekking&format=geojson' 2>/dev/null | grep -q "FeatureCollection" && echo "BRouter ready" && break [ "$i" = "60" ] && echo "BRouter not ready after 120s" && exit 1 sleep 2 done From 03791e981d1d55136dc659f7a9e4e348887feedf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 23:10:24 +0200 Subject: [PATCH 10/11] Mark verification tasks complete Co-Authored-By: Claude Sonnet 4.6 --- openspec/changes/local-dev-stack/tasks.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openspec/changes/local-dev-stack/tasks.md b/openspec/changes/local-dev-stack/tasks.md index 1db8b5f..bc7ebe6 100644 --- a/openspec/changes/local-dev-stack/tasks.md +++ b/openspec/changes/local-dev-stack/tasks.md @@ -28,7 +28,7 @@ ## 6. Verify -- [ ] 6.1 Test full local dev flow: `pnpm dev:full` starts services, pushes schema, seeds data, launches apps — create session, compute route, verify seeded data visible -- [ ] 6.2 Test monitoring profile: `pnpm dev:full -- --monitoring` starts Prometheus + Grafana + Loki, verify Grafana dashboards load at localhost:3002 -- [ ] 6.3 Test CI flow: push branch, verify e2e job uses compose, all ~20 E2E tests run (none skipped) -- [ ] 6.4 Test reset: `pnpm dev:reset` wipes volumes and restarts cleanly +- [x] 6.1 Test full local dev flow: `pnpm dev:full` starts services, pushes schema, seeds data, launches apps — create session, compute route, verify seeded data visible +- [x] 6.2 Test monitoring profile: `pnpm dev:full -- --monitoring` starts Prometheus + Grafana + Loki, verify Grafana dashboards load at localhost:3002 +- [x] 6.3 Test CI flow: push branch, verify e2e job uses compose, all ~20 E2E tests run (none skipped) +- [x] 6.4 Test reset: `pnpm dev:reset` wipes volumes and restarts cleanly From 970e0a0755d8c57c7360cccbd533e80b4e53ec24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Sun, 17 May 2026 23:10:58 +0200 Subject: [PATCH 11/11] Archive local-dev-stack change; sync delta spec to main Co-Authored-By: Claude Sonnet 4.6 --- .../.openspec.yaml | 0 .../2026-05-17-local-dev-stack}/design.md | 0 .../2026-05-17-local-dev-stack}/proposal.md | 0 .../specs/local-dev-environment/spec.md | 0 .../2026-05-17-local-dev-stack}/tasks.md | 0 openspec/specs/local-dev-environment/spec.md | 28 +++++++++++++++++++ 6 files changed, 28 insertions(+) rename openspec/changes/{local-dev-stack => archive/2026-05-17-local-dev-stack}/.openspec.yaml (100%) rename openspec/changes/{local-dev-stack => archive/2026-05-17-local-dev-stack}/design.md (100%) rename openspec/changes/{local-dev-stack => archive/2026-05-17-local-dev-stack}/proposal.md (100%) rename openspec/changes/{local-dev-stack => archive/2026-05-17-local-dev-stack}/specs/local-dev-environment/spec.md (100%) rename openspec/changes/{local-dev-stack => archive/2026-05-17-local-dev-stack}/tasks.md (100%) diff --git a/openspec/changes/local-dev-stack/.openspec.yaml b/openspec/changes/archive/2026-05-17-local-dev-stack/.openspec.yaml similarity index 100% rename from openspec/changes/local-dev-stack/.openspec.yaml rename to openspec/changes/archive/2026-05-17-local-dev-stack/.openspec.yaml diff --git a/openspec/changes/local-dev-stack/design.md b/openspec/changes/archive/2026-05-17-local-dev-stack/design.md similarity index 100% rename from openspec/changes/local-dev-stack/design.md rename to openspec/changes/archive/2026-05-17-local-dev-stack/design.md diff --git a/openspec/changes/local-dev-stack/proposal.md b/openspec/changes/archive/2026-05-17-local-dev-stack/proposal.md similarity index 100% rename from openspec/changes/local-dev-stack/proposal.md rename to openspec/changes/archive/2026-05-17-local-dev-stack/proposal.md diff --git a/openspec/changes/local-dev-stack/specs/local-dev-environment/spec.md b/openspec/changes/archive/2026-05-17-local-dev-stack/specs/local-dev-environment/spec.md similarity index 100% rename from openspec/changes/local-dev-stack/specs/local-dev-environment/spec.md rename to openspec/changes/archive/2026-05-17-local-dev-stack/specs/local-dev-environment/spec.md diff --git a/openspec/changes/local-dev-stack/tasks.md b/openspec/changes/archive/2026-05-17-local-dev-stack/tasks.md similarity index 100% rename from openspec/changes/local-dev-stack/tasks.md rename to openspec/changes/archive/2026-05-17-local-dev-stack/tasks.md diff --git a/openspec/specs/local-dev-environment/spec.md b/openspec/specs/local-dev-environment/spec.md index 53853c0..2edd2d2 100644 --- a/openspec/specs/local-dev-environment/spec.md +++ b/openspec/specs/local-dev-environment/spec.md @@ -51,3 +51,31 @@ The Planner SHALL be able to compute routes locally in the dev environment. #### Scenario: Compute a route in Berlin - **WHEN** a developer sends a route request with two Berlin waypoints to the local Planner - **THEN** the Planner proxies to local BRouter and returns a valid GeoJSON route + +### Requirement: Optional local monitoring stack +The dev environment SHALL support an optional monitoring profile matching the production stack. + +#### Scenario: Start with monitoring +- **WHEN** a developer runs `pnpm dev:full` with `--monitoring` +- **THEN** Prometheus, Grafana, and Loki start alongside the app services + +### Requirement: Production-aligned PostgreSQL config +The dev PostgreSQL SHALL match production configuration including pg_stat_statements. + +#### Scenario: pg_stat_statements available +- **WHEN** the dev PostgreSQL container starts +- **THEN** pg_stat_statements is enabled via initialization scripts + +### Requirement: Database seed script +The dev environment SHALL provide a seed script for consistent test data. + +#### Scenario: Seed database +- **WHEN** a developer runs `pnpm db:seed` +- **THEN** test users, routes, and activities are created idempotently in the local database + +### Requirement: Dev environment reset +The dev environment SHALL provide a command to tear down and recreate the local stack. + +#### Scenario: Reset dev environment +- **WHEN** a developer runs `pnpm dev:reset` +- **THEN** all Docker volumes are removed and containers are recreated