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.jsonenginesis>=20.0.0; the README asks for 20.9 or later for Next.js 16). - npm. The repository ships
package-lock.jsonandnpm-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 .envEnvironment
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.comGenerate each secret separately, for example with openssl rand -hex 32.
What the code enforces:
SESSION_SECRETmust be at least 32 characters in every environment.- Outside production, missing
DATABASE_URL,SESSION_SECRET,HOTEL_DATA_ENCRYPTION_KEY, andHOTEL_QUOTE_SECRETfall back to local development values. - In production (
NODE_ENV=production),features/keystone/lib/productionConfig.tsrequires a valid PostgreSQLDATABASE_URL, and requiresSESSION_SECRET,HOTEL_DATA_ENCRYPTION_KEY, andHOTEL_QUOTE_SECRETto be 32 or more characters with no placeholder words. It requiresNEXT_PUBLIC_SITE_URL(orNEXTAUTH_URL) as a bare HTTPS origin. If both are set they must match. - Keep
HOTEL_DATA_ENCRYPTION_KEYstable. 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-endpointWithout them, room images and storefront media can still use public paths such as /images/hotel/....
Other variables the code reads:
| Variable | Used for |
|---|---|
SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM | Guest and password-reset email. All five must be set. |
NEXTAUTH_URL | Alias for NEXT_PUBLIC_SITE_URL; if both are set they must match |
PASSWORD_RESET_ORIGIN, PASSWORD_RESET_TOKEN_TTL_MINUTES | Password reset links (TTL default 10 minutes, max 1440) |
RESET_ACTION_SECRET | Optional. When unset, a reset secret is derived from SESSION_SECRET. When set in production it must pass the same strength check. |
GUEST_ACCESS_SECRET | Signs the guest access cookie; falls back to SESSION_SECRET, then NEXTAUTH_SECRET |
PUBLIC_SIGNUPS_ALLOWED | true opens dashboard sign-up; default closed |
TRUSTED_PROXY_IPS | Comma-separated proxy addresses whose X-Forwarded-For is trusted for rate limits |
STRIPE_ENABLED, STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET | Fallback 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_ID | Optional HTTP outbox delivery. Set all three or none; a partial set fails. |
HOTEL_OUTBOX_RECEIVER_SECRET, HOTEL_OUTBOX_RECEIVER_CREDENTIAL_KEY_ID | Enables /api/hotel/outbox/receiver |
HOTEL_OUTBOX_WORKER_ID, HOTEL_OUTBOX_INTERVAL_MS, HOTEL_REFUND_WORKER_ID, HOTEL_REFUND_INTERVAL_MS | Worker identity and poll interval (default 5000 ms) |
OPENROUTER_API_KEY, OPENROUTER_MODEL, OPENROUTER_MAX_TOKENS | Dashboard 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 devnpm 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
| Script | Effect |
|---|---|
npm run build | keystone build --no-ui && next build; does not migrate |
npm run migrate | prisma migrate deploy |
npm start | next start |
npm run lint | ESLint |
A typical release:
npm run build
npm run migrate
npm startThe 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.