# AlmaLinux 9 + cPanel/WHM deployment

Browser installation is available: see `BROWSER-INSTALLER.md`. The root preparation steps below apply to both browser and manual installation. The wizard replaces the manual `.env`, migration, seeding and administrator steps in section 5. Do not create `.env` before using the wizard. Root provisions system packages/services; the cPanel account owns and runs Laravel. Do not run Composer or the web application as root. Scripts are supplied for review; they were syntax-checked locally but have not been executed on your VPS.

## 1. Prepare staging and backups

Use a staging domain first. Back up existing cPanel configuration, database and application files. Use a dedicated cPanel account for this scanner. Plan for at least 4 GiB of RAM (preferably 8 GiB when running multiple ClamAV scans), adequate CPU and a quarantine disk quota. Two scanner children can run concurrently. Set hosting account storage limits and monitor free space; the CMS has per-file/request limits but does not replace filesystem capacity planning.

Keep the existing cPanel database and web server packages. Do not install an OS Apache/PHP/MySQL replacement over cPanel. Confirm `SELECT VERSION()` is MySQL 8, and select EA-PHP 8.3 for the domain in WHM. PHP needs ctype, curl, fileinfo, intl, mbstring, OpenSSL, PDO MySQL, tokenizer, XML/DOM and ZIP. CLI workers also need pcntl/posix (EA php-process).

Use a current supported Composer 2 installation. The installed `vendor/` tree and compiled frontend are included; a locked `composer install` is still required to validate your platform. Node 22+ is needed only to rebuild frontend assets with `npm ci && npm run build`.

## 2. Place the application and set the document root

As the cPanel account, extract `multiscan/` to `/home/ACCOUNT/multiscan` (replace ACCOUNT in every command below). The public domain document root MUST be `/home/ACCOUNT/multiscan/public`. Use a cPanel domain/subdomain whose document root can be set accordingly, or the supported WHM userdata include process. Do not expose the project root, `vendor/`, `.env`, `storage/`, scanner sources or deployment scripts. Do not use `php artisan storage:link` for quarantine.

Enable TLS/AutoSSL for the domain. `deploy/apache-vhost-notes.conf` describes the supported include and upload limits. The shipped root `.htaccess` denies direct root access; `public/.htaccess` contains Laravel rewrite rules and access for the public directory. Keep both files when extracting.

In WHM's domain PHP-FPM settings, set `upload_max_filesize=32M`, `post_max_size=36M`, `memory_limit=256M`, `max_execution_time=60`, and `expose_php=Off`. Set the web request body limit to at least 36 MiB. Disable script execution/CGI in any other account upload locations as part of the host's policy.

## 3. Database and Redis

Create a cPanel MySQL database and database user, grant that user access to this database only, and use a strong password. Runtime needs CRUD plus the migrations' DDL privileges during deployment; separate migration credentials may be used under your operations policy.

Install Redis from the approved AlmaLinux repository if it is not already installed (`dnf install redis`), enable its system service, and review its configuration. Bind only to loopback, keep protected mode enabled, configure a strong password or a dedicated ACL, and do not publish port 6379. This application uses Predis, so a PHP Redis extension is not needed. Use a dedicated Redis instance or database and prefix for this application. Redis DBs are operational namespaces, not security isolation from other tenants with the same Redis credentials. A dedicated instance/ACL is preferable on a shared VPS.

No script here changes the existing Redis or MySQL installation automatically.

## 4. Root scanner provisioning

From the extracted application, as root:

```bash
bash deploy/provision-almalinux.sh
```

The script installs EPEL scanner packages and EA-PHP extensions, creates the locked `multiscan-scanner` user and `multiscan` group, creates private quarantine at `/var/lib/multiscan/quarantine`, copies a root-owned daemon to `/opt/multiscan/scanner`, compiles YARA rules and installs the scanner unit. It does not start the scanner until definitions and configuration are reviewed.

Review `/etc/multiscan/scanner.env`. Package paths differ between OS ClamAV and cPanel's optional ClamAV plugin. The supplied path uses `/usr/bin/clamscan` with `/var/lib/clamav`. If your chosen approved scanner installation uses different binaries/database paths, change this root-owned environment file and the systemd `ReadOnlyPaths` entry to match. Do not make scanner executable/rule/config paths writable by the cPanel account.

Review the vendor `freshclam` configuration, initialize official definitions using the selected installation's `freshclam`, and enable its packaged updater (the unit name can vary; inspect `systemctl list-unit-files '*freshclam*'`). Do not run two updater processes against one database. ClamAV needs official fresh databases; the daemon refuses a missing database or one whose newest signature file is over 72 hours old. Do not touch database timestamps to bypass freshness checks. Monitor successful updater runs and the dates in scan reports.

The scanner service has no network access. Signature updates run separately with the vendor updater. YARA rule updates are an explicit root-controlled review/compile/restart operation; see OPERATIONS.md.

## 5. Configure Laravel (cPanel account)

