Files
NextChair/.env.business.example
T
Stefan Lazic 68a62e02a4
CI / ci (push) Canceled after 0s
Remaining features
Built
No-show flag and block:
Two new numbers under Booking Rules (blank means off).
A symbol appears on the clients list, client page, calendar entries and appointment pages.
Online booking is refused at the block number, and customers are told at the details step.
Staff can still book a blocked customer in, and an owner can reset someone with a two-tap button.
The count resets when they attend, and matches the same email or phone, so a guest can’t dodge it with a new profile.
Email rules: staff and owners get email only for customer cancellations, schedule changes and invitations. Routine events (new booking, reschedule) are push-only. Nobody is told about their own action. A barber reassignment sends one customer email instead of two. Reward expiry sends one warning.
Email redesign: your brand colour, a details card, Add to calendar and View on map links, a preferences link, hidden preview text, and dark mode. Replies now go to the business’s own email.
WhatsApp “due now” queue: a list in time order with a highlighted next one, an owner setting for how many hours ahead (12–72), and a once-a-day push telling each barber how many reminders are waiting.
Monitoring: /api/health now reports whether notifications are actually going out. The worker resets stuck deliveries and alerts the owner on the first setup failure (such as a bad SMTP login). The app emails your operator address about server errors, and the manager pushes that address to each business.
Google scope: now only calendar.app.created. Events go to a calendar the app creates in the staff member’s Google account, called “<Business> bookings”.
2026-10-17 23:10:18 +01:00

130 lines
6.5 KiB
Bash

