Files
boarding-pass/README.md
T
bsncubed c2e78cb256 Pull the image from Gitea's registry instead of building locally
docker-compose.yml now references gitea.apointless.space/bsncubed/
boarding-pass:latest by default; building from source is still available
by swapping image: back to build: .
2026-07-04 22:58:05 +10:00

138 lines
5.2 KiB
Markdown

# boarding-pass-pipeline
Photograph a boarding pass → decode its BCBP (IATA Resolution 792) PDF417
barcode → wait until the day after the flight → fetch the historical flight
track from FlightAware's public site → stage it for manual review → notify
via ntfy with Approve/Deny/Investigate actions → on approval, write the
flight (with track) into [AirTrail](https://github.com/johanohly/AirTrail)
via its REST API.
Not part of any other microservice stack. Single container, SQLite for all
local state, no task queue — a background thread handles the daily job.
## Quick start
1. Copy `.env.example` to `.env` and fill in `PUBLIC_BASE_URL`,
`AIRTRAIL_BASE_URL`, `AIRTRAIL_API_KEY`, and `TZ`.
To get an AirTrail API key: log into your AirTrail instance → Account →
API Keys → create a new key. This pipeline authenticates to AirTrail as
that user and writes flights on their behalf.
2. Create the external Docker network shared with NPMplus, if it doesn't
already exist:
```
docker network create proxy
```
(or edit `docker-compose.yml` to point at whatever network your NPMplus
container is already on.)
3. Log in to Gitea's container registry (see "Pulling the pre-built image"
below for the token you need), then pull and start:
```
docker login gitea.apointless.space -u bsncubed
docker compose pull
docker compose up -d
```
To build locally from source instead, change `image:` back to `build: .`
in `docker-compose.yml` and run `docker compose up -d --build`.
4. Add a proxy host in NPMplus pointing at `boarding-pass:8080`, behind
Authelia, **except** for the two paths below (see "Approve/Deny callback
paths").
5. Open the site, go to **Settings**, and configure your ntfy server/topic
(defaults to the public `ntfy.sh` — use a private topic name, or point at
a self-hosted ntfy with an auth token). Send a test notification to
confirm it arrives.
6. Go to **Upload** and photograph a boarding pass to confirm end-to-end
decoding works.
## Approve/Deny callback paths — read this before exposing the service
ntfy's action buttons fire a plain HTTP request from wherever the
notification is opened (phone, desktop) with **no Authelia session**. For
those buttons to work, `/flights/*/approve` and `/flights/*/reject` must
bypass the Authelia `auth_request` check in NPMplus. Add a location block
like this in the proxy host's advanced config:
```nginx
location ~ ^/flights/[0-9]+/(approve|reject) {
# Bypasses Authelia for these two paths only.
proxy_pass http://boarding-pass:8080;
}
```
**This bypass applies to every request on that path, not just ntfy's** —
there is no way to distinguish "Authelia already checked this" from "anyone
on the internet hit this URL directly" once the bypass is in place. Because
of that, these two routes are protected only by a signed, expiring,
per-flight HMAC token (`tokens.py`) baked into the URL — including the
review page's own Approve/Reject buttons, which render a freshly-signed
token on every page load rather than relying on the surrounding page being
behind Authelia. Don't add a `?token=` bypass anywhere else, and don't widen
the nginx match beyond these two paths.
## Pulling the pre-built image
`docker-compose.yml` points `boarding-pass` at
`gitea.apointless.space/bsncubed/boarding-pass:latest` by default rather than
building locally. To pull it you need to be logged in:
```bash
docker login gitea.apointless.space -u bsncubed
```
Gitea's web login goes through Authelia (OIDC), but that's a browser SSO
flow that `docker login` can't do — the registry still authenticates with a
plain Gitea **personal access token**, not your Authelia/account password.
Generate one under Gitea → Settings → Applications with `read:package` scope
(`write:package` too if you'll also be pushing new builds) and use it as the
password.
Pull new versions with:
```bash
docker compose pull
docker compose up -d
```
## Re-running the reference data build
`data/airports.csv` and `data/airlines.csv` are trimmed snapshots of
OurAirports/OpenFlights data, fetched once via `scripts/build_reference_data.py`
and committed. Re-run it if IATA/ICAO mappings go stale:
```
python3 scripts/build_reference_data.py
```
Known corrections in `reference_data.py`'s `CARRIER_ICAO_OVERRIDES` (e.g.
Virgin Australia VA → VOZ, where OpenFlights has a stale `VAU`) take priority
over the CSV and aren't touched by re-running the script.
## Testing the daily job on demand
The daily job normally runs once at `daily_run_time` (Settings page, default
03:00 container time). To trigger it immediately against whatever is queued:
```
docker exec boarding-pass python -c "from scheduler import run_daily_job; run_daily_job()"
```
## Notes
- Uploaded photos are stored under `instance/uploads/`; the SQLite database
is `instance/boarding_pass.db`. Both are in the `instance/` volume — back
that directory up if you care about upload history.
- No OCR fallback: if barcode decoding fails, the flight is queued as
`decode_failed` and every field is editable on the review page instead.
- AirTrail integration only ever calls its documented REST API
(`/api/flight/save`, `/api/flight/list`) — it never touches AirTrail's
Postgres database directly.