# Setup & configuration For the quick version — just running the app — see the root [README](../README.md#getting-started). This covers the rest: running without Docker, every configuration option, and the test suite. ## Run with Docker The only requirement is Docker with the Compose plugin. ```bash docker compose up -d ``` This runs a multi-stage build — a Node stage compiles the Vue frontend, then a PHP 8.3 + Apache stage bakes in the PHP source and the built SPA — applies migrations, and serves the whole app at : the SPA at `/` (assets and all) and the REST API under `/api` (e.g. `curl http://localhost:8080/api/health`). Unknown paths fall back to the SPA shell for client-side routing. - A **[Mailpit](https://mailpit.axllent.org/)** container (the maintained MailHog successor — one ~15 MB Go binary, messages kept in memory) also starts. The API sends all email to it; read it at . Set `MAIL_TRANSPORT=mail` or `=smtp` (with `MAIL_SMTP_*`) to send for real. - The image is the artifact: PHP source and the compiled frontend are copied in at build time, not bind-mounted. Rebuild to pick up any code change: `docker compose up -d --build`. For iterating on the frontend, run the Vite dev server on the host instead (see [web/README.md](../web/README.md)). - The SQLite database and the generated JWT signing key live in the `storage` named volume, mounted at `/var/www/storage`, so they survive `docker compose restart` / `down` + `up`. - `docker compose down -v` removes the volume and gives you a clean database. - Override settings via the environment or a `.env` file in the repo root (Compose substitutes `APP_DEBUG`, `JWT_SECRET`, `JWT_TTL` — see [docker-compose.yml](../docker-compose.yml)). ## Run without Docker Requires PHP 8.1+ with the `pdo_sqlite` and `mbstring` extensions, plus [Composer](https://getcomposer.org/). On Fedora: ```bash sudo dnf install php-cli php-pdo php-mbstring composer ``` ### Setup ```bash composer install cp .env.example .env # optional; sane defaults are used without it composer migrate # creates storage/database.sqlite and its tables ``` ### Running ```bash composer serve # http://localhost:8080 (php -S localhost:8080 -t public) ``` Any web server can serve the API as long as the document root is `public/` and unknown paths fall through to `public/index.php`. `public/.htaccess` also serves a built frontend from `public/` (copy `web/dist/` there) and only falls back to `index.php` for `/api` and when no `index.html` is present. The frontend itself still needs its own toolchain — see [web/README.md](../web/README.md). ## Configuration All settings are optional environment variables (read from `.env` or the real environment). See [.env.example](../.env.example). | Variable | Default | Purpose | |----------|---------|---------| | `APP_DEBUG` | `false` | Include exception details in error responses | | `DATABASE_PATH` | `storage/database.sqlite` | SQLite file location | | `JWT_SECRET` | auto-generated into `storage/secret.key` | Token signing key | | `JWT_TTL` | `86400` | Token lifetime in seconds | | `APP_ALLOW_REGISTRATION` | `true` | When `false`, a magic link is only ever sent to an existing address — an unknown one is silently ignored, so no new accounts get created | | `MAGIC_LINK_RESEND_SECONDS` | `60` | Minimum gap before a magic link can be resent to the same address (sign-in or email-change). Docker Compose overrides this to `0`, so links resend immediately in development | | `APP_URL` | `http://localhost:8080` | Base URL used to build magic links (`http://localhost:5173` for a host `npm run dev`) | | `WEBAUTHN_RP_ID` | `APP_URL`'s host | Passkey relying party ID (domain). Must be `localhost` or a real domain over HTTPS — a LAN IP won't work | | `WEBAUTHN_RP_NAME` | `Projects` | Passkey relying party display name, shown in the browser/OS prompt | | `MAIL_TRANSPORT` | `mail` | `mail` (PHP `mail()`), `smtp`, or `log` (append to a file) | | `MAIL_FROM` / `MAIL_FROM_NAME` | `no-reply@todo.test` / `Projects` | Envelope sender | | `MAIL_LOG_PATH` | `storage/mail.log` | Where `log` transport writes | | `MAIL_SMTP_HOST` / `_PORT` / `_USERNAME` / `_PASSWORD` / `_ENCRYPTION` | — / `587` / — / — / `tls` | Used only when `MAIL_TRANSPORT=smtp` | Standalone, SMTP is opt-in and the API otherwise falls back to PHP's `mail()`. Under Docker Compose the default is `MAIL_TRANSPORT=smtp` pointed at the bundled Mailpit container (`mailpit:1025`, no auth/TLS); open to read what was "sent". ## Tests ```bash composer install # installs phpunit (require-dev) vendor/bin/phpunit ``` Each test run applies every file in `migrations/*.sql` to a fresh SQLite database, so it always exercises the current schema from scratch.