Run admin-dashboard with other DB/Mailpit specs after parallel tests.
Stop admin-dashboard from mutating the sent seed order before fulfillment.
Wait longer for backend readiness in the E2E stack.
Co-authored-by: Cursor <cursoragent@cursor.com>
Operators can fix prod admin passwords without email via Byt lösenord;
end users can use forgot-password when SMTP is configured. Local and CI
use Mailpit to capture outbound mail and verify reset links end-to-end.
- Backend: V8 password_reset_tokens, PasswordResetService, EmailService,
POST /api/auth/forgot-password, reset-password, change-password
- Optional testToken in forgot-password response (docker profile only, for E2E)
- Frontend: ForgotPasswordPage, ResetPasswordPage, ChangePasswordPage,
routes, login link, header Byt lösenord
- Mailpit (ghcr.io/axllent/mailpit:v1.28) in docker-compose + e2e stack
- E2E: password-reset.spec.ts + Mailpit API helper tests SMTP delivery
- Separate dev/e2e Docker image names to avoid overwriting bilhej-frontend
- Docs: README email section, production-email-checklist, .env.example
- Unit/integration tests for reset, change password, and Vitest page specs
Co-authored-by: Cursor <cursoragent@cursor.com>
The postgres:16 image declares a VOLUME for /var/lib/postgresql/data.
Docker Compose creates an anonymous volume that persists across CI runs.
When a Flyway migration file is modified, the next run sees a checksum
mismatch because the old migration is already recorded in the schema_history
table in the stale volume.
- Add tmpfs: [/var/lib/postgresql/data] to the postgres service
- This keeps data in RAM only, guaranteeing a completely fresh database
on every E2E run with no persistent state between invocations
Result: eliminates FlywayValidateException caused by migration checksum
mismatches in CI.
The per-job DinD approach failed because Forgejo Runner's service container
DNS resolution does not work when the runner itself uses DinD
(container.docker_host: tcp://dind:2375). The job container could not resolve
the 'dind' service hostname, causing docker compose to fail immediately.
New approach:
- Runner now uses container.docker_host: 'automount' which mounts the host
Docker socket into job containers. The runner runs as root (user: 0:0)
to access /var/run/docker.sock.
- E2E job no longer uses a 'dind' service. docker compose runs directly
against the host Docker daemon inside the job container.
- docker-compose.e2e.yml gets a custom 'e2e' bridge network. All E2E
containers (postgres, backend, frontend, playwright) attach only to this
network, isolating them from other host containers (Nextcloud, Jellyfin,
etc.). They can still reach the internet for vehicle lookup and npm.
Tradeoff: job containers can see other containers via docker ps, but they
are on an isolated network. For a single-user home server, this is the
simplest reliable configuration.
Implement per-job Docker-in-Docker (DinD) for E2E tests, giving each
job a completely isolated Docker daemon and network. This prevents
leakage to the host Docker or other containers.
The previous E2E approach failed because:
1. The Forgejo runner's container.docker_host was not set, causing
the runner itself to try unix:///var/run/docker.sock and crash-loop.
2. The host DinD daemon had isolated networking — job containers
running docker compose could not resolve 'dind' hostname or access
host filesystem bind mounts (e.g. .:/app).
New approach — zero bind mounts, all COPY-based images:
- docker/backend.e2e.Dockerfile: multi-stage build from repo root.
Copies gradlew + settings.gradle + backend/build.gradle to download
dependencies in a cacheable layer, then copies backend/src and builds
the bootJar. Runs the JAR directly on startup.
- docker/frontend.e2e.Dockerfile: multi-stage Node build → nginx.
Reuses existing docker/nginx.conf for /api proxy to backend service.
No volume mounts, fully self-contained.
- docker/playwright.e2e.Dockerfile: extends official Playwright image.
Installs deps from package-lock.json, copies e2e tests + config.
- docker-compose.e2e.yml: zero bind mounts. Services depend on each
other in order: postgres (healthy) → backend → frontend → playwright.
Playwright waits for backend and frontend via curl loops before
running tests.
- .forgejo/workflows/ci.yml: E2E job adds a 'dind' service container
(docker:28-dind, privileged, no TLS). The job sets DOCKER_HOST to
tcp://dind:2375 so the docker CLI inside the job talks to the
per-job DinD daemon. The compose file is docker-compose.e2e.yml.
- Runner fix on tocke: added container.docker_host: 'tcp://dind:2375'
to runner-config.yaml so the runner's own Docker client connects to
the host DinD container, stopping the crash loop.
Key properties:
- Network isolation: each E2E job gets its own DinD with its own
container network. No host container visibility.
- No bind mount leakage: all images use COPY instead of volume mounts.
The per-job DinD has its own filesystem and can't see host paths.
- Deterministic: builds start from clean state every time. Image cache
exists only within the per-job DinD lifetime.
- Lint-and-test job is untouched and remains green.