# mBlog — Migrate & Host Guide (from the 7 module zips)

This takes you from the seven bundles to a live, upgraded site. It evolves your
existing mblog.in in place; nothing is dropped and every step is reversible.

Your bundles:
| Zip | What it is | Where it goes |
|---|---|---|
| `mblog_module1.zip` | Database migration (SQL) | run against the DB (not copied into the site) |
| `mblog_api_v1.zip` | Base `app/` layer + `/api/v1` | `app/`, `config/`, `web/` |
| `mblog_admin.zip` | Admin CMS (layers onto `app/`) | `app/`, `config/`, `web/admin/`, `scripts/` |
| `mblog_seo.zip` | Sitemap, robots, `/category`, `/page` | `app/`, `web/` |
| `mblog_feed.zip` | Public feed on `/api/v1` | `web/` |
| `mblog_article.zip` | Article page on `/api/v1` | `web/` |
| `mblog_deploy.zip` | Runbook + scripts | run from anywhere |

Order matters — later bundles intentionally overwrite earlier `config/app.php`,
`.htaccess`, and `post.php`.

Throughout, adjust these to your server:
```
PROJECT_ROOT=/var/www/mblog          # parent of the web root (holds app/, config/, logs/…)
DOCROOT=$PROJECT_ROOT/web            # Apache DocumentRoot
APACHE_USER=apache                   # Fedora/RHEL; Debian uses www-data
DB: name=mblog user=mblog_app host=127.0.0.1 port=5432
```

---

## Phase 0 — Prerequisites (once)

On the server:
```bash
php -v                              # need >= 8.1
php -m | grep -E 'pdo_pgsql|mbstring'   # both must appear; install php-mbstring if missing
psql -h 127.0.0.1 -U mblog_app -d mblog -c '\dt'   # DB reachable
```
Confirm Apache has `AllowOverride All` for the docroot (so `.htaccess` routing
works). If any are missing, fix before continuing.

Upload the seven zips to a scratch folder, e.g. `/tmp/release/`, and unzip:
```bash
mkdir -p /tmp/release && cd /tmp/release
# upload the 7 .zip files here, then:
for z in mblog_*.zip; do unzip -o "$z"; done
ls -d mblog_*/                      # you should see 7 folders
```

---

## Phase 1 — Assemble the staging tree

Build a complete project tree in `/tmp/mblog-staging` from a copy of your
**current live site** plus the bundles, layered in order.

```bash
cd /tmp/release
S=/tmp/mblog-staging
rm -rf "$S" && mkdir -p "$S/web"

# 1) Base = your current live document root (keeps icons, manifest, images,
#    the legacy /api, favicon, etc.). Point this at your real docroot:
rsync -a /path/to/your/current/docroot/  "$S/web/"

# 2) api_v1 — base app layer + versioned API + .htaccess
cp -a mblog_api_v1/app        "$S/"
cp -a mblog_api_v1/config     "$S/"
cp -a mblog_api_v1/web/.      "$S/web/"

# 3) admin — merges new classes into app/, replaces config/app.php, admin files
cp -a mblog_admin/app/.       "$S/app/"
cp -a mblog_admin/config/.    "$S/config/"
cp -a mblog_admin/web/.       "$S/web/"
mkdir -p "$S/scripts" && cp -a mblog_admin/scripts/scheduler.php "$S/scripts/"

# 4) seo — adds SeoRepository + sitemap/robots/category/page + cumulative .htaccess
cp -a mblog_seo/app/.         "$S/app/"
cp -a mblog_seo/web/.         "$S/web/"

# 5) feed — app.js / index.html / styles.css / sw.js
cp -a mblog_feed/web/.        "$S/web/"

# 6) article — post.php
cp -a mblog_article/web/.     "$S/web/"
```

