c2e78cb256
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: .
138 lines
5.2 KiB
Markdown
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.
|