# Operations and maintenance

## Health and errors

`php artisan multiscan:doctor` verifies database, Redis, production settings, private quarantine write access and enabled local scanner health. `--remote` also performs credentialed provider connection tests. A connection test checks the API endpoint, not full file-submit/poll/paid-plan entitlement. Always test a harmless file through the full staging pipeline.

Inspect `systemctl status multiscan-scanner multiscan-queue multiscan-scheduler.timer`, `journalctl -u multiscan-scanner -u multiscan-queue`, the Laravel daily logs and Admin → Provider Logs. Laravel logs use warning level by default. Provider request logs keep status/code/duration, not raw sensitive payloads. Failed queue jobs can contain request identifiers and encrypted mail payloads; restrict DB access. Keep debug disabled.

Scan statuses are queued, running, completed, partial and failed. Run statuses also include polling and waiting_quota. A scan with no successful result is failed. Failed or partial results do not mean clean. Local socket unavailability, stale signatures and timeout produce explicit failure codes. The cleanup scheduler marks overdue runs failed and removes expired scans/files. A temporary queue outage is recovered for queued runs by the five-minute recovery schedule, within the 20-minute run deadline.

## Retention

At submission, expiration is fixed from the current effective plan: Guest 1 day, Free 7 days, Premium 30 days, Business 90 days by default. Later plan changes do not retroactively extend existing retention. Every five minutes, expired reports/quarantine are removed. Complete scans can be deleted by their owner. Orphan quarantine files older than one hour are removed when they have no DB record. Stale quota buckets are removed after a day, provider request logs after 30 days, audit logs after 365 days, webhook IDs after 90 days, and failed queue jobs after 7 days. Account records remain until an authorized administrative data-retention process removes them. Backups need a separate documented retention policy.

Monitor disk capacity and malware quarantine backup policy. Do not back up samples into a publicly downloadable location. The application never provides original-file downloads. Use host filesystem quotas/monitoring to bound total storage. Protect raw request/access logs, which can contain URL paths and identifiers.

## Signature/rule updates

Use the chosen ClamAV installation's vendor updater, separately from the network-isolated scanner service. Verify recent official DB files and actual EICAR detection. No official databases are bundled.

For YARA, review rule source and license as root. Add rules to a root-controlled source file (or include tree), compile into a temporary root-owned `.yarc`, run a benign and known test match, then atomically replace `/etc/multiscan/rules.yarc` and restart the scanner. The cPanel account must not write the rules, scanner daemon or scanner environment. Rules may generate false positives and do not replace antivirus coverage.

## Secrets and billing

Store `.env` and APP_KEY backups encrypted and access-controlled. Rotate provider keys by entering a replacement in Engines. Blank means preserve; key deletion can be done through an authorized server-side operation or by disabling the engine and replacing the credential securely. API key text is not displayed in forms or exports. SMTP password behaves the same way. Secrets are encrypted at rest with Laravel APP_KEY, which must be protected separately from the database.

Stripe webhooks are replay-idempotent and retrieve canonical subscription state, protecting against out-of-order status grants. If webhook delivery fails, inspect Stripe's delivery dashboard and rerun signed deliveries; scheduled reconciliation runs hourly. If the last canonical status check is older than 48 hours, paid access falls back to Free. To change Stripe environment keys/prices, coordinate all settings and rebuild config/restart queues. A live bank settlement or refund workflow is outside local contract testing.

## Backups/upgrades

Back up the MySQL database, APP_KEY and approved configuration before schema changes. Decide explicitly whether quarantine samples belong in backups. Track installed scanner, PHP, OS and dependency updates. Run Composer/npm audits and staging tests with the lockfiles before upgrades. Install only the reviewed lockfile in production; do not run `composer update` or `npm update` on the live service.

Use `php artisan down` during incompatible schema changes, stop queues when necessary, migrate with the matching release and restart. Never downgrade schemas blindly on a live database. Use a tested restore procedure and retain the previous compatible release. Systemd service paths must be adjusted if the release directory changes.
