Files
wp-healthcheck/server/DEPLOY.md
Steve Hanlon f6d2573a35 DEPLOY.md: version-agnostic FPM socket + PHP 8.1+ requirement louder + sites-enabled backup gotcha
Two issues hit during a real deploy:

1. The nginx example pinned fastcgi_pass to /run/php/php8.2-fpm.sock.
   When the target box runs a different PHP-FPM (8.3 here), the socket
   doesn't exist and nginx 502s. Switch to /run/php/php-fpm.sock (the
   alternatives symlink installed by Ondrej Sury's packaging) so the
   default config works against whatever current PHP is installed,
   with a comment explaining the fallback for boxes without the symlink.

2. The PHP version requirement was buried in a one-liner. Promote it to
   a multi-line note that names the specific 8.x features used, says
   loudly that 7.x will not parse the code, and points at Sury's repo
   for boxes stuck on an old default.

Also added a callout warning against cp foo.conf foo.conf.bak inside
sites-enabled/, since nginx loads every file in that directory and the
backup becomes a second vhost claiming the same server_name (silently
shadowed via the "conflicting server name … ignored" warning).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-29 13:41:54 +01:00

170 lines
5.5 KiB
Markdown

# Deploying the ATT Healthcheck Server
Single-tenant PHP/SQLite app. Tiny — runs comfortably on the smallest VPS tier.
## Requirements
- **PHP 8.1+ is required.** The code uses typed properties, `str_starts_with`,
`mixed`/`never` return types, named arguments, and constructor property
promotion. PHP 7.x will not even parse the source files. PHP 7.2 has also
been EOL since November 2020 — don't run a public service on it.
Confirm with `php -v`. If the box is on an older default, install a current
PHP alongside it (Ondrej Sury's repo is the standard on Debian/Ubuntu) and
point this vhost at the new FPM socket.
- PHP extensions: `pdo`, `pdo_sqlite`, `json`, `mbstring` (all standard).
- Apache + `mod_rewrite`, or nginx with a `try_files` fallback.
- HTTPS terminating in front of PHP. Plain HTTP is technically accepted, but the plugin client will refuse to talk to non-`https://` endpoints.
## Layout on the server
Anywhere works — every path inside the app is resolved relative to its own files.
Example using `/home/www/healthcheck`:
```
/home/www/healthcheck/
├── public/ ← docroot
│ ├── .htaccess
│ └── index.php
├── src/
├── migrations/
├── data/ ← SQLite file lives here (writable by web user)
├── config.php ← deployment-specific, NOT in git
└── config.php.example
```
> **Perm gotcha for `/home/...` locations:** home directories often default to
> `750` or `700`, which blocks the web user from traversing into the app.
> `sudo chmod 755 /home/www` once is usually enough. `/var/www` doesn't have
> this problem (always world-readable by default).
## First-time install
```sh
# As you, on the server:
sudo mkdir -p /home/www/healthcheck
sudo chown $USER:www-data /home/www/healthcheck
sudo chmod 755 /home/www # see perm gotcha above
# From your dev box:
rsync -avz --exclude='data/' --exclude='config.php' \
~/dev/att-site-healthcheck/server/ \
user@vps:/home/www/healthcheck/
# Back on the server:
cd /home/www/healthcheck
cp config.php.example config.php
php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;' # generate api_key
$EDITOR config.php # paste it in
mkdir -p data
sudo chown -R www-data:www-data data/ config.php
sudo chmod 640 config.php
sudo chmod 770 data/
```
## Apache vhost (example)
```apache
<VirtualHost *:443>
ServerName healthcheck-history.example.com
DocumentRoot /home/www/healthcheck/public
<Directory /home/www/healthcheck/public>
AllowOverride All
Require all granted
</Directory>
# Pass Authorization header through to PHP — Apache strips it by default with FPM.
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/healthcheck-history.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/healthcheck-history.example.com/privkey.pem
</VirtualHost>
```
## nginx (example)
```nginx
server {
listen 443 ssl http2;
server_name healthcheck-history.example.com;
root /home/www/healthcheck/public;
index index.php;
location / {
try_files $uri /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
# Version-agnostic — Ondrej Sury's packaging installs this symlink
# pointing at whichever php-fpm is the current "alternative". If the
# symlink doesn't exist on your box, hardcode e.g. php8.3-fpm.sock.
# Check with: ls -la /run/php/
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
}
ssl_certificate /etc/letsencrypt/live/healthcheck-history.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/healthcheck-history.example.com/privkey.pem;
}
```
> **Gotcha:** nginx loads *every* file in `sites-enabled/` regardless of
> extension. Don't `cp healthcheck.conf healthcheck.conf.bak` inside that
> directory — the backup becomes a second vhost claiming the same
> `server_name`, and nginx silently ignores one of them ("conflicting server
> name … ignored" in the error log) which manifests as the new config not
> taking effect. Back up *outside* `sites-enabled/`.
## TLS
Use Let's Encrypt + certbot. Example:
```sh
sudo certbot --apache -d healthcheck-history.example.com
# or for nginx:
sudo certbot --nginx -d healthcheck-history.example.com
```
## Configure the plugin to talk to it
In `wp-config.php` on each WordPress install:
```php
define('ATT_HC_API_URL', 'https://healthcheck-history.example.com');
define('ATT_HC_API_KEY', '<the long random string from config.php>');
```
## Backups
The SQLite DB is a single file. Snapshot it on a cron:
```cron
# Daily at 03:15, rotate 14 days. SQLite-safe online backup.
15 3 * * * /usr/bin/sqlite3 /home/www/healthcheck/data/att_hc.sqlite ".backup '/var/backups/att-hc/att_hc-$(date +\%F).sqlite'" && find /var/backups/att-hc -name 'att_hc-*.sqlite' -mtime +14 -delete
```
Pull those backups offsite with `rsync` / `rclone` to whatever you already use.
## Updating
Just rsync the source again — migrations run on first request after deploy.
```sh
rsync -avz --delete --exclude='data/' --exclude='config.php' \
~/dev/att-site-healthcheck/server/ \
user@vps:/home/www/healthcheck/
```
## Quick health check
```sh
curl https://healthcheck-history.example.com/
# → {"ok":true,"service":"att-site-healthcheck-server","version":"..."}
```