# Deployment Checklist

Aradhya Enterprises ERP — target architecture (from `claude.md`'s Deployment section):

```
Cloudflare / SSL
      ↓
    Nginx
      ↓
   Laravel
      ↓
   PHP-FPM
      ↓
    MySQL
      ↓
    Redis
```

Two rules from the spec, non-negotiable:

- **Never expose MySQL publicly.** It should only be reachable from the application server itself
  (bind to `127.0.0.1`, firewall port 3306 from the outside world).
- **Use environment variables for production credentials.** Never commit a real `.env`.

This document is a checklist for setting this app up on a real server. Nothing in it has been run
against a live production server — it's been written to match this codebase exactly (verified
locally: `config:cache`/`route:cache`/`view:cache` all succeed cleanly, `composer audit` is clean,
`php artisan backup:run` produces a real, restorable MySQL dump) but the actual server/DNS/SSL
setup is outside what this repo or session can execute or verify.

## 1. Server prerequisites

- PHP 8.3+ with the extensions Laravel 13 / this app need: `pdo_mysql`, `mbstring`, `openssl`,
  `tokenizer`, `xml`, `ctype`, `json`, `bcmath`, `fileinfo`, `zip` (needed by `spatie/laravel-backup`
  for the zip archive), `gd` or `imagick` (Excel export via `maatwebsite/excel`).
- MySQL 8+ (or MariaDB equivalent).
- `mysqldump` available on `PATH` for the backup command (see §6) — if it isn't, set
  `DB_DUMP_BINARY_PATH` in `.env` to the directory containing it (see `.env.production.example`).
- Nginx + PHP-FPM (see `deploy/nginx.conf.example`).
- Supervisor (or systemd) for the queue worker (see `deploy/supervisor-queue-worker.conf.example`)
  — **required** as of Sprint 10: all 9 notification classes are now `ShouldQueue`, so without a
  running worker, in-app notifications will queue up and never actually be delivered.
- Composer 2, Node 18+/npm (for `npm run build`).
- Cloudflare (or equivalent) in front for SSL termination — see `deploy/nginx.conf.example`'s note
  on Cloudflare SSL modes.

## 2. Application setup

```bash
git clone <repo> /var/www/erp.aradhyaenterprise.com
cd /var/www/erp.aradhyaenterprise.com

cp .env.production.example .env
php artisan key:generate
# Fill in DB_*, MAIL_*, and (optionally) AI/AWS credentials in .env — see .env.production.example
# for what each one is for. APP_DEBUG must stay false.

composer install --no-dev --optimize-autoloader
npm install && npm run build

php artisan migrate --force
php artisan db:seed --class=RolePermissionSeeder --force
# Create the first Super Admin user — see database/seeders/DemoDataSeeder.php for the pattern,
# or create one directly via `php artisan tinker` (User::create([...])->assignRole('Super Admin')).
```

## 3. Caching (verified safe this sprint)

```bash
php artisan config:cache
php artisan route:cache
php artisan view:cache
```

Confirmed this sprint: there are zero `env()` calls anywhere outside `config/*.php` in this
codebase, so `config:cache` won't silently break anything at runtime — a common Laravel production
gotcha that doesn't apply here. Re-run these three commands after every deploy (they cache the
*current* code/config; stale caches after a deploy are a common source of confusing bugs).

## 4. Web server

Copy `deploy/nginx.conf.example` to your Nginx sites-available, adjust `server_name`, the PHP-FPM
socket path, and the SSL block per your Cloudflare SSL mode (see the comment at the bottom of that
file). `root` must point at this app's `public/` directory, never the repo root — the rest of the
codebase (including `.env`) must not be web-accessible at all.

## 5. Queue worker

Copy `deploy/supervisor-queue-worker.conf.example` to `/etc/supervisor/conf.d/`, adjust the path
and user, then:

```bash
supervisorctl reread && supervisorctl update && supervisorctl start erp-queue-worker:*
```

## 6. Scheduler

Add one cron entry (this is how Laravel's `bootstrap/app.php` `withSchedule()` — which already
defines the daily business-alerts, backup, and backup-cleanup jobs — actually gets triggered):

```
* * * * * cd /var/www/erp.aradhyaenterprise.com && php artisan schedule:run >> /dev/null 2>&1
```

## 7. Backups