Sanity-check the result:
```bash
find "$S" -maxdepth 2 -type d | sort
# expect: web/  web/api  web/api/v1  web/admin  app/  app/Core  app/Http ...
#         config/  scripts/
test -f "$S/app/bootstrap.php" && test -f "$S/web/api/v1/index.php" \
  && test -f "$S/web/post.php" && test -f "$S/web/sitemap.php" \
  && grep -q 'admin' "$S/config/app.php" && echo "STAGING OK"
```
`STAGING OK` means the layering worked (the config has the admin key, i.e. the
admin bundle's config won).

---

## Phase 2 — Set credentials

The new layer reads DB creds and the admin password from environment variables
via `config/app.php`.

1. Generate an admin password hash:
   ```bash
   php -r 'echo password_hash("YOUR-STRONG-PASSWORD", PASSWORD_DEFAULT), "\n";'
   ```
2. Make the variables visible to PHP. **Which method depends on your SAPI:**
   - **mod_php:** add the `SetEnv` lines from
     `mblog_deploy/config/mblog-vhost.conf.example` to your vhost.
   - **php-fpm:** add them to the FPM pool instead (`SetEnv` is not seen by
     `getenv()` under FPM):
     ```
     env[MBLOG_DB_HOST] = 127.0.0.1
     env[MBLOG_DB_NAME] = mblog
     env[MBLOG_DB_USER] = mblog_app
     env[MBLOG_DB_PASS] = your-db-pass
     env[ADMIN_PASSWORD_HASH] = $2y$...
     ```
     then `systemctl reload php-fpm`.
   - **Last resort:** edit the fallback values directly in `config/app.php`.
3. Also export them in your shell for the migration/deploy commands below:
   ```bash
   export MBLOG_DB_HOST=127.0.0.1 MBLOG_DB_PORT=5432 MBLOG_DB_NAME=mblog \
          MBLOG_DB_USER=mblog_app MBLOG_DB_PASS='your-db-pass'
   export PGPASSWORD="$MBLOG_DB_PASS"
   ```

---

## Phase 3 — Back up

```bash
cd /tmp/release/mblog_deploy/scripts
chmod +x *.sh
PROJECT_ROOT=/var/www/mblog ./backup.sh
```
This writes `backups/db-<ts>.sql.gz` and `backups/files-<ts>.tar.gz`. Keep the
paths — they are your rollback.

---

## Phase 4 — Rehearse, then apply the database migration

Rehearse on a throwaway copy first (does **not** touch the live DB):
```bash
PG_SUPERUSER=postgres ./rehearse-migration.sh \
  /var/www/mblog/backups/db-<ts>.sql.gz  /tmp/release/mblog_module1
```
Proceed only if it prints `REHEARSAL OK`.

Apply to production (the migration is transactional, additive and idempotent):
```bash
cd /tmp/release
psql -h 127.0.0.1 -U mblog_app mblog -v ON_ERROR_STOP=1 \
     -f mblog_module1/001_module1_core_alignment.up.sql
psql -h 127.0.0.1 -U mblog_app mblog \
     -f mblog_module1/001_verify.sql
```
In the verify output expect `mismatched_created_at = 0`, the new tables/columns
listed, and a `schema_migrations` row.

---

## Phase 5 — Deploy the files

```bash
cd /tmp/release/mblog_deploy/scripts
PROJECT_ROOT=/var/www/mblog APACHE_USER=apache ./deploy.sh /tmp/mblog-staging
```
`deploy.sh` lints every PHP file, backs up again, syncs **additively** (your
legacy `/api` and `web/images` are preserved), and makes `logs/`, `cache/`,
`web/images/` writable.

If your DocumentRoot is not yet `$PROJECT_ROOT/web`, point it there now using
`mblog_deploy/config/mblog-vhost.conf.example` as a template, then:
```bash
apachectl configtest && systemctl reload httpd
```

---

## Phase 6 — Finishing touches

**a) Stop PageSpeed caching the API** (fixes the stale-feed issue). Ensure the
vhost has the `ModPagespeedDisallow "*/api/*"` block, then:
```bash
touch /var/cache/mod_pagespeed/cache.flush   # path may differ on your box
systemctl reload httpd
```

**b) Scheduler cron** (auto-publish / auto-archive):
```bash
crontab -u apache -e
# add (adjust path):
* * * * * /usr/bin/php /var/www/mblog/scripts/scheduler.php >> /var/www/mblog/logs/scheduler.log 2>&1
```

**c) Rotate the API key** used by your ingestion:
```bash
KEY=$(openssl rand -hex 24); echo "New API key: $KEY"
HASH=$(printf '%s' "$KEY" | sha256sum | awk '{print $1}')
psql -h 127.0.0.1 -U mblog_app mblog -c \
 "INSERT INTO api_client(name,key_hash,is_active) VALUES('ingest-prod','$HASH',true);
  UPDATE api_client SET is_active=false WHERE name<>'ingest-prod';"
```
Store `$KEY` in your ingestion client (sent as the `X-API-Key` header). It is
not recoverable later — only its hash is stored.

---

## Phase 7 — Verify

```bash
cd /tmp/release/mblog_deploy/scripts
./healthcheck.sh https://mblog.in         # must print HEALTHCHECK OK
```
Then by hand:
- `https://mblog.in/` — feed loads, scroll/like/share work
- `https://mblog.in/<a-post-slug>` — article page, SEO title/description correct
- `https://mblog.in/category/<slug>` — category listing
- log in at `https://mblog.in/admin/` with your new password; create a draft,
  schedule a post, upload an image
- `https://mblog.in/sitemap.xml` — lists posts, categories, pages

Finally, submit `https://mblog.in/sitemap.xml` in Google Search Console and
watch `/var/www/mblog/logs/api.log` for a few minutes.

---

## Rollback

- **Frontend only:** restore the previous `web/app.js`, `web/post.php`,
  `web/.htaccess`, `web/sw.js` from the files backup and reload Apache. The
  legacy `/api` still works, so the site returns to its old behaviour without
  touching the database.
- **Everything:** `tar -xzf backups/files-<ts>.tar.gz -C /var/www/mblog` and
  reload Apache.
- **Database (last resort only):** `001_module1_core_alignment.down.sql`
  reverses the migration but drops the new tables (audit, media, static pages…)
  and their data. Prefer a forward fix; take a fresh backup first.

A failed `healthcheck.sh` (non-zero exit) is your signal to roll back.

---

## Notes
- Re-running `deploy.sh` is safe; re-applying the migration is a no-op.
- `deploy.sh` never runs the migration and never deletes files, so it can't
  remove your legacy API or uploaded images.
- Full reference and the mod_php-vs-php-fpm detail are in
  `mblog_deploy/DEPLOYMENT.md`.
