# Live Prawn — True Staging Clone Setup Plan

**Status:** Planning only — **do not execute until explicitly approved.**  
**Created:** 2026-06-15  
**Purpose:** Enable Customer Rewards Club order/checkout/reward testing without touching live `liveprawn.com`.

---

## Current state (problem)

| Item | Live today |
|------|------------|
| Magento root | `/var/www/html/airbenih_stg` |
| Document root | `/var/www/html/airbenih_stg/pub` |
| Database | `airbenih_stg` (misleading name — this **is** live) |
| URLs | `https://liveprawn.com`, `https://airbenih.com` → **same** Magento instance |
| True staging | **None** |
| OpenSearch | Installed 3.6.0; service **failed** (start timeout since 2026-05-28); port **9200 refused** |
| Orders on live | **0** (testing deliberately paused) |
| Disk free | ~52 GB on `/` (Magento live ~2.4 GB) |

---

## Staging target (proposed)

| Item | Staging value |
|------|---------------|
| Code path | `/var/www/html/liveprawn_stage` |
| Database | `liveprawn_stage` |
| URL (recommended) | **`https://stg.liveprawn.com`** |
| URL (alternate) | `https://stage.liveprawn.com` |
| DB user | `dev@localhost` (existing) or dedicated `liveprawn_stage@localhost` |
| Search index prefix | `liveprawn_stage` (separate from live `magento2`) |
| Magento mode | `developer` (match live for parity) |

**Recommendation:** Use `stg.liveprawn.com` — shorter, common convention, single DNS A record.

---

## 1. Exact staging clone plan (ordered steps)

Execute only after approval. Estimated downtime on live: **none** (read-only clone from live; live vhosts untouched until staging verified).

### Phase A — Pre-flight (no live changes)

1. Confirm DNS A record for `stg.liveprawn.com` → server IP **`168.144.108.100`** (add in DNS panel; can propagate while cloning).
2. Confirm disk space ≥ **8 GB** free (code ~2.5 GB + DB + media + OpenSearch headroom).
3. Schedule maintenance window for OpenSearch fix (shared service — see §10).
4. Notify team: staging will receive a **point-in-time copy** of live data; live continues unchanged.

### Phase B — Database clone

1. Create empty database `liveprawn_stage` and grant `dev` user full access (or create dedicated user).
2. Take **consistent** dump of live DB `airbenih_stg` (read-only; no live config changes):
   ```bash
   mysqldump --single-transaction --quick --routines --triggers \
     -u dev -p airbenih_stg | gzip > /root/backups/airbenih_stg_$(date +%Y%m%d_%H%M).sql.gz
   ```
3. Import into staging DB:
   ```bash
   zcat /root/backups/airbenih_stg_YYYYMMDD_HHMM.sql.gz | mariadb -u dev -p liveprawn_stage
   ```
4. **Do not** run import into `airbenih_stg` — staging DB only.

### Phase C — Filesystem clone

1. Copy Magento tree to staging path (see §2 for exact rsync command).
2. Create **new** `app/etc/env.php` for staging (see §4) — do **not** symlink live `env.php`.
3. Remove/regenerate volatile dirs on staging only (see §2 exclusions).
4. Set ownership: `dev:www-data` on `var/`, `generated/`, `pub/static/`, `pub/media/` (match live permission model).

### Phase D — Staging-only configuration

1. Update base URLs in staging DB (see §5).
2. Disable outgoing email (see §8).
3. Lock down payments to offline/test only (see §9).
4. Set OpenSearch index prefix to `liveprawn_stage` in staging `core_config_data`.
5. Add robots/noindex + HTTP basic auth (see §6–7).
6. Optional: disable Amasty affiliate emails, reward notifications, cron email jobs on staging.

### Phase E — OpenSearch + Magento upgrade path

1. Fix OpenSearch service (see §10) — required before full `setup:upgrade` on staging.
2. On staging only:
   ```bash
   cd /var/www/html/liveprawn_stage
   php bin/magento setup:upgrade
   php bin/magento setup:db:status
   php bin/magento indexer:reindex
   ```
3. Confirm `setup:db:status` → **All modules up to date**.

### Phase F — Apache + SSL

1. Add **new** vhost file (see §6) — do not modify live `liveprawn.com` blocks.
2. Enable site + obtain SSL cert (see §7).
3. Reload Apache; verify staging responds on `stg.liveprawn.com`.

### Phase G — Verification (see §11)

Run all separation checks before any order/reward testing.

### Phase H — Reward testing gate

Only after Phase G passes:

- Batch **3B** site credit checkout collector → deploy/test on **staging only**
- Order E2E: Prawn Points earn/redeem, premium activation, referral qualification
- **Do not** enable Batch 3B on live until staging sign-off

---

## 2. Files/directories to copy

### Copy (rsync from live → staging)

| Path | Notes |
|------|-------|
| `app/` | Includes `LivePrawn_CustomerRewards`, `config.php`, **new** `env.php` written separately |
| `bin/` | CLI entry |
| `lib/` | Framework |
| `pub/` | Includes `index.php`, `static/`, `media/` (~109 MB media) |
| `vendor/` | ~1.1 GB — copy for speed; or omit and run `composer install` on staging |
| `setup/` | Installer |
| `composer.json`, `composer.lock` | If using composer reinstall |
| `auth.json` | If present (Composer credentials) |
| `docs/` | Optional — planning docs |
| `.htaccess` files | Root + pub |

### Exclude (regenerate on staging)

| Path | Reason |
|------|--------|
| `var/cache/` | Staging-specific cache |
| `var/page_cache/` | FPC |
| `var/view_preprocessed/` | Regenerated |
| `var/session/` | Must not share sessions with live |
| `var/log/` | Fresh logs |
| `generated/code/` | Regenerated (developer mode) |
| `generated/metadata/` | Regenerated |
| `app/etc/env.php` | **Never copy** — write staging-specific file |

### Recommended rsync command (execute in Phase C only)

```bash
rsync -aHAX --info=progress2 \
  --exclude 'var/cache/' \
  --exclude 'var/page_cache/' \
  --exclude 'var/view_preprocessed/' \
  --exclude 'var/session/' \
  --exclude 'var/log/' \
  --exclude 'var/tmp/' \
  --exclude 'generated/' \
  --exclude 'app/etc/env.php' \
  /var/www/html/airbenih_stg/ \
  /var/www/html/liveprawn_stage/
```

Then copy `env.php` from live as **template**, edit DB name and paths (§4).

**Size estimate:** ~2.0–2.5 GB copied (with vendor); ~52 GB free — sufficient.

---

## 3. Database dump/import steps

### 3.1 Pre-dump checks (live, read-only)

```bash
mariadb -u dev -p -e "SELECT COUNT(*) AS orders FROM airbenih_stg.sales_order;"
mariadb -u dev -p -e "SELECT path, value FROM airbenih_stg.core_config_data WHERE path IN ('web/unsecure/base_url','web/secure/base_url');"
```

Record outputs — baseline for live (must not change during dump).

### 3.2 Create staging database

```bash
mariadb -u root -p <<'SQL'
CREATE DATABASE liveprawn_stage CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON liveprawn_stage.* TO 'dev'@'localhost';
FLUSH PRIVILEGES;
SQL
```

### 3.3 Dump live (no writes to live)

```bash
mkdir -p /root/backups
mysqldump --single-transaction --quick --routines --triggers \
  -u dev -p airbenih_stg | gzip > /root/backups/airbenih_stg_pre_staging_$(date +%Y%m%d_%H%M).sql.gz
```

`--single-transaction` avoids long table locks on InnoDB; live remains online.

### 3.4 Import to staging

```bash
zcat /root/backups/airbenih_stg_pre_staging_*.sql.gz | mariadb -u dev -p liveprawn_stage
```

### 3.5 Post-import staging SQL (run against `liveprawn_stage` only)

See §5 for base URL updates. Run **after** import, **before** first staging web request.

---

## 4. env.php changes needed

Create `/var/www/html/liveprawn_stage/app/etc/env.php` — staging-specific. Start from live template but change:

| Key | Live (`airbenih_stg`) | Staging (`liveprawn_stage`) |
|-----|----------------------|----------------------------|
| `db.connection.default.dbname` | `airbenih_stg` | **`liveprawn_stage`** |
| `db.connection.default.username` | `dev` | `dev` (or dedicated user) |
| `db.connection.default.password` | *(live password)* | Same or staging-only password |
| `cache.frontend.default.id_prefix` | `5c5_` | **`stg_`** (must differ) |
| `cache.frontend.page_cache.id_prefix` | `5c5_` | **`stg_`** |
| `crypt.key` | *(live key)* | **Keep same** for cloned encrypted config to decrypt; alternative: new key + re-enter API secrets |
| `session.save` | `files` | `files` (separate path via separate docroot — OK) |
| `MAGE_MODE` | `developer` | `developer` |
| `downloadable_domains` | `airbenih.com` | Add **`stg.liveprawn.com`** |
| `install.date` | *(keep)* | *(keep — clone metadata)* |

**Optional staging-only env.php additions** (if supported by custom bootstrap or documented admin config):

```php
// Not native Magento — achieve via config:set instead:
// - system/smtp/disable = 1
// - payment/* active flags
```

**Do not** point staging `env.php` at `airbenih_stg` database.

---

## 5. Base URL changes needed

Run against **`liveprawn_stage`** database only:

```sql
UPDATE core_config_data
SET value = 'https://stg.liveprawn.com/'
WHERE path IN ('web/unsecure/base_url', 'web/secure/base_url');

-- If rows missing, insert for default scope:
-- INSERT INTO core_config_data (scope, scope_id, path, value) VALUES
-- ('default', 0, 'web/unsecure/base_url', 'https://stg.liveprawn.com/'),
-- ('default', 0, 'web/secure/base_url', 'https://stg.liveprawn.com/');
```

After file deploy, on staging CLI:

```bash
cd /var/www/html/liveprawn_stage
php bin/magento config:set web/unsecure/base_url 'https://stg.liveprawn.com/' --lock-env
php bin/magento config:set web/secure/base_url 'https://stg.liveprawn.com/' --lock-env
php bin/magento config:set web/secure/use_in_frontend 1
php bin/magento config:set web/secure/use_in_adminhtml 1
php bin/magento cache:flush
```

**Cookie domain:** Magento derives from base URL — staging cookies will be scoped to `stg.liveprawn.com`, not `liveprawn.com`. Verify in browser dev tools after login.

**Admin URL:** Same `/admin` path — use `https://stg.liveprawn.com/admin`. Consider changing admin path on staging later (optional hardening).

---

## 6. Apache vhost changes needed

**Add new file** — do not edit live `liveprawn.com` vhost blocks.

Suggested path: `/etc/apache2/sites-available/liveprawn-staging.conf`

```apache
# HTTP — redirect to HTTPS after cert obtained (or serve basic auth on HTTP during bootstrap)
<VirtualHost *:80>
    ServerAdmin admin@liveprawn.com
    ServerName stg.liveprawn.com
    DocumentRoot /var/www/html/liveprawn_stage/pub

    ErrorLog ${APACHE_LOG_DIR}/stg.liveprawn.com-error.log
    CustomLog ${APACHE_LOG_DIR}/stg.liveprawn.com-access.log combined

    <Directory /var/www/html/liveprawn_stage/pub>
        Options FollowSymLinks
        AllowOverride All
        Require all granted
        DirectoryIndex index.php
    </Directory>

    <FilesMatch \.php$>
        SetHandler "proxy:unix:/var/run/php/php8.4-fpm-dev.sock|fcgi://localhost/"
    </FilesMatch>
</VirtualHost>

# HTTPS (enable after certbot)
<VirtualHost *:443>
    ServerAdmin admin@liveprawn.com
    ServerName stg.liveprawn.com
    DocumentRoot /var/www/html/liveprawn_stage/pub

    ErrorLog ${APACHE_LOG_DIR}/stg.liveprawn.com-ssl-error.log
    CustomLog ${APACHE_LOG_DIR}/stg.liveprawn.com-ssl-access.log combined

    <Directory /var/www/html/liveprawn_stage/pub>
        Options FollowSymLinks
        AllowOverride All
        Require all granted
        DirectoryIndex index.php
    </Directory>

    <FilesMatch \.php$>
        SetHandler "proxy:unix:/var/run/php/php8.4-fpm-dev.sock|fcgi://localhost/"
    </FilesMatch>

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/stg.liveprawn.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/stg.liveprawn.com/privkey.pem

    # Optional: password-protect entire staging site
    # <Directory /var/www/html/liveprawn_stage/pub>
    #     AuthType Basic
    #     AuthName "Live Prawn Staging"
    #     AuthUserFile /etc/apache2/.htpasswd-staging
    #     Require valid-user
    # </Directory>
</VirtualHost>
```

