Răsfoiți Sursa

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>
polybjorn 1 săptămână în urmă
părinte
comite
d74337deb6

+ 1 - 0
app/Models/SystemConfiguration.php

@@ -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 - 0
cli/README.md

@@ -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 - 0
cli/export-sqlite-auto.php

@@ -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 - 0
config.default.php

@@ -210,6 +210,16 @@ return [
 		'from' => 'root@localhost',
 	],
 
+	# Automatic SQLite export of each user’s 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 - 0
docs/en/admins/05_Backup.md

@@ -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: