# TaxPilot License Server — cPanel Deployment Runbook

**Target:** `https://taxpilot.tmsoagency.com`
**Deploy package:** `taxpilot-license-server-deploy.tar.gz` (~24 MB, app + `vendor/`, no secrets)
**Assumption:** browser-only cPanel (no SSH/FTP), same constraints as the CMS host. Everything below is done through File Manager, MySQL Wizard, Cron Jobs, MultiPHP, and SSL/TLS Status.

> The `vendor/` folder is bundled because composer isn't available on the host. Nothing here touches the live CMS (`my.lifeassociate.com`) — this is a separate app on a separate subdomain.

---

## 1. Create the subdomain
cPanel → **Domains** (or **Subdomains**) → add `taxpilot` under `tmsoagency.com`.

Set the **Document Root to the app's `/public` folder**, e.g. `taxpilot/public` (or `public_html/taxpilot/public`, depending on how your cPanel roots subdomains). The app itself lives one level **above** `public`. This is the single most important step — pointing the docroot at the app root instead of `/public` exposes `.env`.

## 2. Upload & extract
1. File Manager → open the **app dir** (the subdomain folder that *contains* `public`).
2. Upload `taxpilot-license-server-deploy.tar.gz`.
3. Select it → **Extract**. It expands to `app/ bootstrap/ config/ public/ routes/ vendor/ artisan …` right there.
4. Confirm `.../taxpilot/public/index.php` and `.../taxpilot/vendor/autoload.php` exist, and the subdomain docroot equals `.../taxpilot/public`.

## 3. PHP version
MultiPHP Manager → set the subdomain to **PHP 8.3** (the app requires `^8.3`).

## 4. Create the database
MySQL Database Wizard → create a **database**, a **user** with a strong password, and grant **ALL PRIVILEGES**. Note the cPanel-prefixed names (e.g. `tmso_taxpilot`, `tmso_lic`).

## 5. Configure `.env`
File Manager → in the app dir, copy `.env.production.example` → `.env`, then edit:

| Key | Value |
|---|---|
| `APP_URL` | `https://taxpilot.tmsoagency.com` (already set) |
| `APP_KEY` | leave blank — generated in step 6 |
| `DB_DATABASE` / `DB_USERNAME` / `DB_PASSWORD` | from step 4 |
| `OWNER_EMAIL` / `OWNER_PASSWORD` | your **real** admin login + a **strong** password |
| `MAIL_*` | a cPanel mailbox on `tmsoagency.com` (or set `MAIL_MAILER=log` to defer email) |

Keep `APP_DEBUG=false`, `SESSION_SECURE_COOKIE=true`, `CACHE_STORE=database`.

## 6. Initialize — key, tables, seed, caches
Do **one** of the following.

### 6a. cPanel Terminal (if the account has it) — fastest
```bash
cd ~/taxpilot                       # your app dir
php artisan key:generate --force
php artisan migrate --force
php artisan db:seed --force         # seeds packages + owner (from .env OWNER_*)
php artisan config:cache
php artisan route:cache
```

### 6b. One-off Cron Job (pure browser)
cPanel → **Cron Jobs** → add a job timed ~2 min out. Command on one line (replace `USER` and confirm the PHP path in MultiPHP):
```bash
cd /home/USER/taxpilot && /usr/local/bin/ea-php83 artisan key:generate --force && /usr/local/bin/ea-php83 artisan migrate --force && /usr/local/bin/ea-php83 artisan db:seed --force && /usr/local/bin/ea-php83 artisan config:cache && /usr/local/bin/ea-php83 artisan route:cache >> /home/USER/taxpilot/storage/logs/deploy.log 2>&1
```
After it runs once, check `storage/logs/deploy.log`, then **delete the cron job**.

> If a table already exists on a re-run, that's fine — `migrate` skips applied migrations and the seeder uses `firstOrCreate`.

## 7. Permissions
Ensure `storage/` and `bootstrap/cache/` (and everything under them) are writable — **755** dirs / **644** files, or **775/664** if the host runs as a separate group. File Manager → select → Permissions.

## 8. DNS & SSL
- **DNS:** point `taxpilot.tmsoagency.com` (A record) at the cPanel server IP. If `tmsoagency.com` is on Cloudflare, add the record there; **DNS-only (grey cloud)** is simplest for AutoSSL issuance — you can enable the proxy afterward.
- **SSL:** cPanel → **SSL/TLS Status** → run **AutoSSL** for the subdomain (or Let's Encrypt). Wait for the certificate before testing HTTPS.

## 9. Verify
1. Open `https://taxpilot.tmsoagency.com/login` → the owner login screen.
2. Log in with `OWNER_EMAIL` / `OWNER_PASSWORD`.
3. Dashboard loads → create a **Customer** → **generate a License** (this is the key you'll activate a customer CMS with).
4. Optional API smoke: a plain `GET`/unsigned `POST` to `/api/v1/license/verify` should be **rejected** (missing/invalid signature) — that's correct.

## 10. Post-deploy security checklist
- [ ] `https://taxpilot.tmsoagency.com/.env` returns **403/404** (proves docroot is `/public`).
- [ ] `APP_DEBUG=false` (trigger a bad URL → generic error page, no stack trace).
- [ ] Owner password is strong and **not** the local `password` default.
- [ ] AutoSSL certificate active (padlock), and **Force HTTPS Redirect** enabled (Domains → your subdomain).
- [ ] `storage/` and `.git`/`vendor` are **not** reachable over the web (they sit outside `/public`).

## 11. Connecting a customer CMS install (later — never the live CMS)
On a **new customer's** CMS `.env`:
```env
LICENSE_SERVER_URL=https://taxpilot.tmsoagency.com   # already the default
LICENSE_VERIFY_SSL=true
LICENSE_ENFORCEMENT=true          # ONLY on that customer install
```
Then `php artisan config:clear`, visit the CMS → it redirects to `/activate` → enter the license key from step 9.

> ⚠️ **Never** set `LICENSE_ENFORCEMENT=true` on `my.lifeassociate.com` — it serves 3,945 real clients and must stay a no-op. Enforcement is only ever turned on for a *fresh* licensed customer install.