```bash
cd /home/ACCOUNT/multiscan
cp .env.example .env
chmod 600 .env
/opt/cpanel/ea-php83/root/usr/bin/php /usr/local/bin/composer install --prefer-dist --no-interaction
/opt/cpanel/ea-php83/root/usr/bin/php artisan key:generate
```

Edit `.env` locally on the VPS: real HTTPS `APP_URL`, DB credentials, Redis credentials/DBs, SMTP sender and authentication. Keep `APP_DEBUG=false`, `APP_ENV=production`, Redis queue/cache/session drivers, `SESSION_ENCRYPT=true` and secure cookies. Keep the generated APP_KEY secret and backed up: it encrypts provider/SMTP credentials, sessions and queued notifications. Never regenerate it on an existing installation without a planned key rotation and re-encryption.

The database must be reachable before running Artisan, since persisted site settings are applied during boot. During initial install, a missing settings table is tolerated. Do not paste keys into command arguments, tickets or public logs.

Only the cPanel account needs write access to `storage/` and `bootstrap/cache/`. Use owner permissions and narrowly scoped ACLs, never `chmod -R 777`. The source and public assets can otherwise be read-only for the worker. Leave the private quarantine outside the web root.

```bash
/opt/cpanel/ea-php83/root/usr/bin/php artisan migrate --force
/opt/cpanel/ea-php83/root/usr/bin/php artisan db:seed --force
/opt/cpanel/ea-php83/root/usr/bin/php artisan multiscan:admin
/opt/cpanel/ea-php83/root/usr/bin/php artisan filament:assets
/opt/cpanel/ea-php83/root/usr/bin/php artisan optimize
```

The administrator command prompts for credentials without echoing the password. No default administrator is seeded. The seeder preserves existing plans and engine settings on repeat runs.

## 6. Install root-managed services

As root:

```bash
bash /home/ACCOUNT/multiscan/deploy/install-services.sh ACCOUNT /home/ACCOUNT/multiscan
```

This assigns the account to the scanner group, renders units, and enables scanner, queue worker and scheduler timer. Restart the domain's PHP-FPM pool in WHM so its supplementary group membership is refreshed. If the web worker cannot open `/run/multiscan/scanner.sock`, verify the actual PHP-FPM process groups and path permissions before changing anything broadly.

The queue worker processes both `scans` and `default` (verification/reset emails). It uses a 120-second worker timeout; Redis retry-after is 180 seconds. The scanner subprocess timeout is 90 seconds. Do not lower retry-after below the worker timeout. The scheduler handles expiry, stranded submissions, failed job retention and hourly membership sync.

If SELinux is enforcing, apply narrowly scoped contexts/permissions for the account's `storage`, `bootstrap/cache`, quarantine and scanner socket, using host policy and AVC audit evidence. cPanel configurations differ. Do not disable SELinux or grant broad root capabilities to make a check pass. The needed SELinux policy is a live VPS gate.

## 7. Configure providers, SMTP and subscriptions

Open `/admin` with the verified administrator account. In Engines, set credentials, account-specific quotas/priorities, licensing confirmation and enable state. Use the connection-test action. Credentials are encrypted in the database and are not sent back into the edit form; a blank replacement field preserves the existing key. Connection tests consume provider requests. They check endpoint authentication, not the legal scope of your agreement.

In Settings, save the site name, operator/contact details, additional privacy/terms text and SMTP settings. Test SMTP to the administrator's own address and verify a real registration/reset email. Payment credentials remain in `.env`, not a form.

Create recurring Stripe prices for Premium/Business and save their Price IDs in Plans. Configure `STRIPE_SECRET`, `STRIPE_WEBHOOK_SECRET`, and the optional publishable key in `.env`. The website uses hosted Checkout and does not require client-side card handling. Create an HTTPS webhook for `/billing/webhook` with `checkout.session.completed`, `customer.subscription.created`, `customer.subscription.updated`, and `customer.subscription.deleted`. Configure the Stripe billing portal for cancellation and payment management. Run test-mode payment/cancellation/replay cases first, then switch keys and prices together. After changing `.env`, clear/rebuild config and restart queue workers.

The website displays subscription pricing in Stripe Checkout. Do not enable paid plans until prices, taxes, billing period, refund/cancellation and operator terms are complete and reviewed.

## 8. Staging acceptance and release

Run `php artisan multiscan:doctor`, then `php artisan multiscan:doctor --remote` only after authorized credentials are configured. Complete `docs/ACCEPTANCE.md`; this local build does not certify your VPS. Do not run destructive test suites against the production database.

For later locked releases, from the configured release directory as the account:

```bash
bash deploy/release.sh
```

This installs production PHP dependencies from the lockfile, enters maintenance mode, migrates/seeds, optimizes, publishes Filament assets, restarts queues and runs doctor. It exits nonzero if doctor fails and restores the application from maintenance mode via a trap. Deploy only a release that has passed staging gates. Take database/quarantine backups before migrations; rollback requires the matching database backup if a schema migration is incompatible.
