# Sprint Planner — Administrator Manual This guide walks an administrator through the initial configuration and the day-to-day operation of the Sprint Planner web application. It assumes a Linux host with Docker and Docker Compose v2 installed. For architecture, schema, and feature history, see [SPEC.md](../SPEC.md) in the repository root. --- ## 1. Prerequisites - A Linux (or macOS / Windows with WSL2) host with: - Docker Engine 24+ and the `docker compose` plugin. - Outbound HTTPS access during build (Composer + npm pull dependencies). - One of the following for sign-in: - **Microsoft Entra ID tenant** with permission to register an application (preferred for production), or - **Local admin credentials** (email + password) — useful for initial setup, on-prem-only deployments, or development. - A directory on the host to hold the persistent SQLite database and PHP session files. The compose file mounts `./data` next to `docker-compose.yml`; nothing else is needed for storage. --- ## 2. Getting the code ```bash git clone sprint_planer_web cd sprint_planer_web ``` All commands in the rest of this manual are run from the repository root. --- ## 3. Initial configuration of `.env` The application reads its configuration from a single `.env` file in the repository root. Start by copying the template: ```bash cp .env.example .env ``` Open `.env` in an editor and fill in the values described below. The file must be readable by the user that runs `docker compose`; on a shared host restrict it with `chmod 600 .env`. ### 3.1 Entra ID (Microsoft 365) — production sign-in If your team will sign in with their Microsoft 365 / Entra accounts: | Variable | What to put there | |-----------------------|-------------------| | `ENTRA_TENANT_ID` | Directory (tenant) ID — a GUID, visible on the Entra "Overview" page. | | `ENTRA_CLIENT_ID` | Application (client) ID of the app registration. | | `ENTRA_CLIENT_SECRET` | A client secret you generated for the app registration. Treat as sensitive. | In the Entra app registration, configure: - **Redirect URI** (Web platform): `{APP_BASE_URL}/auth/callback`, e.g. `https://sprint.example.com/auth/callback`. - **ID tokens** issued from the auth endpoint: enabled. - **Supported account types**: usually "Accounts in this organisational directory only". - **API permissions**: `openid`, `profile`, `email` (delegated, default for OIDC). Grant admin consent if your tenant requires it. Leave Entra fields blank if you only intend to use the local-admin fallback. The OIDC sign-in button still appears, but clicking it will fail until the variables are populated — keep `LOCAL_ADMIN_EMAIL` / `LOCAL_ADMIN_PASSWORD` filled if you skip Entra entirely. ### 3.2 Application URL ``` APP_BASE_URL=https://sprint.example.com ``` The base URL the application is reachable at, **without** trailing slash. This is used to construct the OIDC redirect URI and must match exactly what is registered in Entra. For local testing the default `http://localhost:8080` is fine — but the compose file ships the app on port `8088`, so use `http://localhost:8088` if you have not edited `docker-compose.yml`. ### 3.3 Database and session storage paths ``` DB_PATH=/var/www/data/app.sqlite SESSION_PATH=/var/www/data/sessions ``` Leave the defaults unless you are also remapping the volume. The parent directory `/var/www/data` is the volume mount point inside the container and corresponds to `./data/` on the host. ### 3.4 Environment mode ``` APP_ENV=production ``` `production` silences verbose PHP errors. Any other value (e.g. `dev`) turns them on — useful when troubleshooting in a non-public install. ### 3.5 Local admin fallback (optional) ``` LOCAL_ADMIN_EMAIL=admin@example.com LOCAL_ADMIN_PASSWORD= LOCAL_ADMIN_NAME=Local Admin ``` If both `LOCAL_ADMIN_EMAIL` and `LOCAL_ADMIN_PASSWORD` are set, a second sign-in form appears at `/auth/local`. The password is **compared in plain text** against the env value — so: - Pick a long, unique passphrase you do not reuse anywhere else. - Make sure `.env` is `chmod 600` and owned by a single trusted user. - Rotate the passphrase by editing `.env` and restarting the container. The local-admin user is recorded in the database as `entra_oid = "local:"` with `is_admin = 1`. If the `users` table is empty at the moment of the first successful login (OIDC or local), that user is auto-promoted to admin and an audit entry of type `BOOTSTRAP_ADMIN` is recorded. This is how the very first administrator is created — there is no separate "create admin" step. To disable the fallback later, blank both variables and restart. --- ## 4. Running the containers The project ships a multi-stage Dockerfile (Node-based Tailwind build → PHP 8.3 + Apache runtime) and a `docker-compose.yml` that wires the container to the host. ### 4.1 First start ```bash docker compose up --build ``` What happens: 1. Compose builds the image. The Node stage compiles `assets/css/input.css` into `public/assets/css/app.css`. The PHP stage installs Composer dependencies and copies the application source into `/var/www/html`. 2. The container starts Apache on port 80, exposed on the host as port **8088** (see `docker-compose.yml`). 3. On the first request, the migration runner creates `app.sqlite` inside the volume and applies all migrations under `migrations/`. Open `http://:8088`. If you used the local-admin fallback, sign in at `/auth/local`. Otherwise click the Entra sign-in CTA on `/`. ### 4.2 Running detached (recommended for production) ```bash docker compose up -d --build ``` Logs are then read with: ```bash docker compose logs -f ``` Stopping the stack: ```bash docker compose down ``` `down` keeps the `./data` volume intact — your database and sessions survive. To wipe and start over, stop the stack and remove `./data` manually (see §5.4). ### 4.3 Rebuilding after changes You only need to rebuild the image when the build inputs change: - `Dockerfile`, `composer.json` / `composer.lock`, or `package.json` / `package-lock.json` - Tailwind sources under `assets/css/` or any view file that introduces new utility classes (the css-builder stage scans the views). ```bash docker compose build --no-cache && docker compose up -d ``` For pure PHP / view edits without a rebuild, restart the container: ```bash docker compose restart ``` ### 4.4 Health check The application exposes an unauthenticated `GET /healthz` route that returns `200 OK` with a small JSON body. Use it from a load balancer or uptime monitor: ```bash curl -fsS http://:8088/healthz ``` ### 4.5 Changing the host port Edit the `ports` line in `docker-compose.yml`: ```yaml ports: - "8088:80" ``` Change `8088` to any free port on the host. After editing, `docker compose up -d` is enough — no rebuild required. Update `APP_BASE_URL` in `.env` and the redirect URI in Entra to match. --- ## 5. Day-to-day administration ### 5.1 Promoting and demoting administrators Once the bootstrap admin is signed in, additional administrators are managed through the web UI: 1. Open the hamburger menu → **Users**. 2. Toggle the admin checkbox next to a user and confirm. Guardrails enforced server-side: - An administrator cannot demote themselves while signed in (avoids locking yourself out). - The last remaining administrator cannot be demoted (avoids locking the org out). If both guardrails fire at once — i.e. you are the only admin — the only safe escape is to sign in via the local-admin fallback (if configured) and promote a second account first. ### 5.2 Backups Everything that needs a backup lives under `./data/`: - `app.sqlite` — the database, including audit log. - `sessions/` — active PHP session files. Losing these only forces re-login, no data loss. A safe nightly backup is a straight file copy while the container is running. SQLite handles concurrent reads fine; for a guaranteed consistent snapshot, prefer `sqlite3` `.backup`: ```bash docker compose exec app sqlite3 /var/www/data/app.sqlite \ ".backup /var/www/data/app.sqlite.bak" cp ./data/app.sqlite.bak /your/offsite/backup/location/ ``` ### 5.3 Inspecting the audit log Every create / update / delete on a domain entity is recorded, plus sign-in events. Admins can browse and filter the log via the hamburger menu → **Audit log**, with filters by entity type, action, user, and date range, and collapsible JSON diffs per row. ### 5.4 Resetting the application To wipe all data and start from a blank slate: ```bash docker compose down rm -rf ./data docker compose up -d ``` Migrations run again on the next request and you are back to "no users, first sign-in becomes admin". ### 5.5 Updating to a new release ```bash git pull docker compose build --no-cache docker compose up -d ``` Schema migrations under `migrations/` run automatically on the next request after restart. Always take a backup of `./data/app.sqlite` before pulling. --- ## 6. Troubleshooting | Symptom | Likely cause | Fix | |---|---|---| | `/auth/login` returns "redirect URI mismatch" | The redirect URI registered in Entra does not equal `{APP_BASE_URL}/auth/callback`. | Update either `APP_BASE_URL` in `.env` or the Entra app registration. | | `/auth/local` returns 404 | `LOCAL_ADMIN_EMAIL` or `LOCAL_ADMIN_PASSWORD` is blank. | Set both, then `docker compose restart`. | | Sign-in succeeds but every page shows "not authorised" | The user has no `is_admin` flag and is trying to access an admin-only page. | Promote them via Users (logged in as another admin). | | Container restarts in a loop | Most often a malformed `.env` line or a permission problem on `./data`. | `docker compose logs` will show the PHP fatal. Check that `./data` is writable by the `www-data` user inside the container (uid 33 on the Apache image). | | CSS looks broken / unstyled | The Node build stage was skipped or used a stale layer. | `docker compose build --no-cache` and restart. | | OIDC works but PHP 8.4 deprecation warnings appear in logs | Known upstream issue in `jumbojett/openid-connect-php`. | Harmless on PHP 8.3 (the shipped runtime); ignore until upstream releases a fix. | For anything else, check the audit log first — most user-facing errors also leave a trail there with the exact request that failed. --- ## 7. Where to go next - [SPEC.md](../SPEC.md) — full specification, schema, route list, and build phase history. Read this if you intend to modify the code. - [ACCEPTANCE.md](../ACCEPTANCE.md) — manual acceptance checklist used to validate releases.