# FMS — Deployment & Operations Guide (Version 1)

This guide covers putting FMS on a server, keeping it running, and recovering it.

## 1. Server requirements

| Component | Requirement |
|---|---|
| PHP | 8.2, 8.3 or 8.4 with `bcmath`, `ctype`, `curl`, `fileinfo`, `json`, `mbstring`, `openssl`, `pdo_mysql`, `tokenizer`, `xml` (recommended: `gd`, `intl`, `zip`) |
| Database | MySQL 8.0+ (MariaDB 10.4+ works), InnoDB, `utf8mb4` |
| Web server | Nginx or Apache, with the document root set to `public/` |
| HTTPS | Required in production (Let's Encrypt is fine) |
| Process supervisor | Supervisor/systemd to keep the queue worker running |
| Cron | One entry for the Laravel scheduler |
| Tools | `mysqldump` (for backups) |

## 2. Install

1. Upload the code without `node_modules/`. Build on your machine or in CI:
   ```bash
   composer install --no-dev --optimize-autoloader
   npm ci && npm run build
   ```
2. Point the web root at `public/`. Make sure `storage/` and `bootstrap/cache/` are writable by the web server user, and `.env` is writable during installation.
3. Open the site in a browser. Every page redirects to **/install** until installation is complete. The wizard:
   - checks the server requirements
   - tests the database connection and can create the database
   - collects application settings (name, URL, environment, timezone, currency)
   - collects outgoing email settings
   - creates the **Platform Owner** account
4. When it finishes, it writes `storage/app/installed.json`, and `/install` then returns 404. To reinstall from scratch, delete that file (it does not delete any data).
5. After installing, optimise:
   ```bash
   php artisan config:cache && php artisan route:cache && php artisan view:cache && php artisan event:cache
   ```
   Re-run these after every update.

## 3. Background processes

### Scheduler (cron, every minute)

```cron
* * * * * cd /path/to/fms && php artisan schedule:run >> /dev/null 2>&1
```

| Task | Schedule | What it does |
|---|---|---|
| `fms:subscriptions:process` | every 5 min | Starts grace periods and returns expired plans to Free |
| `fms:recurring:run` | hourly | Records due recurring transactions (each occurrence once) |
| `fms:subscriptions:remind` | daily 09:00 | Renewal reminders 7 days and 1 day before a plan ends |
| `fms:exports:prune` | daily | Deletes report exports older than 7 days |
| `fms:backup` | daily 02:30 | Database backup, verified, newest 14 kept |

### Queue worker (always running)

Emails, notifications and report exports run on the `database` queue.

```ini
; /etc/supervisor/conf.d/fms-worker.conf
[program:fms-worker]
command=php /path/to/fms/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
user=www-data
numprocs=1
stopwaitsecs=360
```

After each deploy, run `php artisan queue:restart`.

## 4. Payments (Razorpay)

1. In Razorpay, create API keys. Use **test mode** (`rzp_test_…`) until you have verified the full flow.
2. In Razorpay → Webhooks, add the URL shown in **Admin → Settings → Payments**:
   `https://YOUR-DOMAIN/webhooks/payments/razorpay`
   - Events: `payment.captured`, `payment.failed`, `order.paid`, `refund.processed`, `refund.failed`.
   - Choose a webhook secret.
3. Enter the Key ID, Key secret and webhook secret in **Admin → Settings → Payments** and select Razorpay. The secrets are stored encrypted and are never shown again.
4. Plans activate **only** from the signed webhook, so the webhook URL must be reachable from the internet.
5. Check **Admin → Payments → Webhook log** for any failed or ignored events.

The **Sandbox** gateway is for local and staging testing only. It is not offered in production.

## 5. Email

Set SMTP in the installer or in **Admin → Settings → Email**, then use **Send a test email**. Without working email, customers cannot verify their address and cannot use the app.

## 6. Backups and restore

### What to back up

- **Database**: `php artisan fms:backup` writes `storage/app/private/backups/fms-YYYYMMDD-HHMMSS.sql.gz`.
  - The dump is taken with `--single-transaction`, so the site stays online.
  - Each backup is checked for completeness after it is written.
  - The 14 newest are kept (`FMS_BACKUP_KEEP`).
  - Set `FMS_MYSQLDUMP` if `mysqldump` is not on the PATH.
- **Files**: `storage/app/private/workspaces/` (receipts and exports) and the `.env` file (it holds `APP_KEY`, which decrypts stored secrets — **without it, encrypted settings and MFA secrets cannot be read**).
- Copy both off the server every day (object storage, another host). A backup that only exists on the same server is not a backup.

### Check a backup

```bash
php artisan fms:backup:verify                 # newest backup
php artisan fms:backup:verify path/to/file.sql.gz
```

### Restore (practise this every quarter)

```bash
php artisan down
mysql -u USER -p -e "CREATE DATABASE fms_restore CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
gunzip < storage/app/private/backups/fms-YYYYMMDD-HHMMSS.sql.gz | mysql -u USER -p fms_restore
# check the restored data, then point DB_DATABASE at it (or rename databases)
php artisan config:cache
php artisan up
```

Restore `storage/app/private/workspaces/` and `.env` (with the **same** `APP_KEY`) from the file backup.

## 7. Updating

```bash
php artisan down
git pull                          # or upload the new release
composer install --no-dev --optimize-autoloader
npm ci && npm run build
php artisan migrate --force
php artisan db:seed --class="Database\Seeders\InstallSeeder" --force   # adds new permissions/features; safe to re-run
php artisan config:cache && php artisan route:cache && php artisan view:cache && php artisan event:cache
php artisan queue:restart
php artisan up
```

Always take a backup before migrating.

## 8. Security checklist

- [ ] HTTPS everywhere; `APP_URL` starts with `https://`; `APP_ENV=production`; `APP_DEBUG=false`
- [ ] `SESSION_SECURE_COOKIE=true` (the installer sets this for https URLs)
- [ ] Two-factor authentication turned on for every staff account, then **Admin → Settings → Security → Require two-factor** switched on
- [ ] Only the people who need it have the Platform Owner or Super Admin role
- [ ] `.env` is not web-accessible (the web root is `public/`) and is backed up securely
- [ ] Razorpay is in live mode only after a successful end-to-end test
- [ ] Backups run, verify, are copied off the server, and a restore has been tested
- [ ] Terms of service and privacy policy pages are replaced in **Admin → CMS → Pages**
- [ ] The support email is set in **Admin → Settings → General**

## 9. Lost access

If no Platform Owner can sign in:

```bash
php artisan fms:admin:create you@example.com --name="Your Name" --role=platform-owner
```

This is recorded in the staff audit log.
