- Disable Sentry Vite plugin telemetry (telemetry: false)
- Pass SENTRY_RELEASE as build-arg (not secret) since it's just the git sha
- Keep only SENTRY_AUTH_TOKEN as Docker secret
The release version was showing as *** because Docker secrets get masked
by GitHub Actions. Git sha is not sensitive — safe as a build arg.
The sourcemap warning from react-router is a known upstream issue (ships
without sourcemaps) and is harmless.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Approve @sentry/cli and esbuild build scripts so postinstall runs
- Use --mount=type=secret instead of ARG for SENTRY_AUTH_TOKEN
(fixes Docker warning about secrets in build args)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The decrypted secrets.app.env contains values with angle brackets
(SMTP_FROM) that bash interprets as redirects. The build job only
needs SENTRY_AUTH_TOKEN, so extract it with grep instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Write decrypted env files to infrastructure/ instead of /tmp/ so
strip_components: 1 correctly places them on the server.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Secrets:
- Add .sops.yaml with age encryption config
- Add encrypted secrets.app.env (app secrets) and secrets.infra.env (Grafana OAuth)
- CD decrypts at deploy time with AGE_SECRET_KEY — all other secrets
move out of GitHub Actions into version-controlled encrypted files
Split CD:
- cd-apps.yml: triggered by apps/packages changes, builds Docker images, deploys apps
- cd-infra.yml: triggered by infrastructure/ changes, copies configs, restarts services
- Remove monolithic cd.yml
Grafana auth:
- GitHub OAuth (trails-cool org), disable login form
- Remove Caddy basic_auth block and all GRAFANA_* env vars/secrets
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GF_SECURITY_ADMIN_PASSWORD only works on first boot. Reset the admin
password via grafana-cli after each deploy to stay in sync with secrets.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add prometheus.yml, loki-config.yml, and grafana provisioning/dashboards
to the SCP step so they're copied to the server on each deploy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pass GRAFANA_USER, GRAFANA_PASSWORD, and GRAFANA_PASSWORD_HASH
from GitHub Actions secrets to the deploy script.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The upstream gitleaks/gitleaks-action@v2 errors on merge_group events.
Use our fork which includes https://github.com/gitleaks/gitleaks-action/pull/186.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The automerge GitHub Action (pascalgn/automerge-action) is removed in
favor of GitHub's built-in merge queue, which is now enabled on the
main branch ruleset.
- Delete automerge.yml workflow
- Add merge_group trigger to CI so it runs on queue candidates
- Remove workflow_run trigger for Automerge from CD
- Update CLAUDE.md to document gh pr merge --merge-queue
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The CD workflow exported SENTRY_RELEASE but not the SMTP env vars,
so docker-compose substituted them as empty strings and emails
silently failed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Gitleaks needs explicit contents:read and pull-requests:read permissions
to scan dependabot PR commits. Without them, the GitHub API returns 403.
Ref: https://github.com/gitleaks/gitleaks/issues/1343#issuecomment-2690243151
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 38GB disk filled up with 36.5GB of old Docker images from repeated
deploys. Add docker image prune -af after each deploy to prevent this.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Client: VITE_SENTRY_ENVIRONMENT=ci baked in during CI build, disables
Sentry and sets 0 sample rates. Server: checks process.env.CI to
disable and tag as "ci" environment.
Production remains enabled with "production" environment tag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- @sentry/vite-plugin uploads source maps during Docker build
- Release tagged with git SHA (SENTRY_RELEASE) on both client and server
- Environment set to production/development for filtering
- CD passes SENTRY_AUTH_TOKEN + SENTRY_RELEASE as Docker build args
- docker-compose passes SENTRY_RELEASE to runtime containers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In CI, Playwright now starts apps with production commands (react-router-
serve, node --experimental-strip-types) instead of dev servers. This
catches runtime issues like missing exports and import resolution errors
before deploy. Locally, dev servers are still used for fast iteration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Planner crashed because Node.js couldn't handle .ts imports — add
--experimental-strip-types to the CMD. Journal crashed because the
database had no schemas — add drizzle-kit push to the deploy script.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GITHUB_TOKEN merges (from automerge action) don't trigger push
events on other workflows. Added:
- workflow_run trigger after Automerge completes
- workflow_dispatch for manual gh workflow run cd.yml
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dockerfiles:
- Add db package.json to deps stage
- Copy full node_modules (including app-level) to runtime
- Copy packages/ dir for workspace imports at runtime
CD workflow:
- docker/login-action v3 → v4
- docker/build-push-action v6 → v7
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CD workflow:
- Uncommented deploy step
- Copy docker-compose.yml + Caddyfile to server via SCP
- Login to ghcr.io with DEPLOY_GHCR_TOKEN (classic PAT, read:packages)
- Download Germany BRouter segments on first deploy (~750MB)
- Pull images, restart services, verify
Infrastructure:
- BRouter segments as bind mount (./segments) not Docker volume
- Added ORIGIN, PLANNER_URL, SESSION_SECRET, NODE_ENV to Journal
- Added NODE_ENV to Planner
- Disabled Garage for now (no media storage yet)
Required secrets: DEPLOY_HOST, DEPLOY_SSH_KEY, DEPLOY_GHCR_TOKEN
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Playwright JSON has file → describe → specs nesting.
Use stats.expected/unexpected for counts, iterate nested suites.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generates a markdown summary table in GitHub Actions showing
passed/failed tests with timing per test. Uses the JSON reporter
to output results, then a Node script to format as markdown
and append to $GITHUB_STEP_SUMMARY.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
check_suite and status events don't reliably trigger for workflows
on the same commit (GitHub prevents infinite loops). Replaced with
workflow_run on CI completion, which fires after all CI jobs finish.
Added comments explaining why each trigger group exists.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pg_isready reports true before PostGIS extension is initialized.
Added a second wait loop that queries PostGIS_Version() to ensure
the extension is fully loaded before running drizzle-kit push.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace service container with manual docker run + cached image tar.
Service containers pull the image before steps run (uncacheable).
Manual approach: cache the image as a tar, load on cache hit.
PostGIS image is ~400MB — first run pulls and saves, subsequent
runs load from cache in ~2s instead of ~19s.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Playwright browser download (25s) is now cached by pnpm-lock hash.
On cache hit, only install system deps (fast). On miss, full install.
Current E2E timings (first run):
- Postgres container: 19s
- BRouter JAR: cached
- Berlin segment: 6s (will be cached next run)
- Playwright install: 25s → cached after first run
- BRouter startup: 9s
- Tests: 27s
Expected after caching: ~60s total (down from ~107s).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI now runs the full stack for E2E tests:
- PostgreSQL as a service container + schema push via drizzle
- BRouter JAR + Berlin segment (E10_N50) with caching
- Both cached across runs (BRouter JAR + segment)
Removed all skip/workaround logic from tests — all 20 E2E tests
now run in CI with real services.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Integration test DB check now hits Planner API (not Journal)
so it correctly skips when no PostgreSQL in CI
- Playwright uploads HTML report artifact on all runs (not just failures)
- Install Chromium with --with-deps for CI compatibility
- Report retained for 30 days
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use the full set of triggers from pascalgn/automerge-action docs:
pull_request (labeled, synchronize, opened, etc), pull_request_review,
check_suite, and status events.
Replaces workflow_run trigger which wasn't firing reliably after
the label was added before CI completed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SSR fix:
- ClientOnly wrapper prevents Leaflet/Yjs from running during SSR
- SessionView lazy-loaded to avoid browser API usage on server
Automerge fix:
- Set MERGE_READY_STATE to 'clean' only (was including 'unstable')
- Prevents merging before all CI checks complete
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changed trigger from check_suite to workflow_run on CI workflow.
This ensures all jobs (typecheck, lint, test, build, e2e) pass
before auto-merging, not just the first check to complete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pascalgn/automerge-action@v0.16.4 hasn't released a Node 24 build yet.
Use FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 env var to suppress the warning.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Squash merges caused orphaned commits when pushing after auto-merge.
Regular merges preserve full commit history and avoid this issue.
- Disabled squash and rebase merge on repo settings
- Updated automerge action: MERGE_METHOD squash → merge
- Updated CLAUDE.md workflow docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PRs labeled 'automerge' will squash-merge when all checks pass.
Uses pascalgn/automerge-action. No branch protection needed.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Terraform:
- Hetzner CX22 server with Docker, firewall (80/443/22)
- SSH key and output for server IP
Docker Compose:
- Caddy reverse proxy (auto HTTPS)
- Journal and Planner app containers
- BRouter routing engine container
- PostgreSQL + PostGIS
- Garage S3 storage
BRouter:
- Dockerfile based on eclipse-temurin:11-jre
- Segment download script for Germany (4 tiles, ~750MB)
CD Pipeline:
- Build & push Docker images to ghcr.io on main push
- Deploy step commented out (enable when server provisioned)
Scripts:
- PostgreSQL daily backup with 14-day retention
- Dockerfiles for both apps (multi-stage builds)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Actions runtime and project node-version both set to 24.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 opts in early.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CI pipeline with parallel jobs for fast feedback:
- typecheck, lint, unit tests, build run in parallel
- e2e tests run after build
- Playwright artifacts uploaded on failure
ESLint wired into both apps with sensible defaults:
- underscore-prefixed unused vars allowed
- root package.json marked as type: module
Architecture updated: Caddy as reverse proxy with auto HTTPS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>