`spatie/laravel-backup` is configured (`config/backup.php`) to dump the MySQL database plus
`app/`, `config/`, `database/`, `resources/`, `routes/`, and `composer.json`/`composer.lock` —
deliberately **excluding** `.env` (credentials shouldn't be duplicated into backup archives) and
`storage/` (logs/cache/uploaded import files aren't business-critical to back up daily; the
database is the source of truth). Runs daily at 02:00 via the scheduler above, with cleanup at
01:30. Verify it after first deploy:

```bash
php artisan backup:run
php artisan backup:list
```

To back up to S3 instead of (or in addition to) local disk: fill in the `AWS_*` variables in
`.env`, add `s3` to `config/backup.php`'s `destination.disks`. The env vars and filesystem disk
have been scaffolded since Sprint 1 but were never used until now — confirm the bucket exists and
the IAM credentials have write access before relying on it.

## 8. Verify the deploy

- Visit the app, log in, confirm the dashboard loads with the right widgets for your role.
- `php artisan queue:work --once` — or just wait for the worker — and trigger something that
  notifies (e.g. change a product's price by >10%) to confirm notifications actually arrive.
- Check `storage/logs/laravel.log` for anything unexpected.
- Confirm `https://` is enforced (Cloudflare) and that visiting `http://` redirects.

## 9. GitLab CI/CD to cPanel

This repo includes `.gitlab-ci.yml` for GitLab CI. The pipeline:

- runs Composer install, npm build, and the Laravel test suite on every branch/MR;
- manually deploys the `master` branch to cPanel over SSH/rsync;
- deploys to `/home/aradhyaenterpris/public_html/erp.aradhyaenterprise.com`;
- writes the production `.env` from GitLab CI variables;
- runs `migrate --force`, `db:seed --class=RolePermissionSeeder --force`, `config:cache`,
  `route:cache`, `view:cache`, and `storage:link` after upload.

Add these GitLab CI/CD variables in **Settings > CI/CD > Variables**. Mark secrets as protected and
masked where GitLab allows it.

| Variable | Value |
| --- | --- |
| `SSH_PRIVATE_KEY` | Contents of the deploy private key file, including the BEGIN/END lines |
| `SSH_PASSPHRASE` | Passphrase for that key |
| `DEPLOY_HOST` | `64.202.185.157` (already the default in `.gitlab-ci.yml`) |
| `DEPLOY_PORT` | `22` unless cPanel SSH uses a custom port |
| `DEPLOY_USER` | `aradhyaenterpris` (from the cPanel home path) |
| `DEPLOY_PATH` | `/home/aradhyaenterpris/public_html/erp.aradhyaenterprise.com` |
| `APP_KEY` | Production Laravel app key, or leave blank only if `.env` already exists on the server |
| `DB_HOST` | `127.0.0.1` (already the default in `.gitlab-ci.yml`) |
| `DB_PORT` | `3306` (already the default in `.gitlab-ci.yml`) |
| `DB_DATABASE` | `aradhyaenterpris_erp` (already the default in `.gitlab-ci.yml`) |
| `DB_USERNAME` | `aradhyaenterpris_erp` (already the default in `.gitlab-ci.yml`) |
| `DB_PASSWORD` | Production database password |

The pipeline deliberately excludes the local `.env`, creates `.env` on the server from CI
variables, and preserves the existing server `APP_KEY` when the GitLab `APP_KEY` variable is blank.
For a fresh deploy, either add `APP_KEY` in GitLab or generate it once on the server before relying
on encrypted cookies/data.

For Laravel, the cPanel domain document root should point at:

```text
/home/aradhyaenterpris/public_html/erp.aradhyaenterprise.com/public
```

Do not point the web root at the project root, because `.env`, `vendor/`, `storage/`, and app source
files must not be directly web-accessible.

## Known gaps (not addressed this sprint, deliberately out of scope)

- No specific IP allowlist is configured for `TrustProxies` (`bootstrap/app.php` trusts all
  proxies, appropriate behind Cloudflare/Nginx where the exact edge IPs aren't knowable in
  advance) — an operator who wants to lock this down further to Cloudflare's published IP ranges
  can do so in `bootstrap/app.php`.
- Redis is in the spec's architecture diagram but `QUEUE_CONNECTION`/`CACHE_STORE` default to
  `database` here (verified working) — switching to `redis` once it's provisioned is a config
  change, not a code change.
