Part of the admin-panel expansion epic. Depends on Foundation.
Make the cron-only backup runnable + browsable from the panel.
Tasks
- Extract the backup engine: move the dump logic out of
bin/backup-database.php into src/functions/db.backup.php → db_backup(array $settings, int $time): array returning ['ok' => bool, 'file' => ?string, 'error' => ?string] (instead of echo/exit). Preserve every current behaviour: the p:-prefix strip on db_host, the 0600 temp --defaults-extra-file, the two proc_open mysqldump passes (full minus peers rows, then peers schema only), and rotation by backup_rotate days. bin/backup-database.php becomes a thin wrapper that calls db_backup() and echoes/exits — cron behaviour unchanged.
- List backups:
src/functions/db.backup.list.php → db_backup_list(), reusing the existing glob($backup_dir.$db_name.'.*.sql') pattern, returning name/size/mtime per file.
- Controller/action/view:
src/controller/admin.backups.php → admin_backups_controller() renders the page (list + "Run backup now" button).
src/controller/admin.backup.php → admin_backup_action() — process=backup; calls db_backup(), then task_log(..., 'backup', $time).
src/views/html.admin.backups.php → view_admin_backups_html().
Environment caveat (surface in the view)
db_backup() needs the mysqldump binary + proc_open available to the web user, and a writable backup_dir; show a clear error from the result array when unmet.
Optional — download a backup
Validate the requested basename strictly against db_backup_list() (no path traversal) before streaming.
Tests
DbBackupListTest; DbBackup is integration-ish (needs mysqldump) — test the result-shape contract and skip when the binary is absent.
Files
- New:
src/functions/db.backup.php, src/functions/db.backup.list.php, src/controller/admin.backups.php, src/controller/admin.backup.php, src/views/html.admin.backups.php.
- Modified:
bin/backup-database.php (thin wrapper).
Part of the admin-panel expansion epic. Depends on Foundation.
Make the cron-only backup runnable + browsable from the panel.
Tasks
bin/backup-database.phpintosrc/functions/db.backup.php→db_backup(array $settings, int $time): arrayreturning['ok' => bool, 'file' => ?string, 'error' => ?string](instead ofecho/exit). Preserve every current behaviour: thep:-prefix strip ondb_host, the 0600 temp--defaults-extra-file, the twoproc_openmysqldumppasses (full minus peers rows, then peers schema only), and rotation bybackup_rotatedays.bin/backup-database.phpbecomes a thin wrapper that callsdb_backup()and echoes/exits — cron behaviour unchanged.src/functions/db.backup.list.php→db_backup_list(), reusing the existingglob($backup_dir.$db_name.'.*.sql')pattern, returning name/size/mtime per file.src/controller/admin.backups.php→admin_backups_controller()renders the page (list + "Run backup now" button).src/controller/admin.backup.php→admin_backup_action()—process=backup; callsdb_backup(), thentask_log(..., 'backup', $time).src/views/html.admin.backups.php→view_admin_backups_html().Environment caveat (surface in the view)
db_backup()needs themysqldumpbinary +proc_openavailable to the web user, and a writablebackup_dir; show a clear error from the result array when unmet.Optional — download a backup
Validate the requested basename strictly against
db_backup_list()(no path traversal) before streaming.Tests
DbBackupListTest;DbBackupis integration-ish (needsmysqldump) — test the result-shape contract and skip when the binary is absent.Files
src/functions/db.backup.php,src/functions/db.backup.list.php,src/controller/admin.backups.php,src/controller/admin.backup.php,src/views/html.admin.backups.php.bin/backup-database.php(thin wrapper).