# Server — dvla-api

Production host for the `dvla-api` Laravel application. Single VPS, single app.
This file is the source of truth for how the box is configured. Update it when
you change the box.

## Host

- **Provider / type:** Hetzner Cloud CX23 (2 vCPU, 4 GB RAM, 80 GB disk)
- **OS:** Ubuntu 24.04 LTS (noble)
- **Public IP:** 37.27.203.46
- **Hostname:** ubuntu-4gb-dvla-api
- **Swap:** 2 GB swapfile at `/swapfile` (mounted via `/etc/fstab`)

## Users (UPDATED)

- deploy (UID 1000) — human admin, sudo, your SSH key
- deploy-dvla (UID 1001) — owns and runs dvla-api, no sudo, your SSH key
- www-data — nginx only, no group memberships in app users

## PHP-FPM (UPDATED)

- Pool: /etc/php/8.4/fpm/pool.d/dvla-api.conf
- Runs as deploy-dvla
- Socket: /run/php/php8.4-fpm-dvla.sock
- pm.max_children = 3
- Default www.conf disabled (renamed to www.conf.disabled)

## Filesystem perms

- /var/www/dvla-api: 2755 (traversable so nginx can reach public/)
- App subdirs: 2750 (deploy-dvla owner+group only)
- public/: 755/644 (world-readable, intentional)
- .env: 600
- storage/, bootstrap/cache/: 2770 (writable by deploy-dvla)
- database/: 2770, database.sqlite: 660

## Known gaps

- No backups
- No HTTPS yet
- Token-based git auth (not SSH) — Pangolin doesn't forward SSH to Gitea o

## Web stack

- **nginx** 1.24 — site config: `/etc/nginx/sites-available/dvla-api`
  - Listens on `:80` (HTTP only — TLS pending)
  - `server_name 37.27.203.46;`
  - Document root: `/var/www/dvla-api/public`
- **PHP-FPM** 8.4 — pool: default `www.conf`
  - Runs as `www-data:www-data`
  - Socket: `/run/php/php8.4-fpm.sock`
  - `pm = dynamic`, `pm.max_children = 5` (CX23-appropriate)

## Application

- **Path:** `/var/www/dvla-api`
- **Owner:** `deploy:www-data`, directories have SGID bit (`drwxrwsr-x`) so
  new files inherit the `www-data` group automatically
- **Framework:** Laravel 11 on PHP 8.4
- **Git remote:** SSH to Gitea via deploy key (see `DEPLOY.md`)
- **Branch deployed:** `main`

## Database — SQLite

- **File:** `/var/www/dvla-api/database/database.sqlite`
- **Perms:** file `660`, directory `770`, both owned `deploy:www-data`
  - Directory must be writable by `www-data` so SQLite can create
    `-journal` / `-wal` / `-shm` siblings
- **Journal mode:** WAL (enabled with
  `sqlite3 database/database.sqlite "PRAGMA journal_mode=WAL;"`)
- **Backups:** _<TODO: document backup mechanism — see Backups section>_

`SESSION_DRIVER`, `CACHE_STORE`, and `QUEUE_CONNECTION` are all `database`,
meaning sessions/cache/queue tables live in the same SQLite file as app
data. Acceptable for low traffic; revisit if write contention shows up
(`database is locked` errors in `storage/logs/laravel.log`).

## Queue / scheduler

- **Worker:** _<TODO: either set `QUEUE_CONNECTION=sync` in `.env` or
  install the systemd unit below>_
- **Scheduler:** _<TODO: cron entry for `php artisan schedule:run` if any
  scheduled tasks exist>_

## Network / firewall

- **UFW:** _<TODO: confirm enabled; document allowed ports>_
- **Open ports (verified via `ss -tlnp`):**
  - 22 — SSH
  - 80 — HTTP (nginx)
  - 53 — systemd-resolved, loopback only (not public)
- **TLS:** _<TODO: not yet configured. Plan: domain → Let's Encrypt via certbot>_

## Backups

_<TODO. SQLite makes this easy: a single file. Suggested approach:
nightly cron on `deploy` running `sqlite3 database/database.sqlite ".backup
/var/backups/dvla-api/db-$(date +\%F).sqlite"` followed by offsite copy
(rsync to home server / S3 / Hetzner Storage Box).>_

## Known divergences from the multi-app pattern in project chats

This box was set up for a single app and intentionally simplified:
- Single `deploy` user instead of `deploy-<appname>`
- Default `www-data` PHP-FPM pool instead of a per-app pool
- No Redis (cache/sessions/queue all on SQLite)

These are acceptable as long as this box hosts one app. Revisit the older
project chats before adding a second app to this VPS.
