feat(cli): automatic periodic SQLite export with retention (#8819)

Add an opt-in CLI that exports each user's database to
`data/users/<user>/sqlite-backups/<YYYYMMDDTHHMMSSZ>.sqlite` (UTC) and
prunes older files to a configured count. Gated by two new settings,
`auto_sqlite_export.enabled` and `auto_sqlite_export.retention`.

Kept separate from `cli/db-backup.php` / `cli/db-restore.php`, which
stay the fixed-filename migration tool. First step of #8183.

Co-authored-by: Bjørn A. Andersen <polybjorn@users.noreply.github.com>
This commit is contained in:
polybjorn
2026-05-12 06:44:00 +00:00
committed by GitHub
parent 557e190a40
commit d74337deb6
5 changed files with 110 additions and 0 deletions
+1
View File
@@ -9,6 +9,7 @@ declare(strict_types=1);
* @property bool $api_enabled
* @property string $archiving
* @property 'form'|'http_auth'|'none' $auth_type
* @property array{enabled:bool,retention:int} $auto_sqlite_export
* @property-read bool $reauth_required
* @property-read int $reauth_time
* @property-read string $auto_update_url
+5
View File
@@ -127,6 +127,11 @@ cd /usr/share/FreshRSS
# Back-up all users respective database to `data/users/*/backup.sqlite`
# -q, --quiet suppress non-error messages
./cli/export-sqlite-auto.php
# Periodic SQLite export per user to `data/users/*/sqlite-backups/<YYYYMMDDTHHMMSSZ>.sqlite`, pruned to retention.
# Gated by `auto_sqlite_export` in `data/config.php`.
# -q, --quiet suppress non-error messages
./cli/db-restore.php --delete-backup --force-overwrite
# Restore all users respective database from `data/users/*/backup.sqlite`
# --delete-backup: delete `data/users/*/backup.sqlite` after successful import
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
require __DIR__ . '/_cli.php';
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
$cliOptions = new class extends CliOptionsParser {
public bool $quiet;
public function __construct() {
$this->addOption('quiet', (new CliOption('quiet', 'q'))->withValueNone());
parent::__construct();
}
};
if (!empty($cliOptions->errors)) {
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
}
$config = FreshRSS_Context::systemConf()->auto_sqlite_export;
$enabled = !empty($config['enabled']);
$retention = max(1, (int)($config['retention'] ?? 7));
$verbose = !$cliOptions->quiet;
if (!$enabled) {
if ($verbose) {
echo "FreshRSS automatic SQLite export is disabled (see `auto_sqlite_export.enabled` in `data/config.php`).\n";
}
exit(0);
}
$ok = true;
$timestamp = gmdate('Ymd\THis\Z');
foreach (FreshRSS_user_Controller::listUsers() as $username) {
$username = cliInitUser($username);
$exportDir = DATA_PATH . '/users/' . $username . '/sqlite-backups';
if (!is_dir($exportDir) && !@mkdir($exportDir, 0755, true)) {
fwrite(STDERR, "FreshRSS error: unable to create export directory: {$exportDir}\n");
$ok = false;
continue;
}
$filename = $exportDir . '/' . $timestamp . '.sqlite';
if ($verbose) {
echo 'FreshRSS automatic SQLite export for user “', $username, '” -> ', $filename, "\n";
}
$databaseDAO = FreshRSS_Factory::createDatabaseDAO($username);
$exported = $databaseDAO->dbCopy($filename, FreshRSS_DatabaseDAO::SQLITE_EXPORT, false, $verbose);
$ok = $ok && $exported;
if (!$exported) {
continue;
}
$existing = glob($exportDir . '/*.sqlite') ?: [];
if (count($existing) > $retention) {
sort($existing);
$toDelete = array_slice($existing, 0, count($existing) - $retention);
foreach ($toDelete as $old) {
if (@unlink($old)) {
if ($verbose) {
echo "Pruned old export: {$old}\n";
}
} else {
fwrite(STDERR, "FreshRSS warning: failed to prune old export: {$old}\n");
}
}
}
}
done($ok);
+10
View File
@@ -210,6 +210,16 @@ return [
'from' => 'root@localhost',
],
# Automatic SQLite export of each users database, triggered by `./cli/export-sqlite-auto.php`.
# Intended to be scheduled by an admin (e.g. via cron) for periodic on-server backups
# distinct from the manual `./cli/db-backup.php` / `./cli/db-restore.php` migration workflow.
'auto_sqlite_export' => [
# Enable the automatic export. When false, `./cli/export-sqlite-auto.php` exits without writing.
'enabled' => false,
# Number of past exports to retain per user. Older files are pruned after a successful export.
'retention' => 7,
],
# List of enabled FreshRSS extensions.
'extensions_enabled' => [
],
+19
View File
@@ -57,6 +57,25 @@ cd /usr/share/FreshRSS/
./cli/db-restore.php --delete-backup --force-overwrite
```
## Automatic periodic SQLite export
For ongoing on-server backups, separate from the one-shot `db-backup.php` / `db-restore.php` migration workflow, enable automatic SQLite export in `./data/config.php`:
```php
'auto_sqlite_export' => [
'enabled' => true,
'retention' => 7,
],
```
Then schedule it (for example via cron):
```sh
./cli/export-sqlite-auto.php
```
Each run writes `./data/users/<username>/sqlite-backups/<YYYYMMDDTHHMMSSZ>.sqlite` (UTC) for every user and prunes older files to the configured `retention` count.
## Migrating the database
First, back up all user databases to SQLite files: