Getting started with Hotel

Clone the source, configure PostgreSQL, secrets, and storage, apply migrations, create the first admin, run onboarding, and deploy.

Requirements

  • Node.js 20 or later (package.json engines is >=20.0.0; the README asks for 20.9 or later for Next.js 16).
  • npm. The repository ships package-lock.json and npm-shrinkwrap.json.
  • A PostgreSQL database you are allowed to migrate.
  • Optional: S3-compatible object storage for uploaded room images, and SMTP for guest email.

Hosted app

The hosted app is at hotel.openship.org, with the dashboard at /dashboard. It shows the property The Alder House.

Install

git clone https://github.com/openshiporg/openfront-hotel.git
cd openfront-hotel
npm ci
cp .env.example .env

Environment

The checked-in .env.example is a production template. It sets NODE_ENV=production and uses placeholder values that production startup rejects. For local work, remove NODE_ENV=production and the placeholder lines you do not need.

Required:

DATABASE_URL=postgresql://user:password@127.0.0.1:5432/openfront_hotel
SESSION_SECRET=replace-with-at-least-32-random-characters
HOTEL_DATA_ENCRYPTION_KEY=replace-with-a-different-32-plus-character-secret
HOTEL_QUOTE_SECRET=replace-with-a-third-32-plus-character-secret
NEXT_PUBLIC_SITE_URL=https://hotel.example.com

Generate each secret separately, for example with openssl rand -hex 32.

What the code enforces:

  • SESSION_SECRET must be at least 32 characters in every environment.
  • Outside production, missing DATABASE_URL, SESSION_SECRET, HOTEL_DATA_ENCRYPTION_KEY, and HOTEL_QUOTE_SECRET fall back to local development values.
  • In production (NODE_ENV=production), features/keystone/lib/productionConfig.ts requires a valid PostgreSQL DATABASE_URL, and requires SESSION_SECRET, HOTEL_DATA_ENCRYPTION_KEY, and HOTEL_QUOTE_SECRET to be 32 or more characters with no placeholder words. It requires NEXT_PUBLIC_SITE_URL (or NEXTAUTH_URL) as a bare HTTPS origin. If both are set they must match.
  • Keep HOTEL_DATA_ENCRYPTION_KEY stable. It encrypts stored payment-provider and channel credentials.

Object storage. Uploads are blocked until all five are set:

S3_BUCKET_NAME=your-bucket
S3_REGION=your-region
S3_ACCESS_KEY_ID=your-access-key-id
S3_SECRET_ACCESS_KEY=your-secret-access-key
S3_ENDPOINT=https://your-s3-compatible-endpoint

Without them, room images and storefront media can still use public paths such as /images/hotel/....

Other variables the code reads:

VariableUsed for
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROMGuest and password-reset email. All five must be set.
NEXTAUTH_URLAlias for NEXT_PUBLIC_SITE_URL; if both are set they must match
PASSWORD_RESET_ORIGIN, PASSWORD_RESET_TOKEN_TTL_MINUTESPassword reset links (TTL default 10 minutes, max 1440)
RESET_ACTION_SECRETOptional. When unset, a reset secret is derived from SESSION_SECRET. When set in production it must pass the same strength check.
GUEST_ACCESS_SECRETSigns the guest access cookie; falls back to SESSION_SECRET, then NEXTAUTH_SECRET
PUBLIC_SIGNUPS_ALLOWEDtrue opens dashboard sign-up; default closed
TRUSTED_PROXY_IPSComma-separated proxy addresses whose X-Forwarded-For is trusted for rate limits
STRIPE_ENABLED, STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRETFallback Stripe credentials when the Stripe provider record has none stored. Prefer the dashboard.
HOTEL_OUTBOX_DISPATCH_URL, HOTEL_OUTBOX_DISPATCH_SECRET, HOTEL_OUTBOX_DISPATCH_CREDENTIAL_KEY_IDOptional HTTP outbox delivery. Set all three or none; a partial set fails.
HOTEL_OUTBOX_RECEIVER_SECRET, HOTEL_OUTBOX_RECEIVER_CREDENTIAL_KEY_IDEnables /api/hotel/outbox/receiver
HOTEL_OUTBOX_WORKER_ID, HOTEL_OUTBOX_INTERVAL_MS, HOTEL_REFUND_WORKER_ID, HOTEL_REFUND_INTERVAL_MSWorker identity and poll interval (default 5000 ms)
OPENROUTER_API_KEY, OPENROUTER_MODEL, OPENROUTER_MAX_TOKENSDashboard AI chat

.env.example also lists INTERNAL_APP_URL, GRAPHQL_INTERNAL_URL, NEXT_PUBLIC_GRAPHQL_ENDPOINT, and CHANNEL_SYNC_WORKER_ID. The current application code does not read them, so setting them has no effect.

Payment-provider credentials and channel settings belong in the dashboard, not in environment variables. Never commit .env.

Migrations and local run

npm run migrate   # prisma migrate deploy
npm run dev       # keystone build, view-order generation, next dev

npm run dev does not apply migrations. Run npm run migrate first and after pulling new migrations. To change the schema, run npm run migrate:gen against a disposable database; it builds Keystone, regenerates view order, and runs keystone prisma migrate dev.

npm run migrate applies to whatever DATABASE_URL points at.

A migration creates the single HotelBusinessDate row. Onboarding fails without it.

Once running:

  • Storefront: /
  • Staff dashboard: /dashboard
  • Booking lookup: /bookings/lookup
  • GraphQL API: /api/graphql

First user

When no users exist, every page redirects to /dashboard/init. Other auth pages are /dashboard/signin, /dashboard/signin/mfa, /dashboard/reset, /dashboard/signup (closed unless PUBLIC_SIGNUPS_ALLOWED=true), and /dashboard/no-access. Create the first account there. It gets an Admin role with every permission. Then turn on MFA for that account at /dashboard/platform/security.

Onboarding

Open the onboarding dialog from the dashboard and pick a template:

  • Complete Setup (full): The Alder House with 3 room types, 8 rooms, 4 rate plans, guests, bookings, payments, housekeeping, maintenance, loyalty, inventory, and two inactive channels.
  • Basic Setup (minimal): a smaller linked subset of the same data.
  • Custom Setup (custom): paste your own property JSON.

Onboarding only runs on an empty property. It sets the business date to today in the property time zone. After it completes, edit the property at /dashboard/platform/property, then configure Stripe or PayPal at /dashboard/platform/payment-providers. Without an enabled provider, the storefront shows that online payment is unavailable.

Deployment

ScriptEffect
npm run buildkeystone build --no-ui && next build; does not migrate
npm run migrateprisma migrate deploy
npm startnext start
npm run lintESLint

A typical release:

npm run build
npm run migrate
npm start

The included railway.toml builds with keystone build --no-ui && next build and starts with npm run migrate && next start. The README also has a Vercel import button.

Workers (channel sync, outbox, refunds, hold expiry) start inside the Next.js Node process through instrumentation.ts. They need a long-running process. A serverless deploy will not run them reliably. Every process must share the same database, SESSION_SECRET, HOTEL_DATA_ENCRYPTION_KEY, and HOTEL_QUOTE_SECRET. Workers use database leases, so several app instances will not run the same job at once.

Use /api/health for liveness and /api/ready for readiness.

On this page