# Security Backup and Restore Runbook

**Applies to:** Microfinance production and disaster-recovery environments  
**Backup commands:** `php artisan security:backup` and `php artisan security:backup-verify {path}`

## 1. Security assumptions

- Production must run with `APP_ENV=production` and `APP_DEBUG=false`.
- `APP_KEY` must be escrowed separately in an approved secret manager. It is deliberately **not** copied into backup bundles. The same key is required after restore to decrypt application-encrypted values such as two-factor secrets and recovery-code hashes.
- Database credentials, SMS/WhatsApp tokens and other secrets stay in environment/secret storage and are not committed to Git or written to the backup manifest.
- `BACKUP_PATH` must point outside `storage/app/private`. A mounted encrypted backup volume is recommended in production.
- Backup bundles contain sensitive customer and financial data. Restrict the backup root to the application/backup operator and replicate it only to encrypted, access-controlled off-host storage.

## 2. What a backup contains

`security:backup` creates one timestamped bundle containing:

1. `database.sql` — logical database backup.
2. `private/` — an exact copy of every file under the configured private storage root.
3. `manifest.json` — database and per-file SHA-256 checksums, byte sizes, creation time, application name and environment.

The command verifies the completed bundle before reporting success. Expired bundles are pruned only after a new bundle passes verification.

### Database-backed records covered

The database dump includes business/branch configuration, users and roles, customers, loans, schedules, collateral records, financial accounts, transactions, accounting entries, payments, payment allocations, receipts and statement source records, disbursements, expenses, payroll/payslip source records, write-offs/recoveries, audit logs, notifications, tasks, closings/reconciliations, login monitoring and 2FA metadata.

### Private files covered

Because the backup copies the entire private disk rather than a hard-coded folder list, it covers current and future private attachments, including:

- Customer/KYC documents and identification scans.
- Loan agreements/contracts, signed contracts and loan documents.
- Collateral photos, supporting files and custody-related attachments.
- Expense receipts and financial supporting documents.
- Write-off evidence and other approval evidence stored privately.
- Business logo and stamp files used by authenticated branded documents.
- Other private files created by existing workflows.

Payment receipts, statements, disbursement receipts and payslips that are generated on demand are restored from their permanent database source records. Persisted document files, where present, are restored from `private/`.

## 3. Creating a manual backup

Run from the deployed application release as the application user:

```bash
php artisan security:backup
```

The command prints the bundle path, database size and private-file count. Treat a non-zero exit status as a failed backup.

Immediately verify a selected bundle when moving/copying it:

```bash
php artisan security:backup-verify /secure/backups/20260812_013000-abc123
```

Never consider an unverified copy a recovery point.

## 4. Automatic daily backup

Configure production environment values, for example:

```dotenv
BACKUP_PATH=/secure/microfinance-backups
BACKUP_KEEP_DAYS=30
BACKUP_SCHEDULE_ENABLED=true
BACKUP_SCHEDULE_TIME=01:30
MYSQLDUMP_BINARY=mysqldump
```

Laravel's scheduler then runs `security:backup` daily using the application timezone and `withoutOverlapping()`.

The server still needs the normal scheduler trigger, typically:

```cron
* * * * * cd /var/www/microfinance && php artisan schedule:run >> /dev/null 2>&1
```

Replicate successful bundles to a separate encrypted host/object-storage process managed by infrastructure. The application intentionally does not embed cloud credentials in its backup command.

## 5. Pre-restore checklist

1. Identify the required recovery-point bundle.
2. Confirm you have the matching deployed application code/release and the original `APP_KEY` plus database/service secrets.
3. Copy the bundle onto the recovery server using an encrypted channel.
4. Verify it **before** changing production data:

```bash
php artisan security:backup-verify /path/to/bundle
```

5. Take a safety backup of the current environment if it is still readable.
6. Put the application in maintenance mode:

```bash
php artisan down
```

7. Stop queue workers/schedulers that can write financial or workflow records during restore.

## 6. Restore the database

The backup is a logical SQL dump. Restore into an empty database using the same database engine recorded in `manifest.json`.

### SQLite

Move the existing database aside, create an empty target, then import:

```bash
mv database/database.sqlite database/database.sqlite.pre-restore
sqlite3 database/database.sqlite < /path/to/bundle/database.sql
```

Set the file owner/permissions back to the application user.

### MySQL / MariaDB

Create or empty the intended database, then import with credentials supplied securely by the environment/operator:

```bash
mysql -h 127.0.0.1 -P 3306 -u microfinance -p microfinance < /path/to/bundle/database.sql
```

Do not place the database password directly in shell history or command documentation.

## 7. Restore private documents

Restore the bundle's private tree to the configured local disk root. On the standard deployment:

```bash
rsync -a --delete /path/to/bundle/private/ storage/app/private/
```

Then restore ownership and restrictive permissions appropriate to the web/PHP user. Do **not** expose `storage/app/private` with a public symlink. The application's authenticated download endpoints must remain the access path.

## 8. Bring the application back safely

1. Confirm `.env` has the original `APP_KEY` and correct production secrets.
2. Clear stale cached configuration/routes/views:

```bash
php artisan optimize:clear
```

3. Confirm the restored code and database are from compatible versions. Do not blindly run new migrations before validating the recovered point.
4. Run a focused smoke check while still in maintenance mode from an administrative shell/test environment:
   - Database connectivity.
   - User/role/branch assignments.
   - Financial account balances and recent transaction references.
   - Customer/KYC document existence.
   - Loan contract/document existence.
   - Collateral attachment existence.
   - Expense/financial evidence existence.
   - 2FA decryption for a controlled recovery account.
5. Resume workers/scheduler.
6. Bring the web application online:

```bash
php artisan up
```

7. Confirm an authenticated operator can open the dashboard, a known customer, a loan, a private document, a financial report and an audit-log record.

## 9. Recovery validation

After restore, reconcile at minimum:

- Total available funds against financial-account transactions.
- Outstanding principal against loan schedules.
- Recent collections/disbursements against permanent transaction references.
- Latest confirmed daily closing against the restored ledger.
- A sample of private documents against manifest checksums from the recovery bundle.

Do not create balancing transactions merely to make a restore appear correct. Investigate any mismatch before reopening financial operations.

## 10. Operational testing cadence

- Review backup command output every day when scheduling is enabled.
- Verify off-host copied bundles after transfer.
- Perform a test restore into an isolated environment at least quarterly and after material infrastructure/database changes.
- Record recovery-test date, selected bundle, checksum result, restore duration, reconciliation result and responsible operator in the organization's operational log.
