The scaffold currently expects four runtime roles:
-`faceai`: public HTTP service on port `3001`, serving the built Vue app and the authenticated API
-`processor`: background matcher runner consuming BullMQ jobs from Redis and executing the Linux `face_matcher` binary
-`redis`: short-lived queue and search-state store
-`legacy-php`: local-only PHP Apache simulator for exercising the real bridge files under `www/`
For hosted deployment, the long-lived application topology is `faceai` + `processor` + `redis`. The PHP simulator stays local-only and the real legacy site remains on its existing stack.
The `processor` service is built from `docker/processor.Dockerfile` using the repository root as Docker build context. That image copies only the checked-in Unix `face_matcher` into the image, so the matcher is baked into the processor runtime without bringing along the other Unix or Windows binaries.
-`faceai/logs/backend.log` for backend startup and API-side failures
-`faceai/logs/processor.log` for worker startup, queue processing, and uncaught processor errors
-`faceai/logs/searches/<searchId>/worker.log` for the per-search processor trace
-`faceai/logs/searches/<searchId>/matcher.log` for the native `face_matcher` output
This keeps the useful processor diagnostics outside the Docker-managed runtime volume so they survive container rebuilds and can be inspected directly from the workspace.
Because the service entrypoints now mirror output instead of redirecting it away, the same startup and runtime messages are also visible through `docker logs regalami-faceai`, `docker logs regalami-faceai-processor`, and Portainer's container log viewer.
The current bundled Linux `face_matcher` binary is a PyInstaller build that requires `GLIBC_2.38` or newer and the `libxcb.so.1` runtime library. The checked-in local processor image satisfies that requirement.
That page simulates the legacy race page, loads the original race-page JavaScript from `www/_js/rus-ecom-240621.js`, lets the script replace the visible `tipoPuntoFoto` selector with the new `Face ID` button, and launches the real PHP handoff bridge at `www/faceai_handoff.php`.
The `faceai/` workspace now also includes a separate Playwright project for the live site. It is isolated from the Docker-backed simulator suite and is intended to verify that production login still works and that a real race page loads correctly after authentication.
Set these environment variables before running it:
- opens the real FaceAI app and asserts that the legacy header stylesheets load from the live legacy site without injecting cross-origin Font Awesome assets
- uploads the supplied portrait image, waits for the search to complete, and requires a redirect back to the legacy result page with rendered results
### Processor Troubleshooting
If the processor logs show an error like `spawn /opt/face-recognition/face_matcher ENOENT`, the problem is not the upload flow itself. It means the running processor cannot see the matcher binary at the configured `FACEAI_MATCHER_BINARY` path.
With the current checked-in Dockerfiles, only the Unix `face_matcher` is copied into the processor image from the repository source tree during `docker build`. The runtime container no longer needs a host bind mount for `/opt/face-recognition`.
Published images now get that binary because the Forgejo container workflow builds a dedicated processor image from the repository root, which lets `faceai/docker/processor.Dockerfile` copy:
If a running processor still reports `ENOENT`, the deployed image was built before this change or the build did not include the checked-in matcher directory.
If you only want to iterate on the app without the PHP simulator, you can still run the public site and the processor separately. The queue-backed flow now requires Redis and the processor, so `npm run dev` alone is no longer the full stack.
The checked-in `docker-compose.yml` is for local integration testing because it also includes the PHP simulator and local bind mounts. For hosted deployment, keep the same three-service application topology but remove `legacy-php` and replace the local mounts with the real production paths on the host.
If that shared image also embeds or mounts the current Linux `face_matcher` build, make sure the base OS provides `GLIBC_2.38` or newer and includes `libxcb1`. A Debian Trixie-based image with that package installed satisfies the requirement; a Bookworm-based image does not.
This pattern assumes a reverse proxy on the host publishes `https://ai.regalamiunsorriso.it` and forwards to `127.0.0.1:3001`. The processor is internal-only and does not expose any public port.
The NAS-backed dataset bind mount stays read-only in both containers. That keeps the application aligned with the local Compose contract, where both services can inspect the same PKL tree but neither service can modify the underlying race data.
| `FACEAI_FRONTEND_URL` | yes | `https://ai.regalamiunsorriso.it` | URL used when the legacy bridge redirects into the app |
| `FACEAI_PUBLIC_BASE_URL` | yes | `https://ai.regalamiunsorriso.it` | public base URL used for local links and return flow generation |
| `FACEAI_LEGACY_RETURN_URL` | yes | `https://www.regalamiunsorriso.it/faceai_return.php` | legacy endpoint that receives the signed FaceAI result handoff |
| `FACEAI_LEGACY_HOME_URL` | recommended | `https://www.regalamiunsorriso.it/` | fallback destination used when FaceAI has no valid session and needs to return the browser to the legacy site |
The mounted PKL root is expected to use this structure:
```text
/data/pkl/
2026/
04.APRILE/
PISA/
any-file-name.pkl
```
The public FaceAI site mounts the same path read-only so it can check availability during session bootstrap and refuse uploads immediately when the race has no `.pkl` data.
Do not enable `FACEAI_ENABLE_LOCAL_LEGACY_STATIC` in production. That mode exists only for local simulator flows.
### Legacy-Side Configuration That Must Match
The deployment will not work correctly unless the legacy bridge is configured consistently.
The legacy site must:
- redirect users into `FACEAI_FRONTEND_URL` with a valid signed handoff token
- use the same `FACEAI_SHARED_SECRET` as the FaceAI deployment
- expose the configured `FACEAI_LEGACY_RETURN_URL`
- validate the signed return token and fetch the result payload from FaceAI
The shared secret is the trust boundary between the legacy site and FaceAI. Treat it like any other production secret and inject it through the platform secret store, not through source control.
So the Compose deployment is appropriate for hosted integration and controlled production-like rollout, but not yet for the final hardened architecture described in the integration plan.
If you want FaceAI to return through the new PHP bridge prepared under `www`, point `FACEAI_LEGACY_RETURN_URL` to that endpoint instead, for example `http://localhost/faceai_return.php` or the equivalent URL in your local PHP setup.
In the provided Docker Compose stack, that wiring is already done with:
The log wiring is also already done in the checked-in Compose file with a host bind mount for `./logs:/data/logs`, so both the backend and the processor write persistent diagnostics into the workspace while also remaining visible through Docker and Portainer container logs.
The Compose contract now also includes an HTTP healthcheck on the public FaceAI service and a Redis readiness check. That makes `docker compose ps` meaningful during rollout: `faceai` only becomes healthy after `GET /health` returns `{"ok":true}`, and both the public site and the processor wait for Redis readiness before their own startup sequence begins.