Getting started with Airline
Install Openfront Airline, configure PostgreSQL and storage, apply migrations, create the first operator, run onboarding, and deploy.
The hosted app is at airline.openship.org. The storefront is at / and the operator dashboard at /dashboard.
Requirements
- Node.js 20.9 or later (
package.jsonengines). The README recommends Node.js 22. - npm.
- A PostgreSQL database you are allowed to migrate.
- S3-compatible object storage for production. Development falls back to local defaults.
- An SMTP server if you want password resets and booking access recovery emails.
Clone and install
git clone https://github.com/openshiporg/openfront-airline.git
cd openfront-airline
npm installConfigure .env
Copy the example file and fill in real values:
cp .env.example .envThe minimum for local development:
DATABASE_URL=postgresql://user:password@localhost:5432/openfront_airline
SESSION_SECRET=replace-with-at-least-32-random-charactersSESSION_SECRET must be at least 32 characters. Generate one with openssl rand -hex 32. Never commit .env.
Object storage. All five are required in production. Outside production they default to a local MinIO-style setup:
S3_BUCKET_NAME=
S3_REGION=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_ENDPOINT=https://objects.exampleOther variables the code reads:
| Variable | Purpose |
|---|---|
APP_BASE_URL | Trusted public origin used for internal GraphQL calls. No path, credentials, query, or fragment. |
PUBLIC_APP_URL | Public origin. In production it must match APP_BASE_URL and SMTP_STORE_LINK. |
STOREFRONT_CARRIER_CODE | Two- or three-character carrier code the storefront sells for. If unset, the first carrier is used. |
TRAVEL_DOCUMENT_TOKENIZATION_KEY | HMAC key (32+ characters) for travel-document tokens. Falls back to a key derived from SESSION_SECRET outside production. |
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM | Outgoing mail. Booking access recovery is off unless SMTP_HOST and SMTP_FROM are set. |
SMTP_STORE_LINK | Public storefront URL used in emails and Stripe return links. |
PUBLIC_SIGNUPS_ALLOWED | Set to true to open /dashboard/signup. Off by default. |
TRUSTED_PROXY_IPS | Comma-separated proxy addresses trusted for client IPs in rate limiting. |
OUTBOUND_WEBHOOK_HOSTS | Comma-separated hosts allowed for outbound webhook endpoints. |
ALERT_WEBHOOK_URL | Where the worker posts alerts when dead-lettered outbox events appear. |
REQUIRED_WORKER_ID, OUTBOX_WORKER_ID | Worker identity. /api/health reports whether this worker's heartbeat is fresh. |
BACKUP_RETENTION_DAYS, BACKUP_POLICY_VERIFICATION_ID | Required by the production runtime check. Retention must be at least 30. |
ALLOW_INCOMPLETE_LAUNCH_SETUP | In production, true keeps public sales commands closed while you finish setup. |
AI_ASSISTANT_ENABLED, OPENROUTER_API_KEY, OPENROUTER_MODEL, OPENROUTER_MAX_TOKENS | Optional dashboard AI chat and MCP endpoint. MCP returns 404 in production unless enabled. |
INITIAL_CARRIER_NAME, INITIAL_CARRIER_LEGAL_NAME, INITIAL_CARRIER_CODE, INITIAL_CURRENCY_CODE, INITIAL_TIMEZONE | Optional values for the carrier created with the first user. Defaults: Avante Air, AV, USD, America/New_York. |
AIRLINE_WEBHOOK_SECRET_<NAME> | Webhook signing secrets, referenced by name from provider or connection configuration. |
Apply migrations
The repository has checked-in Prisma migrations under migrations/. Review them, then apply:
npm run migratenpm run dev builds the Keystone schema, runs npm run migrate, and then starts Next.js. It migrates whatever database DATABASE_URL points to.
Start development:
npm run devThe storefront is at /, the dashboard at /dashboard, and GraphQL at /api/graphql.
To create a new migration after a model change, use npm run migrate:gen against a disposable database. It runs a migration-safety check before applying.
Create the first user
On an empty database, open /dashboard/init. Enter a name, email, and a password of at least 10 characters. This creates the user, a carrier (from the INITIAL_* values or the Avante Air defaults), and an "Initial Carrier Administrator" role with every permission. Direct sales start turned off. Once any user exists, /dashboard/init is closed.
Two script alternatives exist:
npm run bootstrap:productioncreates the first admin and carrier fromBOOTSTRAP_ADMIN_EMAIL,BOOTSTRAP_ADMIN_PASSWORD(16+ characters),BOOTSTRAP_CARRIER_NAME,BOOTSTRAP_CARRIER_CODE, and optionalBOOTSTRAP_CURRENCY_CODEandBOOTSTRAP_TIMEZONE. It requiresBOOTSTRAP_CONFIRM=create-first-airline-adminand empty user, carrier, and role tables.npm run seed:runtimeseeds a local Avante Air carrier, a JFK-BOS flight, inventory, seats, a passenger, and the settlement boundary. It refuses to run in production and only targets loopback databases namedruntime_airline*. It needsALLOW_RUNTIME_SEED=1andRUNTIME_ADMIN_PASSWORD(16+ characters) when it creates the admin.
Onboarding
After sign-in, the dashboard shows an onboarding dialog with two templates:
- Full network. Avante Air from New York JFK with four nonstop city pairs (Boston, Chicago, Miami, Los Angeles), both directions, daily for 14 days. It also sets storefront branding and contact details and turns on direct sales. Each step calls the same commands an operator would use (
updateCarrierStorefrontSettings,completeCarrierOnboarding,provisionDatedFlight), so a rerun only fills gaps. - Custom launch. Enter your carrier identity and first sellable flight: carrier name and code, currency (USD, EUR, GBP, CAD, or AUD), timezone, origin and destination, flight number, date, times, capacity, and fare.
The same form is at /dashboard/onboarding. completeCarrierOnboarding creates or reuses airports from a built-in catalog of 45 airports, a route, a schedule, an Economy cabin, a Standard fare brand with one checked bag, a dated flight with inventory and seats, a station assignment for you at the origin, a manual payment provider, and a local settlement-import connection.
It then records six steps: carrier identity, route and catalog, dated service, station authorization, settlement evidence, and launch review. Launch review completes only when:
- the carrier is active with direct sales on,
- the dated flight and bucket are sellable,
- your station assignment allows check-in, bags, and boarding,
- at least two users have
canManagePayments, and - an active settlement-import machine credential with
settlements:writeexists (issued from the onboarding form).
To actually sell, the storefront also needs a homepage title, description, and brand colour from Storefront settings.
Run checks
npm run lint
npm run typecheck
npm testnpm run test:integration runs database tests. Point it only at a disposable database.