Plan Your Visit landing page for visit.waterschurch.org
Next.js 16 static export + PHP 8 API for cPanel/Apache hosting. - Location-aware campus finder (IP estimate, optional precise location, ZIP search) - Three-step Plan Your Visit form with separate email/SMS consent - PHP API: validation, anti-spam, rate limiting, SQLite lead store, Mailchimp and Sinch Engage adapters, idempotent retries via cron - Attribution capture (utm_*, gclid/gbraid/wbraid, fbclid, ttclid, twclid) and consent-aware analytics hooks - Vitest and PHP test suites, cPanel deployment script and docs Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
# Deploying to cPanel (site administrator guide)
|
||||
|
||||
This guide assumes the standard cPanel layout `/home/USER/`. Replace `USER` with the account's
|
||||
username throughout. Nothing here changes DNS or publishes anything until step 8.
|
||||
|
||||
> **Current state (2026-09-22):** `visit.waterschurch.org` already resolves to the same server as
|
||||
> waterschurch.org (108.160.152.27) and currently 301-redirects to a Church Center form
|
||||
> (`waterschurch.churchcenter.com/people/forms/21186`). That redirect lives either in the main
|
||||
> site's `.htaccess` or in a cPanel **Redirects** entry and must be removed when this site goes live.
|
||||
|
||||
## Fastest path: `deploy/deploy.sh` (SSH)
|
||||
|
||||
If you have SSH access (alias `waterschurch` in `~/.ssh/config`), one command does steps 2, 5, 6 and 7 below:
|
||||
|
||||
```bash
|
||||
deploy/deploy.sh --keep-redirect # deploy, but keep visitors on the Church Center form (pre-launch)
|
||||
deploy/deploy.sh # build, deploy and go live (removes the redirect)
|
||||
deploy/deploy.sh --no-build # re-upload the existing ./out
|
||||
```
|
||||
|
||||
It preserves cPanel's own `.htaccess` blocks and `.user.ini`, never overwrites an existing
|
||||
`~/visit-private/.env`, and prints the health check when done. The manual steps below describe
|
||||
what it does, for File Manager users or troubleshooting.
|
||||
|
||||
**Status 2026-09-22 (evening): LIVE.** Deployed with `deploy/deploy.sh`; the Church Center redirect is
|
||||
gone. `INTEGRATION_MODE=live` with Mailchimp (audience "Waters Church", datacenter `us2`) and Sinch
|
||||
(all five campus sub-accounts verified). The 12 merge fields exist in the audience. A real email-only
|
||||
test lead succeeded end to end; the SMS path is verified against Sinch auth but a live text has not yet
|
||||
been sent. Journey "New Visit" (id 5842) is still a draft and triggers only on `Visit:Apollo Beach`.
|
||||
|
||||
## 0. What you will need
|
||||
|
||||
* cPanel login for the waterschurch.org account
|
||||
* The build package: run `npm run build && npm run package` → `dist/visit-waterschurch-<version>-<date>.zip`
|
||||
* Filled-in `.env` values (Mailchimp key, Sinch key/secret, etc.) — see `.env.example`
|
||||
|
||||
## 1. Check the account's PHP
|
||||
|
||||
cPanel → **Software → MultiPHP Manager** (or *Select PHP Version*).
|
||||
|
||||
* Set the domain (once created) to **PHP 8.1, 8.2 or 8.3**.
|
||||
* **MultiPHP INI Editor / Select PHP Version → Extensions**: ensure `curl`, `pdo_sqlite`, `sqlite3`,
|
||||
`mbstring`, `json`, `openssl` are enabled. (`pdo_mysql` only if you choose MySQL storage.)
|
||||
|
||||
Node.js is **not** required. If the account has "Setup Node.js App", ignore it.
|
||||
|
||||
## 2. Create the private configuration folder (outside the web root)
|
||||
|
||||
cPanel → **Files → File Manager** → navigate to `/home/USER/` (the home directory, *not*
|
||||
`public_html`).
|
||||
|
||||
1. Create folder **`visit-private`**.
|
||||
2. Inside it create folders **`data`** and **`logs`**.
|
||||
3. Upload `.env.example` from the package (`dist/env.example.txt`), rename it to **`.env`**, and edit
|
||||
it in File Manager: fill in real values, set `INTEGRATION_MODE=live` only when ready to go live,
|
||||
and set a long random `HASH_SALT`.
|
||||
4. Permissions (right-click → *Change Permissions*): folders `0750`, the `.env` file `0640`.
|
||||
PHP runs as your cPanel user under suPHP/LSAPI, so it can read and write these while the web
|
||||
server cannot serve them.
|
||||
|
||||
If your host runs PHP as a different user (rare), use `0755`/`0644` and rely on the folder being
|
||||
outside the document root.
|
||||
|
||||
## 3. Create the subdomain
|
||||
|
||||
cPanel → **Domains → Domains** (or *Subdomains* on older versions) → **Create a New Domain**:
|
||||
|
||||
* Domain: `visit.waterschurch.org`
|
||||
* Untick "Share document root"; set **Document Root** to `/home/USER/visit.waterschurch.org`
|
||||
* Submit. cPanel creates the folder and the DNS `A` record automatically because the zone is hosted
|
||||
here (`ns1/ns2.waterschurch.org`). The record already exists and points at this server, so nothing
|
||||
external changes.
|
||||
|
||||
Then remove the old redirect:
|
||||
|
||||
* cPanel → **Domains → Redirects**: delete any entry for `visit.waterschurch.org`, **or**
|
||||
* File Manager → `public_html/.htaccess`: remove the `RewriteCond %{HTTP_HOST} ^visit\.` block that
|
||||
redirects to churchcenter.com.
|
||||
|
||||
Do this at the moment you are ready to launch (step 8); until then the redirect keeps working.
|
||||
|
||||
## 4. Enable SSL
|
||||
|
||||
cPanel → **Security → SSL/TLS Status** → tick `visit.waterschurch.org` → **Run AutoSSL**.
|
||||
Wait for the green padlock (usually under 5 minutes). If AutoSSL fails with a DCV error, make sure
|
||||
the document root exists and nothing blocks `/.well-known/` (our `.htaccess` explicitly allows it).
|
||||
|
||||
Verify: `https://visit.waterschurch.org/BUILD.txt` shows the build id once files are uploaded
|
||||
(step 5). Force-HTTPS is handled by the site's own `.htaccess`; you do **not** need cPanel's
|
||||
"Force HTTPS Redirect" toggle, but enabling it is harmless.
|
||||
|
||||
## 5. Upload the build
|
||||
|
||||
1. File Manager → open `/home/USER/visit.waterschurch.org/`. Delete the placeholder `cgi-bin`
|
||||
folder if present.
|
||||
2. **Upload** the zip from `dist/`, then right-click → **Extract** into this folder. Delete the zip.
|
||||
3. Enable **Settings → Show Hidden Files (dotfiles)** and confirm `.htaccess` is present at the root
|
||||
and inside `api/lib`, `api/cron`, `api/config`.
|
||||
4. Tell the API where the private folder is. Either:
|
||||
* leave the default (`/home/USER/visit-private` — the folder *next to* the document root), **or**
|
||||
* edit `/home/USER/visit.waterschurch.org/.htaccess` and uncomment
|
||||
`SetEnv VISIT_PRIVATE_DIR /home/USER/visit-private` with the real path.
|
||||
5. Permissions: folders `0755`, files `0644` (File Manager's defaults after extraction are fine).
|
||||
|
||||
The upload never requires editing generated files. To update the site later, rebuild locally and
|
||||
repeat steps 5.2–5.3 (or use *File Manager → Upload* with "Overwrite existing files"). An alternative
|
||||
for frequent updates is cPanel's **Git Version Control** pointed at a repo whose `out/` is committed,
|
||||
or SFTP with `rsync --delete out/ USER@host:visit.waterschurch.org/`.
|
||||
|
||||
## 6. Verify the API
|
||||
|
||||
Open `https://visit.waterschurch.org/api/health.php`. You should see `"ok": true` and:
|
||||
|
||||
```
|
||||
"private_dir_found": true, "env_file_found": true, "data_dir_writable": true,
|
||||
"database": "ok (sqlite)", "campuses_loaded": 6, "https": true
|
||||
```
|
||||
|
||||
`integration_mode` shows `mock` until you change it in `.env`. Fix anything `false` before continuing
|
||||
(most often: wrong `VISIT_PRIVATE_DIR` path or `data` folder not writable).
|
||||
|
||||
Confirm private paths are blocked (each must return **403**):
|
||||
|
||||
* `https://visit.waterschurch.org/api/lib/Config.php`
|
||||
* `https://visit.waterschurch.org/api/config/campuses.json`
|
||||
* `https://visit.waterschurch.org/api/cron/retry-integrations.php`
|
||||
|
||||
## 7. Cron job for retries
|
||||
|
||||
cPanel → **Advanced → Cron Jobs** → add, every 5 minutes (`*/5 * * * *`):
|
||||
|
||||
```
|
||||
VISIT_PRIVATE_DIR=/home/USER/visit-private /usr/local/bin/php /home/USER/visit.waterschurch.org/api/cron/retry-integrations.php >> /home/USER/visit-private/logs/cron.log 2>&1
|
||||
```
|
||||
|
||||
Check the PHP binary path with **MultiPHP Manager** (often `/usr/local/bin/php`, `/opt/cpanel/ea-php82/root/usr/bin/php`, or `/usr/local/bin/ea-php82`). The job re-sends any Mailchimp/Sinch
|
||||
delivery that failed transiently (up to 5 attempts with backoff) and, if `LEAD_RETENTION_DAYS` is set,
|
||||
deletes fully-processed leads older than that.
|
||||
|
||||
Optional weekly export for staff (contains personal data — download and delete promptly):
|
||||
|
||||
```
|
||||
VISIT_PRIVATE_DIR=/home/USER/visit-private /usr/local/bin/php /home/USER/visit.waterschurch.org/api/cron/export-leads.php --since=$(date -d '7 days ago' +%F) > /home/USER/visit-private/data/leads-week.csv
|
||||
```
|
||||
|
||||
## 8. Go live
|
||||
|
||||
1. Set `INTEGRATION_MODE=live` in `/home/USER/visit-private/.env`.
|
||||
2. Remove the Church Center redirect (step 3).
|
||||
3. Submit one real test lead with your own email and mobile: confirm the Mailchimp contact + tags
|
||||
and the confirmation text arrive. Reply STOP to test opt-out routing, then re-subscribe.
|
||||
4. Run through [LAUNCH-CHECKLIST.md](LAUNCH-CHECKLIST.md).
|
||||
|
||||
## 9. Updating content later
|
||||
|
||||
| Change | Where | Then |
|
||||
| --- | --- | --- |
|
||||
| Service times, address, phone, SMS number, campus status | `config/campuses.json` | `npm run build` → re-upload |
|
||||
| Logo | `assets/source/logos/` (same file names) | `npm run build` → re-upload |
|
||||
| Photos | `assets/source/photos/` (same file names) | `npm run build` → re-upload |
|
||||
| FAQs, kids info, headlines, consent text | `src/content/*.ts` (bump `consent.version` if consent text changes) | `npm run build` → re-upload |
|
||||
| Central contact number, privacy URL | `.env` (`NEXT_PUBLIC_*`) | `npm run build` → re-upload |
|
||||
| API keys, mode, providers | `/home/USER/visit-private/.env` | takes effect immediately, no rebuild |
|
||||
| SMS template | `SINCH_CONFIRMATION_TEMPLATE` in server `.env` | immediate |
|
||||
|
||||
## 10. Files, permissions and logs
|
||||
|
||||
```
|
||||
/home/USER/
|
||||
├── visit.waterschurch.org/ document root (0755) — everything in out/
|
||||
│ ├── .htaccess HTTPS + canonical redirects, caching, security headers, 404
|
||||
│ ├── index.html, 404.html, _next/, images/
|
||||
│ └── api/ PHP endpoints (public) + lib/ cron/ config/ (403)
|
||||
└── visit-private/ NOT web accessible (0750)
|
||||
├── .env secrets (0640)
|
||||
├── data/leads.sqlite lead store (created automatically)
|
||||
├── data/retry.lock
|
||||
└── logs/app.log, cron.log JSON lines; personal data and secrets are redacted
|
||||
```
|
||||
|
||||
Rotate `app.log` occasionally (cPanel cron: `find /home/USER/visit-private/logs -name '*.log' -size +20M -delete`)
|
||||
or simply download and clear it.
|
||||
|
||||
## 11. Optional: MySQL instead of SQLite
|
||||
|
||||
cPanel → **Databases → MySQL Database Wizard**: create `USER_visit` + a user with ALL PRIVILEGES.
|
||||
In `.env`: `DATABASE_DSN=mysql:host=localhost;dbname=USER_visit;charset=utf8mb4`,
|
||||
`DATABASE_USER`, `DATABASE_PASSWORD`. Tables are created automatically on first request.
|
||||
|
||||
## 12. Content Security Policy
|
||||
|
||||
`.htaccess` ships a **report-only** CSP covering GTM, GA4, Meta, TikTok and Turnstile. After
|
||||
confirming the tags you actually use, change `Content-Security-Policy-Report-Only` to
|
||||
`Content-Security-Policy` and remove hosts you do not need.
|
||||
Reference in New Issue
Block a user