Files
boarding-pass/README.md
T
bsncubed bd85229e71 Document pulling the pre-built image from Gitea's container registry
Gitea's web UI uses Authelia/OIDC, but docker login needs a plain Gitea
access token since the registry can't do a browser SSO redirect.
2026-07-04 22:50:34 +10:00

5.3 KiB

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 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. Build and start:

    docker compose up -d --build
    

    Or, to use the pre-built image from Gitea's container registry instead of building locally, see "Pulling the pre-built image" below.

  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:

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

Images are pushed to Gitea's container registry at gitea.apointless.space/bsncubed/boarding-pass. To pull instead of building locally:

docker login gitea.apointless.space -u bsncubed
docker pull gitea.apointless.space/bsncubed/boarding-pass:latest

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) and use it as the password.

Then point docker-compose.yml at the image instead of building:

services:
  boarding-pass:
    image: gitea.apointless.space/bsncubed/boarding-pass:latest
    # remove/comment out "build: ." above
    ...

and pull new versions with 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.