Central history server + plugin write-through sync (epic hc-0p1)
Adds a PHP/SQLite history server in server/ and refactors the plugin to write every session change through it. Healthcheck history now survives plugin uninstall and groups across dev + live URLs for the same engagement via an editable site_key (defaults to the normalised host). Server (server/): - Front controller + hand-rolled autoloader, no framework, no composer - SQLite default DSN; swap to MySQL by changing config.php - Schema: healthchecks (PK id, UNIQUE (site_key, started_at)) + step_updates (PK (healthcheck_id, step_id)) + request_log; auto-migration runner - 8 endpoints: POST/GET/PUT healthchecks, PUT/GET step rows, GET step history with exclude_id, GET /sites (recent), GET /step-counts (badge data) - Bearer auth via hash_equals; HTTPS expected (plugin enforces client-side) - DEPLOY.md with Apache/nginx vhosts, Let's Encrypt, SQLite backup cron, and the /home/www/ perm gotcha - dev-router.php works around PHP -S 405-ing dotted uniqid paths Plugin: - ATT_HC_Api HTTP client reads ATT_HC_API_URL/ATT_HC_API_KEY constants from wp-config.php; refuses non-HTTPS with a loopback dev exception - ATT_HC_Session is now write-through: every start/update_step/finish/ set_autocheck POSTs or PUTs to the server first, then updates the local WP option cache. No drift possible — failures throw ATT_HC_Api_Exception - previous() now reads from /healthchecks?include=steps and reconstructs; the old att_hc_previous_session local option is gone - ATT_HC_Session::resume(id) hydrates a server session into the local cache - Start screen: editable site_key (defaults to normalise_site_url()), datalist of recent engagements, table of in-progress sessions for the chosen key with Resume buttons. Double-click guard on start + resume handlers short-circuits if a session is already active - Per-step <details> disclosure shows "Previous notes (N)" badge from /step-counts; lazy-loads detail rows on first expand via admin-ajax, caches via data-loaded, resets on error so user can retry - All admin handlers catch ATT_HC_Api_Exception and surface via att_hc_api_error transient → admin notice - Hard config-error gate at the top of the admin page blocks the UI when ATT_HC_API_URL/ATT_HC_API_KEY are missing or malformed Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
149
server/DEPLOY.md
Normal file
149
server/DEPLOY.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Deploying the ATT Healthcheck Server
|
||||
|
||||
Single-tenant PHP/SQLite app. Tiny — runs comfortably on the smallest VPS tier.
|
||||
|
||||
## Requirements
|
||||
|
||||
- PHP **8.1+** with `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;
|
||||
fastcgi_pass unix:/run/php/php8.2-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;
|
||||
}
|
||||
```
|
||||
|
||||
## 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":"..."}
|
||||
```
|
||||
Reference in New Issue
Block a user