Enable:

```bash
a2ensite liveprawn-staging.conf
apache2ctl configtest
systemctl reload apache2
```

**Live vhosts unchanged:** `liveprawn.com` and `airbenih.com` continue pointing to `/var/www/html/airbenih_stg/pub`.

### Robots / noindex on staging

**Option A — `pub/robots.txt` on staging only:**

```
User-agent: *
Disallow: /
```

**Option B — Magento admin (staging):** Content → Design → Configuration → HTML Head → meta robots `NOINDEX,NOFOLLOW`.

**Option C — Apache header** in staging vhost:

```apache
Header set X-Robots-Tag "noindex, nofollow"
```

(requires `a2enmod headers`)

---

## 7. SSL / Certbot or temporary HTTP option

### Preferred: HTTPS via Certbot

Prerequisites: DNS `stg.liveprawn.com` → `168.144.108.100` propagated.

```bash
certbot certonly --apache -d stg.liveprawn.com \
  --non-interactive --agree-tos -m admin@liveprawn.com
```

Then enable HTTPS vhost block and reload Apache.

### Bootstrap option: HTTP only (short-term)

1. Use HTTP vhost only until DNS + cert ready.
2. **Mandatory:** HTTP basic auth (see §6 commented block).
3. Do **not** run payment or email tests on plain HTTP without auth.

### Wildcard alternative

Not required — single subdomain cert is sufficient.

---

## 8. Email disable method

Goal: **zero** customer/admin emails from staging.

### Layer 1 — Magento config (staging CLI/DB)

```bash
cd /var/www/html/liveprawn_stage
php bin/magento config:set system/smtp/disable 1
php bin/magento config:set amrewards/notification/send_earn_notification 0
php bin/magento config:set amrewards/notification/send_expire_notification 0
php bin/magento config:set amasty_affiliate/email/affiliate/welcome 0
php bin/magento config:set amasty_affiliate/email/affiliate/transaction_created 0
php bin/magento config:set amasty_affiliate/email/affiliate/transaction_changed 0
```

### Layer 2 — Override recipient (belt-and-braces)

```bash
php bin/magento config:set trans_email/ident_general/email staging@localhost.invalid
php bin/magento config:set trans_email/ident_sales/email staging@localhost.invalid
```

### Layer 3 — Server / cron

- Do **not** point staging cron at live URLs.
- If system MTA sends mail, consider `sendmail` blackhole or `/etc/hosts` sink for staging test addresses.
- Disable Magento cron on staging until needed, or run cron only on staging path:

```bash
# Staging crontab example (when ready):
# * * * * * cd /var/www/html/liveprawn_stage && php bin/magento cron:run 2>&1 | grep -v "Ran jobs"
```

### Layer 4 — Verify

Place test order on staging → confirm **no** outbound mail in `/var/log/mail.log` and Magento `var/log/`.

---

## 9. Payment safety checklist

Live has **no payment rows** in `core_config_data` today — modules enabled include `Magento_OfflinePayments`, `PayPal_Braintree`, `Magento_PaymentServicesPaypal`. Staging must assume gateways could activate from defaults or admin.

### Staging payment lockdown (run on staging only)

