Medowar d915024af7 implementing s3 upload of older backups 1 månad sedan
..
backups b7e6f44187 adding backup-server functionality 1 månad sedan
.htaccess d915024af7 implementing s3 upload of older backups 1 månad sedan
README.md d915024af7 implementing s3 upload of older backups 1 månad sedan
config.sample.php d915024af7 implementing s3 upload of older backups 1 månad sedan
index.php b7e6f44187 adding backup-server functionality 1 månad sedan
lib.php d915024af7 implementing s3 upload of older backups 1 månad sedan
manage.php d915024af7 implementing s3 upload of older backups 1 månad sedan
s3.php d915024af7 implementing s3 upload of older backups 1 månad sedan
upload.php d915024af7 implementing s3 upload of older backups 1 månad sedan

README.md

PSA Orderform Backup Server

This folder can be deployed as a central backup server for distributed PSA orderform instances.

Setup

  1. Copy config.sample.php to config.php.
  2. Change BACKUP_SERVER_PASSWORD.
  3. Ensure the backups/ directory is writable by PHP.
  4. Open index.php or manage.php and log in with the configured password.
  5. Add every allowed distributed instance in the management UI.

The upload endpoint is upload.php. It intentionally does not require authentication, but every upload must include an instance identifier that was added in the management UI.

Client target

Configure a distributed instance with a managed backup target:

define('BACKUP_REMOTE_TARGETS', [
    [
        'name' => 'Managed Backup Server',
        'type' => 'managed',
        'url' => 'https://backup.example.org/upload.php',
        'instance' => 'stadt-freising-prod',
    ],
]);

url must point directly to upload.php. instance may contain letters, numbers, dots, underscores, and dashes.

Retention

The server retains the latest backups per instance. The default is 30.

Retention can be changed in config.php with BACKUP_SERVER_RETENTION and in the management UI. The UI value is stored in backups/settings.json and takes precedence after it has been saved once.

Retention is applied after every successful upload and after retention changes in the management UI.

With S3 enabled (see below) this value controls only the local copies. The minimum is 1, so at least the newest backup always stays on local disk.

S3 archive (optional)

The server can additionally archive every backup to an S3-compatible object storage (e.g. Hetzner Object Storage, MinIO). Local disk then acts as a small hot cache with the newest backups, while the bucket holds the complete archive.

Enable it in config.php:

define("BACKUP_SERVER_S3_ENABLED", true);
define("BACKUP_SERVER_S3_ENDPOINT", "https://fsn1.your-objectstorage.com");
define("BACKUP_SERVER_S3_REGION", "fsn1");
define("BACKUP_SERVER_S3_BUCKET", "my-backup-bucket");
define("BACKUP_SERVER_S3_PREFIX", "psa-backups");
define("BACKUP_SERVER_S3_ACCESS_KEY", "...");
define("BACKUP_SERVER_S3_SECRET_KEY", "...");

By default the server uses virtual-hosted-style addressing (https://<bucket>.<endpoint>/<key>), which Hetzner and most S3-compatible providers expect. If your provider requires path-style (https://<endpoint>/<bucket>/<key>), set BACKUP_SERVER_S3_PATH_STYLE to true.

Behavior:

  • Every received backup is stored locally first and then uploaded to S3 (AWS Signature V4, no SDK required). Objects are stored as <prefix>/<instance>/<filename>.
  • A local copy is only deleted after it fell out of the local retention window and its S3 copy is confirmed. While S3 is unreachable, local copies accumulate beyond the retention setting instead of being deleted.
  • S3 failures never fail a client upload. They are logged to backups/s3.log and shown in the management UI; failed uploads are retried on the next upload for that instance or via the "Retry S3 uploads now" button.
  • S3 has its own count-based retention ("S3 backups retained per instance", default BACKUP_SERVER_S3_RETENTION = 365). Backups that age out of S3 are deleted from the bucket.
  • S3-only backups remain listed in the management UI and are downloaded through the server, so the bucket can (and should) stay private.
  • When enabling S3 on an installation with existing backups, use "Retry S3 uploads now" once to backfill the archive; otherwise the first client upload per instance flushes the whole backlog within that request.

Limitations: uploads to S3 hold the whole file in memory, so a single backup must fit into PHP's memory_limit. Downloads from S3 are streamed and have no such limit. Concurrent uploads for the same instance may race on index.json (pre-existing limitation).

Troubleshooting: S3 errors are written to backups/s3.log with the provider's error code and, on a rejected request, a diagnostic showing the HTTP status chain, any redirect target, and the request id. AccessDenied or a redirect in the status chain usually means the addressing style is wrong — try flipping BACKUP_SERVER_S3_PATH_STYLE. SignatureDoesNotMatch usually means a wrong region or secret key. The server never follows S3 redirects, so a 3xx in the log is reported rather than silently retried against the wrong host.

Storage

Backups are stored under:

backups/<instance>/backup-YYYYmmdd-HHMMSS.zip

Metadata is stored in:

backups/index.json

Allowed instances and UI retention settings are stored in:

backups/settings.json

With the included .htaccess, ZIP, JSON, and log files as well as the internal includes (lib.php, s3.php) are not directly readable through Apache. Downloads should use the authenticated management UI.