# Zentrik HRMS: simple cPanel deployment

Use the prepared `zentrikhrms-cpanel-attendance.zip` for a **fresh installation**. It contains production Composer dependencies, compiled CSS/JavaScript and empty runtime directories. It excludes local accounts, customer/demo data, `.env`, private documents, keys, databases, development dependencies and cached local configuration. No Node.js, Composer, Docker, Redis, WebSocket server, Tesseract installation or Cloudflare configuration is needed on the host for this setup.

The steps below create a new company and Super Admin. To move the existing local data instead, use the migration section at the end; do not run the fresh installer over an imported database.

## 1. Check the hosting plan

Ask the provider for:

- PHP **8.3 or newer** for the website and CLI, with bcmath, ctype, curl, DOM/XML, fileinfo, filter, GD, hash, intl, mbstring, OpenSSL, PDO MySQL, session, tokenizer and zip. The package is checked against its locked production dependencies; `composer check-platform-reqs --no-dev` is an optional additional check if Composer is available.
- MySQL 8+ or compatible MariaDB with **InnoDB**, database migrations and normal application read/write privileges.
- cPanel Terminal/SSH, or support assistance running the one-time installation commands; Cron Jobs every minute.
- A domain/subdomain whose document root can point to `/home/CPANELUSER/zentrikhrms/public`, and a local HTTPS certificate through cPanel SSL/TLS Status / AutoSSL.
- Private ClamAV daemon access for uploaded-document scanning. Its host/port must come from the provider; cPanel's periodic Virus Scanner alone is not this API.

OCR remains disabled and does not need process execution or OCR binaries. If ClamAV is unavailable, the rest of the app can run, but uploaded documents stay encrypted in quarantine and cannot be downloaded or verified. Do not disable scanning to work around this.

## 2. Upload and extract

In **cPanel → File Manager → Settings**, enable **Show Hidden Files**. Upload the ZIP to your account home, outside `public_html`, and extract it. The ZIP contains a top-level `zentrikhrms` folder, resulting in:

```text
/home/CPANELUSER/zentrikhrms/
    artisan
    .env.production.example
    vendor/
    public/index.php
    public/.htaccess
    public/build/
    storage/
```

Remove the uploaded ZIP after extraction. Keep the original download privately. Do not upload the ZIP or entire project into the website's public document root.

## 3. Set the domain and HTTPS

In **Domains**, create/select a dedicated domain or subdomain, such as `hr.yourdomain.com`, and set its document root to:

```text
/home/CPANELUSER/zentrikhrms/public
```

Enable HTTPS through cPanel **SSL/TLS Status / AutoSSL**, then enable **Force HTTPS Redirect** if offered. Point the domain at your hosting server using the normal DNS records your provider gives you. Cloudflare is not required.

Some hosts restrict document roots to `public_html`, and cPanel does not let users change the main domain's root through this screen. In either case, ask support to provide this document root or use a supported addon domain/subdomain. Do not serve the project root or move `index.php` into it.

## 4. Create the MySQL database

Open **MySQL Database Wizard** (or **Manage My Databases**):

1. Create database `zentrikhrms`.
2. Create a dedicated user with a strong password.
3. Assign that user to the database with **ALL PRIVILEGES** for installation/migrations.

cPanel usually prefixes both names. For example, the actual names may be `account_zentrikhrms` and `account_zentrik`. Copy the exact names shown. Use your provider's database hostname, usually `localhost`. Do not use the local development root account or its blank password on hosting.

## 5. Create the production .env

In File Manager, copy `.env.production.example` to `.env`. Edit these values:

```dotenv
APP_ENV=production
APP_DEBUG=false
APP_URL=https://hr.yourdomain.com
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=account_zentrikhrms
DB_USERNAME=account_zentrik
DB_PASSWORD="YOUR_STRONG_DATABASE_PASSWORD"
MAIL_FROM_ADDRESS=hr@yourdomain.com
```

If a password contains `$`, use single quotes around the value to avoid environment interpolation. Keep the remaining simple-profile settings, especially:

