# Docker-free Laravel hosting

For the prepared upload package and numbered cPanel steps, start with [CPANEL_DEPLOYMENT.md](CPANEL_DEPLOYMENT.md).

Zentrik is a standard Laravel 13 application. Its simple hosting profile uses PHP, MySQL/InnoDB, private local files, file sessions/cache, database queues where needed and one cron entry. It needs no Docker, Redis, Reverb, MinIO, Vault, Cloudflare, public OCR service or cloud account. Existing optional SSO/webhook/domain/licence adapters are not activated by this setup.

## Hosting requirements

- PHP 8.3+ with the Composer-required extensions, including PDO MySQL, OpenSSL, GD, mbstring, bcmath, XML/DOM, zip, ctype, fileinfo, tokenizer, session and curl. Use the same PHP version for the web application and CLI/cron.
- MySQL 8+ or a compatible MariaDB version, using InnoDB for every table and connection default. The deployed database account needs the normal Laravel/migration privileges; production uses a dedicated account with a password. The existing local `zentrikhrms` / root / blank-password development configuration is preserved locally and must not be exposed publicly.
- Composer dependencies, the included compiled `public/build` assets, writable `storage` and `bootstrap/cache`, HTTPS from the hosting panel/server, and cron support.
- The domain document root must be **the project's public directory**, never the project root. Keep `.env`, vendor, source, uploads, backups and document master keys outside the web root. Do not create a public storage link to the vault.
- A functioning malware scanner is required to release uploaded documents. Linux uses a host-provided ClamAV daemon on loopback or a private permitted endpoint. No scanner bypass is provided. Unavailable scanning leaves uploads quarantined.

A VPS (including one managed with cPanel) can install all prerequisites. Shared cPanel hosting can run the app when the provider supplies PHP/cron/private storage and scanner access. **OCR additionally requires native binaries, PHP process execution and working user/network/mount namespaces through bubblewrap.** If those are prohibited, keep OCR disabled; host restrictions cannot safely be bypassed by the app. Manual dates remain independent of OCR, but verification still requires a file that passed scanning.

## Fresh installation

1. Place the project outside `public_html`, for example `/home/account/zentrikhrms`. Install PHP dependencies with `composer install --no-dev --prefer-dist --optimize-autoloader` if they were not installed in the release package. Compiled browser assets are included; Node is only needed when rebuilding assets.
2. Copy `.env.production.example` to `.env`, set APP_URL to your HTTPS domain and fill the hosting database name/user/password. cPanel may supply an account-prefixed database name; use the actual name provided by the panel. Keep `HOSTING_PROFILE=simple`, local private storage, file cache/sessions and broadcasting null.
3. For a **new** installation only, run `php artisan key:generate --force`. For an existing app, preserve its APP_KEY exactly so MFA and encrypted settings remain readable.
4. Run `sh deployment/install.sh company-slug "Company Name" company-admin@example.com`. It creates/validates the private document key, applies migrations, verifies InnoDB and prompts for an initial company administrator password. The installer refuses a database containing users. Never use migrate:fresh on customer data.
5. Create the separate Super Admin with `php artisan zentrik:admin platform-admin@example.com`. Sign in; optionally enable MFA in Account security. OCR starts disabled for every tenant.
6. Set the domain document root to `/home/account/zentrikhrms/public`. Apache/cPanel uses the included `public/.htaccess`; Nginx has a template in `deployment/nginx.conf.example`. Configure HTTPS through the hosting panel/server and adjust PHP upload/post limits to allow the 10 MiB document limit (post_max_size at least 12M).
7. Add the cron below, run one `php artisan zentrik:maintenance` pass, then inspect `php artisan zentrik:readiness` and the web `/ready` endpoint. Readiness reports heartbeat/configuration gaps; it does not certify penetration testing or user acceptance.

## One cron entry, no queue daemon

Use your panel's actual PHP CLI path and an absolute application path:

```cron
* * * * * cd /home/account/zentrikhrms && /usr/local/bin/php artisan zentrik:maintenance >/dev/null 2>&1
```

This bounded, locked pass handles reminders, leave accrual, scanning retries, workflow/equipment monitoring, audit outbox and in-app bell delivery. It does not drain arbitrary queued jobs or send webhooks. Email stays off by default; in production, a company administrator can configure and enable new notification email in Settings → Email & SMTP. Local automatic email stays paused. Choose either this direct cron command **or** `php artisan schedule:run`; with `HOSTING_PROFILE=simple`, the Laravel scheduler registers only the same maintenance pass. Do not schedule both. In-app notifications use polling, so no WebSocket server is needed.

Review private logs and `php artisan zentrik:readiness` if a pass fails. File cache and its lock directory must be writable only by the application account. Company SMTP is opt-in through Settings → Email & SMTP; it uses the same production cron. Webhooks/general queue workers still require separate configuration.

## Native OCR, optional

