# Deploying Offer Hat on an aaPanel PHP VPS

This is the full, beginner-friendly guide to putting Offer Hat live on a VPS running
**aaPanel** (Linux + Nginx + PHP 8.3 + MySQL). Budget ~30–45 minutes.

---

## 0. Prerequisites

- A VPS (Ubuntu 22.04 / AlmaLinux recommended), 1 GB+ RAM.
- aaPanel installed (https://www.aapanel.com → install command for your OS).
- A domain pointed (A record) to the VPS IP.

---

## 1. Install the server stack (aaPanel → App Store)

Install:
- **Nginx** (latest)
- **MySQL** 5.7 or 8.0
- **PHP 8.3**

Then open **PHP 8.3 → Settings → Install extensions** and enable:

```
fileinfo  pdo_mysql  mbstring  bcmath  curl  gd  zip  openssl  tokenizer  xml  redis(optional)
```

Also in **PHP 8.3 → Settings → Disabled functions**: make sure `putenv`, `proc_open`,
`exec`, `shell_exec`, `symlink` are **NOT** in the disabled list (Composer + `storage:link`
need them). Remove them from the list if present.

Set **PHP → Config**: `upload_max_filesize = 16M`, `post_max_size = 16M`, `max_execution_time = 120`.

---

## 2. Create the website

**Website → Add site**
- Domain: `your-domain.com` (and `www.your-domain.com`)
- PHP version: **8.3**
- Create an FTP/DB later; for now just the site.

After it's created, the site lives at `/www/wwwroot/your-domain.com`.

> ⚠️ Laravel serves from the **`public/`** subfolder, not the project root. We fix the
> document root in step 6.

---

## 3. Create the database

**Databases → Add database**
- Name: `offerhat`
- User: `offerhat`
- Password: *(generate a strong one — save it)*

---

## 4. Upload the project

**Option A — Git (recommended):** Website → your site → open **Terminal** at the site root:
```bash
cd /www/wwwroot/your-domain.com
# remove the default index files aaPanel created
rm -f index.html .user.ini 2>/dev/null
git clone <your-repo-url> .
```

**Option B — Zip:** zip the project locally (exclude `vendor/`, `node_modules/`,
`database/database.sqlite`), upload via **Files**, and extract into the site root.

---

## 5. Configure & build

In the site **Terminal**:

```bash
cd /www/wwwroot/your-domain.com

# Environment
cp .env.production.example .env
# Edit .env (use aaPanel's file editor or `vi .env`):
#   APP_URL=https://your-domain.com
#   DB_DATABASE=offerhat  DB_USERNAME=offerhat  DB_PASSWORD=...(from step 3)
#   MAIL_* for order emails

# Install PHP deps (use the full path if `composer` isn't global)
composer install --no-dev --optimize-autoloader

php artisan key:generate
php artisan migrate --force --seed      # --seed loads sample data + admin user
php artisan storage:link

# Production caches
php artisan config:cache
php artisan route:cache
php artisan view:cache
```

If `composer` is not found, aaPanel ships it at `/www/server/php/83/bin/php
/usr/bin/composer` — or install via:
`curl -sS https://getcomposer.org/installer | /www/server/php/83/bin/php`.

---

## 6. Point the document root to `public/`

**Website → your site → Site directory**
- **Running directory / Document root:** set to `/public`
- Save.

---

## 7. Nginx rewrite for Laravel pretty URLs

**Website → your site → Config file** (Nginx), inside the `server { … }` block replace the
default `location /` with:

```nginx
location / {
    try_files $uri $uri/ /index.php?$query_string;
}

location ~ \.php$ {
    fastcgi_pass unix:/tmp/php-cgi-83.sock;   # match your PHP 8.3 socket
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}

# Deny access to sensitive files
location ~ /\.(?!well-known).* { deny all; }
```

Save and **reload Nginx** (aaPanel does this on save). The exact PHP socket path is shown
under **PHP 8.3 → it will be `/tmp/php-cgi-83.sock`** on most aaPanel installs.

---

## 8. Permissions

The web user on aaPanel is usually **`www`**. From the site Terminal:

```bash
chown -R www:www /www/wwwroot/your-domain.com
chmod -R 775 storage bootstrap/cache
```

---

## 9. HTTPS (SSL)

**Website → your site → SSL → Let's Encrypt** → select domains → Apply.
Enable **Force HTTPS**.

---

## 10. Queue & scheduler (for Phase 2 automation: SMS, courier, follow-ups)

Order emails, SMS, and courier calls run through Laravel's queue. Enable it later with a
**aaPanel → App Store → PM2/Supervisor**, or a cron:

**Cron (aaPanel → Cron):**
- Every minute, run the scheduler:
  ```
  * * * * * php /www/wwwroot/your-domain.com/artisan schedule:run >> /dev/null 2>&1
  ```
- Queue worker (keep-alive via Supervisor is better, but a simple cron works):
  ```
  php /www/wwwroot/your-domain.com/artisan queue:work --stop-when-empty
  ```

For now (Phase 1), set `QUEUE_CONNECTION=sync` in `.env` to process jobs inline — no worker needed.

---

## 11. Go live checklist

- [ ] `https://your-domain.com` loads the storefront
- [ ] `https://your-domain.com/admin/login` → log in as `admin@offerhat.test` / `password`
- [ ] **Change the admin password immediately** (and delete/disable the demo customer)
- [ ] Settings → set real bKash/Nagad/Rocket numbers, hotline, logo, theme color
- [ ] Add real products & categories (or keep samples while building)
- [ ] Place a test order end-to-end
- [ ] Set `APP_DEBUG=false` in `.env`, then `php artisan config:cache`

---

## Updating later (after code changes)

```bash
cd /www/wwwroot/your-domain.com
git pull
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache && php artisan route:cache && php artisan view:cache
```

---

## Troubleshooting

| Symptom | Fix |
|---|---|
| **500 error, blank page** | `chmod -R 775 storage bootstrap/cache`; check `storage/logs/laravel.log` |
| **404 on every page except home** | Nginx rewrite (step 7) not applied / wrong PHP socket |
| **"No application encryption key"** | `php artisan key:generate` then `php artisan config:cache` |
| **Images don't show** | `php artisan storage:link` and ensure `/public/storage` exists |
| **CSS looks broken** | CDN blocked? The app uses jsDelivr/Google Fonts — allow outbound HTTPS |
| **DB connection refused** | Check `DB_*` in `.env`, then `php artisan config:clear` |