```bash
cd /var/www/html/liveprawn_stage

# Enable offline only
php bin/magento config:set payment/checkmo/active 1
php bin/magento config:set payment/banktransfer/active 0
php bin/magento config:set payment/cashondelivery/active 0

# Disable Braintree / Payment Services (if configured later)
php bin/magento config:set payment/braintree/active 0
php bin/magento config:set payment/braintree_paypal/active 0
php bin/magento config:set payment/payment_services/active 0
php bin/magento config:set payment/paypal_express/active 0
```

### Manual admin verification (staging)

- [ ] Only **Check / Money Order** (or **Bank Transfer** if needed for test) enabled
- [ ] Braintree/PayPal/Payment Services **disabled**
- [ ] No live API keys, merchant IDs, or webhooks pointing to production
- [ ] iPay88 or regional gateways (if added later) **disabled** or sandbox keys only
- [ ] Test order completes with offline payment → invoice → complete (reward testing path)

### During reward E2E tests

- Use **Check/Money Order** → mark order **Processing** → **Complete** manually in admin.
- Never use real card numbers on staging unless explicit sandbox mode confirmed.

---

## 10. OpenSearch fix plan

**Current:** OpenSearch 3.6.0 installed; `opensearch.service` **failed** (start timeout); `localhost:9200` **connection refused**. Magento configured: `catalog/search/engine=opensearch`, prefix `magento2`, auth enabled.

### Step 1 — Diagnose (does not affect live storefront immediately)

```bash
journalctl -u opensearch -n 100 --no-pager
cat /etc/opensearch/opensearch.yml | grep -E 'cluster|network|path|memory'
free -h
df -h /var/lib/opensearch
```

Common causes: JVM heap too high for RAM, disk watermark, corrupted data dir, Java version mismatch.

### Step 2 — Fix service (shared host — benefits live + staging)

```bash
# Example recovery path (adjust after journal review):
systemctl stop opensearch
# If data corrupt: backup /var/lib/opensearch then reindex from Magento
systemctl start opensearch
systemctl status opensearch
curl -u admin:PASSWORD -k https://localhost:9200/_cluster/health?pretty
```

Note: Magento config shows auth enabled — use configured credentials from `core_config_data` (`catalog/search/opensearch_*`), not defaults.

### Step 3 — Separate indexes for staging

On **staging DB only**:

```bash
php bin/magento config:set catalog/search/opensearch_index_prefix liveprawn_stage
```

Live keeps prefix `magento2`. Both can share one OpenSearch cluster with **different prefixes**.

### Step 4 — Reindex staging

```bash
cd /var/www/html/liveprawn_stage
php bin/magento indexer:reindex catalogsearch_fulltext
php bin/magento indexer:status catalogsearch_fulltext
```

### Step 5 — Confirm setup:upgrade safe on staging

```bash
php bin/magento setup:upgrade
php bin/magento setup:db:status   # expect: All modules up to date
```

If OpenSearch still down, `setup:upgrade` may complete but search indexers will fail — fix OpenSearch before reward testing that depends on catalog browse.

---

## 11. Commands to verify staging is separate from live

Run after clone. **All must pass** before order testing.

### Filesystem

```bash
test "$(readlink -f /var/www/html/liveprawn_stage/app/etc/env.php)" != \
     "$(readlink -f /var/www/html/airbenih_stg/app/etc/env.php)" && echo "env.php separate OK"

grep dbname /var/www/html/liveprawn_stage/app/etc/env.php
# Must show: liveprawn_stage

grep dbname /var/www/html/airbenih_stg/app/etc/env.php
# Must show: airbenih_stg
```

### Database

```bash
mariadb -e "SELECT COUNT(*) AS live_orders FROM airbenih_stg.sales_order;"
mariadb -e "SELECT COUNT(*) AS stg_orders FROM liveprawn_stage.sales_order;"
mariadb -e "SELECT path, value FROM liveprawn_stage.core_config_data WHERE path LIKE 'web/%base_url%';"
# Must show https://stg.liveprawn.com/ — NOT liveprawn.com
```

### Web separation

