# 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 ServerName healthcheck-history.example.com DocumentRoot /home/www/healthcheck/public AllowOverride All Require all granted # 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 ``` ## 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', ''); ``` ## 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":"..."} ```