```dotenv
HOSTING_PROFILE=simple
DOCUMENT_KEY_PROVIDER=local
DOCUMENT_SCANNER=clamav
CLAMAV_HOST=127.0.0.1
CLAMAV_PORT=3310
OCR_PROVIDER=tesseract
LOCAL_DEMO_PASSWORD_ONLY_USER_IDS=
SESSION_DRIVER=file
SESSION_SECURE_COOKIE=true
CACHE_STORE=file
QUEUE_CONNECTION=database
BROADCAST_CONNECTION=null
MAIL_MAILER=log
DEPLOYMENT_MODE=saas
```

Replace ClamAV host/port only with the provider's private endpoint. `OCR_PROVIDER=tesseract` selects the engine; it **does not enable OCR**. New clients have OCR disabled, and Super Admin keeps the per-client switch. Leave platform domain/proxy fields empty for direct cPanel hosting. Email is disabled initially. To enable company notification email later, open Settings → Email & SMTP, save the hosting mailbox settings, send yourself a test, then enable company delivery. The same production cron handles new emails; previous pending mail is not released. Webhooks remain paused.

## 6. Install once using cPanel Terminal

Use the same PHP version as the website. The example uses EasyApache PHP 8.3; your provider may give a different absolute CLI path. Run one command at a time and stop on errors:

```sh
cd /home/CPANELUSER/zentrikhrms
PHP_BIN=/opt/cpanel/ea-php83/root/usr/bin/php
"$PHP_BIN" -v
chmod 600 .env
"$PHP_BIN" artisan key:generate --force
PHP_BIN="$PHP_BIN" sh deployment/install.sh my-company "My Company" admin@yourdomain.com
"$PHP_BIN" artisan zentrik:admin superadmin@yourdomain.com
```

The installer prompts for the company administrator password; the last command prompts for the Super Admin password. Choose different strong passwords. The installer initializes the private document key without overwriting one, migrates the database, verifies every table uses InnoDB, creates company defaults, and caches production configuration/views. It refuses a database already containing users. Never run `migrate:fresh` on customer data.

The web/PHP/cron processes must run as the same account and be able to write `storage` and `bootstrap/cache`. The installer makes new private runtime directories accessible only to the account. If ownership differs, ask the provider to fix it; do not use `chmod 777`. Document keys must stay private (file `600`, parent directory `700`). Do not run `storage:link` for the document vault.

If you deploy source from Git instead of this prepared ZIP, run `composer install --no-dev --prefer-dist --optimize-autoloader` and build assets with `npm ci && npm run build` before these steps. The prepared ZIP already includes both outputs.

## 7. Add one cron job

Open **cPanel → Cron Jobs**, select **Once Per Minute**, and enter this command on one line, replacing both paths:

```sh
cd /home/CPANELUSER/zentrikhrms && /opt/cpanel/ea-php83/root/usr/bin/php artisan zentrik:maintenance >> /home/CPANELUSER/zentrikhrms/storage/logs/maintenance-cron.log 2>&1
```

Use exactly one maintenance cron entry. No background queue daemon or `artisan serve` process is needed. It processes reminders, leave accrual, scan retries, monitoring, audit outbox and in-app notifications. Email remains disabled until explicitly enabled in company SMTP settings. Webhooks remain paused. Check this private cron log initially; rotate it through your host's log policy.

## 8. Verify and sign in

Run in Terminal:

```sh
"$PHP_BIN" artisan zentrik:maintenance
"$PHP_BIN" artisan zentrik:storage:verify
"$PHP_BIN" artisan zentrik:readiness
```

Storage verification must report **All application tables and the session default use InnoDB**. Readiness should pass; resolve reported gaps with the provider before using customer data. Readiness checks database/configuration/keys and maintenance heartbeats; it does not test ClamAV. Confirm scanner connectivity and a successful upload scan separately in the document test below.

