Files
Waters ChurchandClaude Fable 5.1 78cd0cc550 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>
2026-09-22 13:22:26 -04:00

9.9 KiB
Raw Permalink Blame History

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:

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.

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.