```bash
curl -sI -H 'Host: liveprawn.com' http://127.0.0.1/ | grep -i location
curl -sI -H 'Host: stg.liveprawn.com' http://127.0.0.1/ | head -5
# Different docroot responses; staging base URL in HTML when loaded
```

### Cache isolation

```bash
grep id_prefix /var/www/html/liveprawn_stage/app/etc/env.php
grep id_prefix /var/www/html/airbenih_stg/app/etc/env.php
# Must differ (e.g. stg_ vs 5c5_)
```

### Functional smoke test (staging)

1. Browse `https://stg.liveprawn.com` — loads storefront.
2. Admin login on staging — does **not** affect live sessions.
3. Place **one** offline-payment test order on staging.
4. Confirm live order count **unchanged**:
   ```bash
   mariadb -e "SELECT COUNT(*) FROM airbenih_stg.sales_order;"
   ```
5. Confirm staging order exists only in `liveprawn_stage`:
   ```bash
   mariadb -e "SELECT increment_id FROM liveprawn_stage.sales_order ORDER BY entity_id DESC LIMIT 1;"
   ```

---

## 12. Rollback plan

If staging clone fails or causes unexpected issues:

| Action | Command / step |
|--------|----------------|
| Remove staging vhost | `a2dissite liveprawn-staging.conf && systemctl reload apache2` |
| Remove staging code | `rm -rf /var/www/html/liveprawn_stage` |
| Drop staging DB | `mariadb -e "DROP DATABASE IF EXISTS liveprawn_stage;"` |
| Remove SSL cert (optional) | `certbot delete --cert-name stg.liveprawn.com` |
| Remove DNS | Delete A record for `stg.liveprawn.com` |
| Live verification | Confirm `liveprawn.com` still serves from `airbenih_stg`; order count unchanged |

**Live rollback not required** if plan followed — live `env.php`, vhosts, and DB were never modified.

Keep pre-clone dump at `/root/backups/` for 30 days.

---

## 13. Things we must NOT do on live

| Prohibited on live | Reason |
|--------------------|--------|
| Change `app/etc/env.php` dbname | Would break production |
| Run `setup:upgrade` for staging experiments | Schema drift / downtime risk |
| Change base URLs to staging domain | Breaks live storefront |
| Enable Batch 3B checkout collector before staging pass | Untested totals on real checkout |
| Place test orders on liveprawn.com | Contaminates reward ledger / affiliate data |
| Send test emails from live | Customer-facing |
| Share `var/session` or cache between live and staging | Session hijack / cache bleed |
| Point staging vhost at `airbenih_stg` DB | Orders/emails hit production data |
| Run `indexer:reindex` on live to “fix staging” | Unnecessary load; fix on staging |
| Disable OpenSearch on live without plan | Search already broken; avoid extra churn during clone |
| Modify live Apache vhost DocumentRoot | Downtime |
| Flush live cache during clone | Unnecessary; staging work is isolated |
| Enable referral/birthday/anniversary flags on live for testing | Use staging first |

---

## Post-staging: Customer Rewards testing sequence

Once staging verified (§11):

| Order | Task | Environment |
|-------|------|-------------|
| 1 | Fix OpenSearch + full `setup:upgrade` | Staging |
| 2 | Prawn Points earn/redeem E2E | Staging |
| 3 | Premium activation → RM20 ledger | Staging |
| 4 | Batch **3B** site credit collector | Staging only |
| 5 | Referral qualification (Batch 4C) | Staging |
| 6 | Birthday/anniversary cron batch | Staging |
| 7 | Sign-off → selective live deploy | Live (explicit approval) |

---

## Approval checklist (before execution)

- [ ] DNS A record for `stg.liveprawn.com` planned
- [ ] Disk space confirmed (≥ 8 GB free)
- [ ] Backup dump path agreed (`/root/backups/`)
- [ ] Staging URL chosen (`stg.liveprawn.com`)
- [ ] HTTP basic auth credentials prepared
- [ ] OpenSearch fix window scheduled
- [ ] Team notified: live unchanged during clone
- [ ] Rollback steps understood (§12)

**Do not execute until this checklist is signed off.**
