Skip to content

First Run

The first time TravStats starts, it generates the secrets it needs, applies all database migrations, and seeds the global airport database. This page covers what happens, what you do, and how to verify the stack is healthy before relying on it.

Watch the first boot with docker compose logs -f app. You’ll see roughly this sequence:

  1. Data directory layout is ensured under /app/data/ (owned by node:node, uid 1000).
  2. JWT secret is generated (openssl rand -hex 32) and persisted to /app/data/secrets/jwt.secret. On subsequent boots it’s loaded from there — your sessions survive container recreation.
  3. Database connectivity is checked (up to 30 retries, 2 s apart).
  4. Prisma migrations are applied (prisma migrate deploy). On a fresh database this creates all tables in one shot.
  5. Time-semantics backfill runs once on existing installs that pre-date v1.2.0 (a no-op on fresh installs).
  6. Airport database is seeded from a packaged OpenFlights CSV (~9000 rows). Skipped on subsequent boots once the table is non-empty.
  7. nginx binds to port 80, backend binds to 8000 internally — only port 80 is exposed via the compose file.

When you see [entrypoint] TravStats is ready (nginx on :80, backend on :8000), the stack is up.

There is no default admin user. TravStats hands you a setup wizard the first time you open it.

  1. Visit http://<your-host>:3000 (or your reverse-proxy URL).
  2. The frontend hits /api/v1/setup/status and gets requiresSetup: true. You’ll be redirected to Setup.
  3. Fill in — the wizard is deliberately short:
    • Username, password, password confirmation — your admin account. The password must be at least 8 characters.
    • Domains — which parts of TravStats this instance uses: flights, cruises, lodging, places. Flights is pre-selected; the rest can be switched on later under Admin → Settings.
    • Usage statistics — whether the instance may send an anonymous usage ping. Off unless you say yes; changeable later.
  4. Submit. The backend creates the first admin user and signs you in via an HttpOnly cookie. You land on the dashboard.

The public URL is taken from the address you opened the wizard on; instance name, registration mode and the user cap are not asked here — they live under Admin → Settings, and the user cap starts at 10 (env MAX_USERS sets a different starting value).

To keep first-run as short as possible, these are configured later under Admin → Settings (no env-var editing required):

  • AirLabs / Aviationstack / OpenSky API keys (encrypted at rest, used to enrich flights with aircraft type, distance, route history)
  • Ollama URL + model (default gemma3:12b, fallback when the template engine doesn’t recognise an email)
  • SMTP (for invitation emails, password reset, flight reminders)
  • Backups — automatic backups are off until you enable them under Admin → Backups (weekly, 30-day retention once on); optional WebDAV off-site copy (Nextcloud, HiDrive, …)
  • Logging level (defaults to info, can be raised to debug when troubleshooting)

You can use TravStats fully without any of these — they unlock specific features rather than gating core functionality.

Three quick checks the homelab muscle-memory will already know:

Terminal window
# Container health
docker compose -f docker-compose.prod.yml ps
# App reports healthy?
curl -fsS http://localhost:3000/health
# → 200 OK with JSON body once migrations are done
# Database reachable?
docker exec travstats-db pg_isready -U flights
# → /var/run/postgresql:5432 - accepting connections

If /health keeps returning 503, watch the logs — migrations or the airport seed may still be running on first boot. The seed does ~9000 inserts and takes 30–60 s on a Pi-class CPU.

If you finish the wizard with a username you already regret, there are two ways out:

  • From the UI (cleanest) — log in, Admin → Users, create a second admin, sign in as the new one, delete the first.
  • CLI fallback — docker exec -it travstats-app node /app/backend/dist/scripts/createAdmin.js reads ADMIN_USERNAME / ADMIN_PASSWORD from the environment and creates an admin if none exists. (No-op if the database already has an admin user — won’t overwrite.)

Lost the admin password before SMTP is configured

Section titled “Lost the admin password before SMTP is configured”

The “forgot password” flow needs SMTP. If you haven’t set it up yet:

Terminal window
# Open a Postgres shell inside the db container
docker exec -it travstats-db psql -U flights -d flights
# Reset the admin password to a known bcrypt hash
# (this is the bcrypt of "tempchangeme" with cost 10)
UPDATE users
SET password_hash = '$2b$10$WdN5oW3bI3p5LKDGo6gBb.MR0J2I3Sw6NkN4QrJj0Dp/2nXFqtbQO',
must_change_password = true
WHERE is_admin = true;
\q

Sign in with tempchangeme, change the password immediately, then go to Admin → Settings and configure SMTP so this never happens again.

  • Add your first flight manually or by parsing a confirmation email.
  • Enable backups — they are off by default; once on, a weekly archive lands in /app/data/backups, and an optional WebDAV copy ships it off-host.
  • Updating — what to expect when a new release lands.