On Debian/Ubuntu VPS, an administrator can run `sudo sh deployment/setup-native-ocr.sh`. As the **unprivileged application account**, run `php artisan zentrik:ocr:check`. The command tests the actual sandbox, engine and PDF renderer and leaves client flags unchanged. Some distributions restrict unprivileged namespaces with security policies; the administrator must provide an appropriate supported policy. Do not globally disable host protections to make OCR work.

Only after the check passes can Super Admin enable OCR for an individual tenant. The worker runs without network access, application folders or keys; only system libraries and its one source file are mounted read-only. The root filesystem is read-only, temporary storage is a private 128 MiB tmpfs and disappears with the sandbox. Process/file/address-space/CPU limits and a hard deadline apply. See `TESSERACT_OCR.md` for the exact boundary.

For the existing Windows/Wamp development installation, the native worker is configured through the separate Debian WSL distribution as the unprivileged `zentrikocr` user. Laravel remains on Wamp; OCR is still off. A Linux VPS runs binaries directly and does not need WSL. No Docker engine or image is involved.

## Malware scanning

Ask the hosting administrator to provide ClamAV with current signatures. For a local TCP daemon, bind it only to loopback (`TCPAddr 127.0.0.1`, `TCPSocket 3310` in the host's clamd configuration), use suitable size/time limits, and keep signature updates working. Set `DOCUMENT_SCANNER=clamav`, `CLAMAV_HOST=127.0.0.1`, `CLAMAV_PORT=3310`. Do not expose this port publicly. Shared hosting needs the provider's supported private scanner endpoint; a panel's unrelated periodic file scan is not automatically proof that an individual upload passed.

The current Windows development installation retains its verified Defender adapter. It does not require Docker either.

## Existing installation migration and key recovery

Preserve the database, private vault, APP_KEY and any external/private files. `DOCUMENT_KEY_PROVIDER=local` selects a separate random 32-byte document master key. `php artisan zentrik:keys:initialize` never overwrites an existing key. The file is private (0600 on Linux, current Windows user/System ACL locally), outside the public directory and separate from the database. Back it up securely, separately from encrypted document backups. Losing it makes affected files/backups unrecoverable. Never regenerate APP_KEY during migration.

Before leaving a Windows DPAPI or Vault installation, run `php artisan zentrik:keys:migrate-local` for a count, then `php artisan zentrik:keys:migrate-local --apply` **on the original host with its original provider available**, after a verified backup. It changes only wrapped data-key envelopes; encrypted file bytes, IDs, dates, holds and records are retained. Completed versions are skipped on rerun. The current development database had no document versions to convert.

Old encrypted backup manifests retain their original providers. Keep the old identity/keys until you create and rehearse a new full portable backup. Do not copy a DPAPI-only database/vault to Linux and expect Windows keys to decrypt there.

## Backups, upgrade and rollback

The application backup command uses a native mysqldump/mariadb-dump binary; point `MYSQLDUMP_BINARY`, `MYSQL_CLIENT_FLAVOR` and `MYSQL_BINARY` at the host tools. On shared hosting where these commands/privileges are restricted, use the provider's consistent database and private-file backups plus a separately protected master-key/APP_KEY backup. Restore to an isolated target first. The built-in restore drill refuses an existing target and uses a new `*_recovery_test` database.

For an upgrade, stop cron temporarily, enter maintenance mode, make a consistent full backup and retain the previous release. Deploy code/dependencies/assets while preserving `.env`, storage and keys, apply `php artisan migrate --force` and `php artisan zentrik:attendance:upgrade`, verify InnoDB, refresh config/views, run smoke tests, then bring the app up and resume cron. Avoid blanket re-seeding existing customer data.

A code rollback selects the previous compatible release/document-root pointer while preserving shared configuration/storage/keys. Do not automatically reverse migrations; assess schema compatibility or rehearse a full recovery to a separate database before switching. `scripts/upgrade.ps1` and `scripts/rollback.ps1` provide corresponding local operator steps without containers.

## Evidence and limits

See `HOSTING_VERIFICATION.json` for execution results and `HOSTING_LOCAL_VERIFICATION.json` for the installed provider/storage/account checks. Source conversion and local Windows/Linux verification do not constitute a deployment to an unspecified VPS/cPanel account. Actual panel paths, permissions, scanner access, PHP process restrictions, certificates and cron delivery must be verified on that host. Older phase/audit reports are historical and are superseded by this hosting guide.

### Windows antivirus acceptance check

The real Windows Defender EICAR acceptance test is skipped during normal regression runs. It intentionally creates a harmless antivirus test file and triggers a Defender notification. Run it only when deliberately verifying antivirus detection by setting the process environment variable `ZENTRIK_RUN_DEFENDER_EICAR_TEST=1`. The scanner cleans up its temporary files; runtime upload scanning remains enabled regardless of this test flag. Do not restore detected test files, disable antivirus, or add project exclusions to run the application.