Open `https://hr.yourdomain.com/login`. Sign in with the accounts created in step 6 and enroll MFA. Local test-account MFA exceptions are disabled in production. In Super Admin → Tenants, confirm **OCR disabled**. Set the company's timezone and review roles. Add an employee and a document, enter the expiry date manually, and verify it only after its upload passes scanning. Confirm renewal/expiry indicators and notifications.

Visit `/up` and `/ready` on your domain. Check styles/icons on desktop and mobile. In **MultiPHP INI Editor**, set `upload_max_filesize=10M`, `post_max_size=16M`, and `memory_limit=256M` or the provider's suitable larger limit. Confirm the cron log receives a successful pass after one minute.

## 9. Back up before real use

Arrange automatic database and private-storage backups. Preserve `.env` / its `APP_KEY`, and back up `storage/app/private/keys/documents.key` separately with restricted access. Losing the keys can make encrypted documents, MFA and settings unrecoverable. Backups must stay outside `public/`. Test a restore into a separate database and directory.

## Moving the existing local database and documents

The deployment ZIP is code only; it contains no local demo accounts or documents. To retain them, pause local writes/maintenance, take a consistent database export and private-storage backup, and transfer these plus the **original APP_KEY and document master key** over a private channel. Do not place them in Git or a public upload directory. Ensure every encrypted document uses a portable `local` key envelope before leaving the original host; the detailed procedure is in [HOSTING.md](HOSTING.md#existing-installation-migration-and-key-recovery).

Import the database into the new cPanel database. Preserve APP_KEY exactly instead of running `key:generate`; preserve the master key at the configured path, with Linux file permissions `600`. Do not run the fresh installer or recreate existing accounts. On an isolated restored copy first, then the intended host, run:

```sh
"$PHP_BIN" artisan config:clear
"$PHP_BIN" artisan migrate --force
"$PHP_BIN" artisan zentrik:attendance:upgrade
"$PHP_BIN" artisan zentrik:storage:verify
"$PHP_BIN" artisan config:cache
"$PHP_BIN" artisan view:cache
```

Use the existing accounts, enroll/use MFA, confirm all tenant OCR switches remain off, and test private document retrieval before switching users over. The local root/blank database credentials must be replaced with the hosting credentials. See [HOSTING.md](HOSTING.md) for upgrades and recovery.

## Quick troubleshooting

| Symptom | Check |
| --- | --- |
| 500 error | PHP version/extensions, private `storage/logs/laravel-*.log`, `.env` APP_KEY, runtime ownership. Keep APP_DEBUG=false. |
| 404 except homepage | Document root must end in `/public`; enable hidden files and confirm `public/.htaccess` and Apache rewrite support. |
| CSS/icons missing | `public/build/manifest.json` and its asset files must exist; APP_URL must match the HTTPS domain. |
| Database access denied | Exact cPanel-prefixed DB names/user, assigned privileges, password and hostname; run `config:clear` then `config:cache` after editing `.env`. |
| Login returns 419 | HTTPS matches APP_URL, SESSION_DOMAIN blank, secure cookies enabled, writable sessions directory; clear browser cookies. |
| Document quarantined | ClamAV endpoint/current signatures unavailable; do not bypass scanning. |
| Reminder processing delayed | Cron PHP path, private cron log, ownership and maintenance heartbeat/readiness. |

References: [Laravel deployment](https://laravel.com/docs/13.x/deployment), [cPanel domain document roots](https://docs.cpanel.net/cpanel/domains/domains/manage-the-domain/), [cPanel Cron Jobs](https://docs.cpanel.net/cpanel/advanced/cron-jobs/). Exact menu names and paths depend on the provider.

After upgrading, Super Admin can open **Platform settings > Email & SMTP** to configure password-recovery email, then test to their own account and enable delivery. It starts disabled. Each company retains a separate **Company settings > Email & SMTP** connection for reminders. The company provisioning form and tenant management page now include branch creation, branch count, user count and employee count controls. See [Company administration](COMPANY_ADMINISTRATION.md) for role presets and onboarding.
