# Production deployment guide

## Supported production stack

- PHP 8.2 or newer with `pdo_mysql`, `mbstring`, `openssl`, `fileinfo`, and `json`.
- MySQL 8 using InnoDB, `utf8mb4`, strict SQL mode, and UTC.
- Nginx or Apache with the document root set to `public/`.
- TLS 1.2 or TLS 1.3. Plain HTTP must redirect to HTTPS.
- A dedicated unprivileged service account, such as `powerams`.

Never expose the repository root as a web document root. The `.env`, private uploads, logs, migrations, tests, and handover documents must remain outside `public/`.

## Repeatable release layout

Use immutable release directories and shared mutable storage:

```text
/var/www/powerams/
  current -> releases/20260719-1900
  releases/
  shared/.env
  shared/storage/
```

For every release:

1. Extract reviewed source into a new `releases/<release-id>` directory.
2. Link `.env` to `shared/.env` and `storage` to `shared/storage`.
3. Install/build dependencies in CI. Run `npm ci && npm run build`; deploy the compiled `public/assets` output.
4. Run `php tests/quality/static-analysis.php` and `php tests/production/production-readiness.php` against the release.
5. Back up the database and private uploads.
6. Enable a maintenance window if the release changes schema or long-running workflows.
7. Run `php cli migrate:status`, then `php cli migrate`.
8. Run `php cli health:check`. Warnings need review; failures block activation.
9. Atomically change `current` to the new release and reload PHP-FPM.
10. Verify `/health`, login, dashboard, one read-only module, and the administration health page.

Run `php cli seed` during first installation. On upgrades, run it only when release notes explicitly say seed catalogs changed; seeders are idempotent but operational role changes require review.

## Environment and secrets

Copy `.env.production.example` to the shared `.env`. Generate a unique `APP_KEY`, database password, administrator password, API tokens, and webhook secrets. Do not commit or transmit the populated file through tickets or chat. Required production values include:

- `APP_ENV=production`
- `APP_DEBUG=false`
- An HTTPS `APP_URL`
- `SESSION_SECURE=true`
- A unique random `APP_KEY`
- A least-privilege MySQL account

After deployment, remove default/demo accounts, require the initial administrator to change their password, revoke unused sessions and API tokens, and verify the installer returns 404.

## File ownership and permissions

Example, assuming deployment group `powerams` and PHP-FPM user `www-data`:

```bash
chown -R root:powerams /var/www/powerams/releases/<release-id>
find /var/www/powerams/releases/<release-id> -type d -exec chmod 0750 {} \;
find /var/www/powerams/releases/<release-id> -type f -exec chmod 0640 {} \;
chown -R www-data:powerams /var/www/powerams/shared/storage
find /var/www/powerams/shared/storage -type d -exec chmod 0750 {} \;
find /var/www/powerams/shared/storage -type f -exec chmod 0640 {} \;
chown root:powerams /var/www/powerams/shared/.env
chmod 0640 /var/www/powerams/shared/.env
```

The web process needs read access to application source and read/write access only to `storage/cache`, `storage/logs`, and `storage/uploads`. It does not need write access to source, configuration, migrations, or `public/`.

## Web server, SSL, PHP-FPM, and MySQL

- Start from `deployment/nginx/powerams.conf` or `deployment/apache/powerams.conf`.
- Start from `deployment/php/99-powerams.ini`, then size memory and workers from measured traffic. Use a dedicated PHP-FPM pool, `clear_env=yes`, `security.limit_extensions=.php`, a Unix socket, slow-request logging, and `pm.max_requests=500` to recycle workers.
- Start from `deployment/mysql/powerams.cnf`; size the buffer pool to approximately 60–70% of database-host RAM when MySQL is dedicated.
- Obtain certificates through the organization’s CA or ACME client. Automate renewal and test it with the CA’s dry-run command. Enable HSTS only after HTTPS works on every required subdomain.

Create separate migration and runtime database identities. The runtime user should not receive `DROP`, `ALTER`, `CREATE USER`, `GRANT`, or global privileges:

```sql
CREATE USER 'powerams_app'@'10.%' IDENTIFIED BY '<random-secret>' REQUIRE SSL;
GRANT SELECT, INSERT, UPDATE, DELETE ON powerams.* TO 'powerams_app'@'10.%';
CREATE USER 'powerams_migrator'@'10.%' IDENTIFIED BY '<separate-secret>' REQUIRE SSL;
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX, DROP, REFERENCES ON powerams.* TO 'powerams_migrator'@'10.%';
```

Temporarily supply migrator credentials only to the migration process; restore runtime credentials before PHP-FPM starts.

## Migrations, health, and index verification

Before migration, capture a verified database backup and record its checksum. Review pending migrations with `php cli migrate:status`. Apply with `php cli migrate`. MySQL DDL can commit implicitly, so do not assume a failed schema migration is transactionally undone.

Health commands:

```bash
php cli migrate:status
php cli health:check
mysql --ssl-mode=REQUIRED powerams -e "SELECT VERSION(), @@global.time_zone, @@session.sql_mode"
mysql --ssl-mode=REQUIRED powerams -e "CHECK TABLE migrations, airlines, users, aircraft, flights, expenses, audit_logs"
```

Index verification:

```sql
SELECT TABLE_NAME, INDEX_NAME, GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS columns
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'powerams'
GROUP BY TABLE_NAME, INDEX_NAME
ORDER BY TABLE_NAME, INDEX_NAME;
```

Compare the applied migration list and index output to the release artifact. Investigate duplicate or missing tenant/date/status indexes before production traffic.

## Cron, background work, and monitoring

Install `deployment/cron/powerams` for the PHP-FPM service account. The application job dispatcher is synchronous, so there is no persistent queue worker. Outbound webhook deliveries are processed through the protected integration workflow; if automated delivery processing is introduced later, implement a dedicated authenticated CLI command before scheduling it.

Monitor:

- `GET /health` for HTTP liveness. It intentionally does not disclose database details.
- `php cli health:check` for database, migrations, storage, installer, cron, failed jobs, queue mode, and backup marker status.
- Administration → System health for privileged database/storage/session/failed-job inspection.
- Web server, PHP-FPM, and `storage/logs` for error rate and latency.
- `storage/cache/cron-heartbeat.json` age; alert after 15 minutes.
- `storage/backups/latest-success.json` age; the backup job must update it only after verification succeeds.
- `failed_integration_jobs` and failed webhook deliveries.

Restrict application log and health-command output to operations staff. Never expose the log viewer permission to ordinary users.

## Upgrade and rollback

Upgrade code using a new immutable release and the process above. Never overwrite a live release in place. Preserve `.env`, private storage, and database backups.

For application-only failure with no incompatible migration, repoint `current` to the prior release and reload PHP-FPM. For schema failure, stop writes, inspect `php cli migrate:status`, and use `php cli migrate:rollback` only when the most recent migration’s `down()` operation is reviewed and data-safe. Otherwise restore the pre-migration database and matching upload snapshot. Never combine old code with a newer incompatible schema.

Record release ID, code checksum, migration batch, backup ID, deployer, validation results, and rollback decision in the change record.