# One business's config on the shared multi-business VPS setup — see
# stacks/infra/compose.yaml for the one-time setup this depends on
# (creating this business's database on the shared MySQL server) and
# stacks/<slug>/compose.yaml for how this file is used. Copy this to
# `stacks/<slug>/.env` (kept out of git, see .gitignore) per business and
# fill in real values. Nothing here is a real credential. Normally the
# management UI (manager/) generates this file for you — this template is
# for the by-hand setup path.
#
# The app starts and runs a complete first-install flow with ONLY the
# "Required for basic startup" section below filled in — every other
# integration (email, push, Google sign-in, Google Calendar) shows a
# disabled state rather than crashing when its variables are left blank.
# ── Required for basic startup ─────────────────────────────────────────
# Manager-only bookkeeping (shown on the dashboard/edit screen, editable
# there) — the app itself never reads this; its own business name lives
# in the database, set via /install.
BUSINESS_NAME="Example Business"
# The public domain this business is served on — must match the .caddy
# site file's own domain (see stacks/infra/sites/.gitkeep) and that
# domain's real DNS (pointed at the VPS) before Caddy can issue a
# certificate for it.
DOMAIN="example-business.example.com"
# The app reaches MySQL via the shared infra's service hostname
# `next-chair-mysql`, never `localhost` — see stacks/infra/compose.yaml.
# DATABASE_NAME/USER/PASSWORD must match exactly what you created for
# this business on the shared server (see stacks/infra/compose.yaml's
# "CREATE DATABASE ..." step) — this file doesn't create them, it only
# tells the app what to connect as.
DATABASE_HOST="next-chair-mysql"
DATABASE_PORT="3306"
DATABASE_NAME="example_business"
DATABASE_USER="example_business"
DATABASE_PASSWORD="replace-me"
# Generate with: npx auth secret
AUTH_SECRET="replace-me-with-a-32-byte-random-string"
# Auth.js needs to trust the host it's served from when not on localhost:3000.
AUTH_TRUST_HOST="true"
# Explicit, not just inferred from AUTH_TRUST_HOST + request headers —
# header-based detection (Host/X-Forwarded-Host) behaves inconsistently
# across reverse-proxy setups, and a wrong guess sends every post-sign-in
# redirect to the wrong origin. Must match DOMAIN above exactly.
AUTH_URL="https://example-business.example.com"
NEXT_PUBLIC_APP_URL="https://example-business.example.com"
# One-time setup token the installation wizard (/install) requires before
# it will create the first business/administrator — generate with:
# openssl rand -hex 32
INSTALLATION_TOKEN="replace-me"
# Shared secret an external scheduler presents to
# /api/cron/process-notifications and /api/cron/sync-google-reviews —
# generate with: openssl rand -hex 32. Without something actually calling
# those on a schedule, queued notifications (staff invites, reminders,
# etc.) never get sent — see the root README's "Notification delivery"
# section: the Manager's built-in scheduler normally does this (the host
# crontab script is only a fallback).
CRON_SECRET="replace-me"
# Where the app emails unhandled server errors (one email per distinct error per hour, max 20 a day).
# The Manager fills this in from Settings > Monitoring. Blank = no error emails.
OPERATOR_ALERT_EMAIL=""
# ── Super Administrator (optional — see scripts/ensure-super-admin.ts) ──
# Platform-level account with every Owner permission for THIS business.
# Runs automatically on every container start (see docker/entrypoint.sh)
# — filling these in and recreating the container is enough, no manual
# `docker exec` needed. No password — signs in the same OTP way as
# everyone else, via a one-time code emailed to SUPER_ADMIN_EMAIL. Leave
# SUPER_ADMIN_EMAIL blank to skip entirely and use the /install wizard's
# own administrator account instead.
SUPER_ADMIN_EMAIL=""
SUPER_ADMIN_FIRST_NAME=""
SUPER_ADMIN_LAST_NAME=""
# ── Google sign-in (optional — "Continue with Google" on Sign In) ─────
AUTH_GOOGLE_ID=""
AUTH_GOOGLE_SECRET=""
# ── Google Calendar sync (optional — staff-level, see /staff/settings/calendar) ─
# From https://console.cloud.google.com/apis/credentials — a separate
# OAuth client from AUTH_GOOGLE_ID/SECRET above (different scopes).
GOOGLE_CALENDAR_CLIENT_ID=""
GOOGLE_CALENDAR_CLIENT_SECRET=""
GOOGLE_CALENDAR_REDIRECT_URI="https://example-business.example.com/api/staff/calendar/google/callback"
# Encrypts stored refresh tokens at rest — generate with: openssl rand -hex 32
CALENDAR_TOKEN_ENCRYPTION_KEY=""
# ── Media Manager (optional — see src/lib/media/media-storage.ts) ──────
# Max upload size in bytes for the Media Manager. Defaults to 5,000,000
# (5MB) if unset.
MEDIA_MAX_UPLOAD_BYTES="5000000"
# ── Google Reviews (optional — see /owner/settings/reviews) ────────────
# From https://console.cloud.google.com/apis/credentials — needs "Places
# API (New)" enabled on that project (a different product from the older
# "Places API" — enabling the wrong one returns REQUEST_DENIED). Leave
# blank to keep review sync disabled entirely (Home shows clearly-labelled
# demo reviews instead).
GOOGLE_PLACES_API_KEY=""
# ── Email (optional — defaults to logging to the console instead of sending) ─
# Managed centrally by the Manager's Settings > Email page and pushed
# into every business's .env automatically — see manager/src/lib/
# global-settings.ts. There's normally no reason to hand-edit these for
# one business only; this by-hand template still needs them set if
# you're not using the Manager.
EMAIL_PROVIDER="console"
EMAIL_SMTP_HOST="smtp-relay.brevo.com"
EMAIL_SMTP_PORT="587"
EMAIL_SMTP_USERNAME=""
EMAIL_SMTP_PASSWORD=""
EMAIL_SMTP_ENCRYPTION="starttls"
EMAIL_FROM_ADDRESS=""
EMAIL_FROM_NAME=""
EMAIL_REPLY_TO=""
# ── Web Push (optional — defaults to logging to the console instead of sending) ─
# Managed centrally by the Manager's Settings > Push Notifications page and
# pushed into every business's .env automatically — see manager/src/lib/
# global-settings.ts. There's normally no reason to hand-edit these for
# one business only; this by-hand template still needs them set if you're
# not using the Manager. No NEXT_PUBLIC_VAPID_PUBLIC_KEY here — the app
# serves its public key at runtime via GET /api/push/vapid-public-key
# instead (see that route's own header for why).
PUSH_PROVIDER="console"
VAPID_PUBLIC_KEY=""
VAPID_PRIVATE_KEY=""
VAPID_SUBJECT="mailto:owner@example.com"