Skip to content

Commit

Permalink
Improvments to settings backup file maintenance for plugins
Browse files Browse the repository at this point in the history
Add login to keep 5 most setting backups that were triggered by plugins via api/config_file. This should help stop flooding the settings backups with changes from a plugin writing get settings frequently.

Increased how many settings backups to keep from 45 to 60
  • Loading branch information
jaredb7 authored and dkulp committed Dec 9, 2023
1 parent 7bbfa76 commit 84e10ae
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 16 deletions.
26 changes: 23 additions & 3 deletions www/api/controllers/backups.php
Expand Up @@ -345,6 +345,7 @@ function process_jsonbackup_file_data_helper($json_config_backup_Data, $source_d
//process each of the backups and read out the backup comment, and work out the date it was created
foreach ($json_config_backup_Data as $backup_filename => $backup_data) {
$backup_data_comment = '';
$backup_data_trigger_source = null;
$backup_alternative = false;
$backup_filepath = $source_directory;
//Check to see if the source direct is the same as the default or not, if it is't then the the directory is the alternative backup directory (USB or something)
Expand All @@ -359,6 +360,9 @@ function process_jsonbackup_file_data_helper($json_config_backup_Data, $source_d
if (array_key_exists('backup_comment', $decoded_backup_data)) {
$backup_data_comment = $decoded_backup_data['backup_comment'];
}
if (array_key_exists('backup_trigger_source', $decoded_backup_data)) {
$backup_data_trigger_source = $decoded_backup_data['backup_trigger_source'];
}

//Locate the last underscore, this appears before the date/time in the filename
$backup_date_time_pos = strrpos($backup_filename_clean, '_');
Expand All @@ -372,6 +376,7 @@ function process_jsonbackup_file_data_helper($json_config_backup_Data, $source_d
'backup_filedirectory' => $backup_filepath,
'backup_filename' => $backup_filename,
'backup_comment' => $backup_data_comment,
'backup_trigger_source' => $backup_data_trigger_source,
'backup_time' => $backup_date_time,
'backup_time_unix' => $backup_date_time_unix
);
Expand All @@ -397,16 +402,31 @@ function MakeJSONBackup()
$fpp_backup_max_age, $fpp_backup_min_number_kept,
$fpp_backup_location, $fpp_backup_location_alternate_drive;

//Get the backup comment out of the post data
$backup_comment = file_get_contents('php://input');
$trigger_source = null;

//Get data out of the post data
$input_data = file_get_contents('php://input');

//Try JSON decode the input in case it's an array with extra data
$input_data_decoded = json_decode($input_data, true);

if (json_last_error() === JSON_ERROR_NONE) {
// JSON is valid, get comment and trigger source
$backup_comment = $input_data_decoded['backup_comment'];
$trigger_source = $input_data_decoded['trigger_source'];
} else {
//It's just a string so it'll be just a comment
//Get the backup comment out of the post data
$backup_comment = $input_data;
}

$toUSBResult = false;

//Include the FPP backup script
require_once "../backup.php";

//Create the backup and return the status
$backup_creation_status = performBackup('all', false, $backup_comment);
$backup_creation_status = performBackup('all', false, $backup_comment, $trigger_source);

if (array_key_exists('success', $backup_creation_status) && $backup_creation_status['success'] == true) {
$toUSBResult = DoJsonBackupToUSB();
Expand Down
2 changes: 1 addition & 1 deletion www/api/controllers/configfile.php
Expand Up @@ -89,7 +89,7 @@ function UploadConfigFile()
fclose($f);

//Trigger a JSON Configuration Backup
GenerateBackupViaAPI('Config File ' . $baseFile . ' was uploaded/modified.');
GenerateBackupViaAPI('Config File ' . $baseFile . ' was uploaded/modified.', 'config_file/' . $baseFile);

$result['Status'] = 'OK';
$result['Message'] = '';
Expand Down
62 changes: 53 additions & 9 deletions www/backup.php
Expand Up @@ -130,10 +130,12 @@ function checkDirectScriptExecution()

/////////////////////
/// HOUSE KEEPING ///
//Max age for stored settings backup files, Default: 90 days
//NOT USED - Max age for stored settings backup files, Default: 90 days
$fpp_backup_max_age = 90;
//Minimum number of backups we want to keep, this works with max age to ensure we have at least some backups left and not wipe out all of them if they were all old. Default: 45
$fpp_backup_min_number_kept = 45;
//Max number of backup files we want to keep Default: 45
$fpp_backup_min_number_kept = 60;
//Max number of backup per plugin or by trigger source (eg. /api/config_file/someconfig all backup generated via this endpoint will have a trigger source of config_file/someconfig we'll limit those backups to the below, default: 5
$fpp_backup_max_plugin_backups_key = 5;

////////////////////
/// DEBUGGING
Expand Down Expand Up @@ -1793,8 +1795,9 @@ function SavePixelnetDMXFile_F16v2Alpha($restore_data)
* @param string $area Backup area to be backed up, refer $system_config_areas for the currently defined list of area
* @param bool $allowDownload Toggle to allow or disallow the backup file being sent to the users browser
* @param string $backupComment Optionally add a comment to the backup file that may be useful to describe what the backup is for
* @param string $backupTriggerSource Optionally add where/what triggered the backup
*/
function performBackup($area = "all", $allowDownload = true, $backupComment = "User Initiated Manual Backup")
function performBackup($area = "all", $allowDownload = true, $backupComment = "User Initiated Manual Backup", $backupTriggerSource = null)
{
global $fpp_backup_version, $system_config_areas, $protectSensitiveData, $known_json_config_files, $known_ini_config_files;

Expand Down Expand Up @@ -2076,6 +2079,8 @@ function performBackup($area = "all", $allowDownload = true, $backupComment = "U

//Add the backup comment into settings data that has been gathered
$tmp_settings_data['backup_comment'] = $backupComment;
//Add the trigger source also, this may be used internally when clearing/manging number of backup files
$tmp_settings_data['backup_trigger_source'] = $backupTriggerSource;
//Lastly insert the backup system version (moved from doBackupDownload to here)
$tmp_settings_data['fpp_backup_version'] = $fpp_backup_version;
//Add the current UNIX epoc time, representing the time the backup as taken, may make it easier in the future to calculate when the backup was taken
Expand Down Expand Up @@ -2343,15 +2348,18 @@ function changeLocalFPPBackupFileLocation($new_location)
*/
function pruneOrRemoveAgedBackupFiles()
{
global $fpp_backup_max_age, $fpp_backup_min_number_kept, $fpp_backup_location, $backups_verbose_logging;
global $fpp_backup_max_age, $fpp_backup_min_number_kept, $fpp_backup_max_plugin_backups_key, $fpp_backup_location, $backups_verbose_logging;

//gets all the files in the configs/backup directory via the API now since it will look in multiple areas
$config_dir_files = file_get_contents('http://localhost/api/backups/configuration/list');
$config_dir_files = json_decode($config_dir_files, true);
$config_dir_files_tmp = $config_dir_files;
$backups_to_delete = array();
$num_backups_deleted = 0;

//If the number of backup files that exist IS LESS than what the minimum we want to keep, return and stop processing
if (count($config_dir_files) < $fpp_backup_min_number_kept) {
$aged_backup_removal_message = "SETTINGS BACKUP: NOT removing JSON Settings backup files older than $fpp_backup_max_age days. Since there are (" . count($config_dir_files) . ") backups available and this is less than the minimum backups we want to keep ($fpp_backup_min_number_kept) \r\n";
$aged_backup_removal_message = "SETTINGS BACKUP: NOT removing JSON Settings backup files. Since there are (" . count($config_dir_files) . ") backups available and this is less than the minimum backups we want to keep ($fpp_backup_min_number_kept) \r\n";
error_log($aged_backup_removal_message);
return;
}
Expand All @@ -2360,10 +2368,46 @@ function pruneOrRemoveAgedBackupFiles()
// $config_dir_files = array_reverse($config_dir_files);

//Select the backups that lie outside what we want to keep, because the array is a list of backups with the newest first then we'll be selecting older/dated backups
$backups_to_delete = array_slice($config_dir_files, $fpp_backup_min_number_kept);
$num_backups_deleted = 0;
$plugin_backups_to_delete_tmp = array();

//Look through all the backups, select ones with a trigger source, because the backups are newest first, we'll be selecting the file at the position of our max limit through to the end of the list
$backups_by_trigger_source = array();
foreach ($config_dir_files as $backups_by_trigger_source_index => $backups_by_trigger_source_meta_data) {
$backup_filename = $backups_by_trigger_source_meta_data['backup_filename'];

if ((stripos(strtolower($backup_filename), "-backup_") !== false) && (stripos(strtolower($backup_filename), ".json") !== false)) {
//
$backup_comment = $backups_by_trigger_source_meta_data['backup_comment'];
if (array_key_exists('backup_trigger_source', $backups_by_trigger_source_meta_data) && $backups_by_trigger_source_meta_data['backup_trigger_source'] !== null) {
//Get the trigger source name & add it and the backup meta data to a temp array, this will group like sources together
//then we'll limit those lists to enforce our backup limit
$backup_trigger_source = $backups_by_trigger_source_meta_data['backup_trigger_source'];
$backups_by_trigger_source[$backup_trigger_source][] = $backups_by_trigger_source_meta_data;

//Check the number of items for this trigger source, if it's > than our allowed limit then add the backup file to the $backups_to_delete list
if (count($backups_by_trigger_source[$backup_trigger_source]) > $fpp_backup_max_plugin_backups_key) {
$plugin_backups_to_delete_tmp[] = $backups_by_trigger_source_meta_data;
//Remove the entry from the list of backup files, because it will be deleted (we want this list to reflect the list after enforcing plugin backup limits)
//so that we can enforce the global limit after
unset($config_dir_files_tmp[$backups_by_trigger_source_index]);
}
}
}
}

//Fix indexes
$config_dir_files_tmp = array_values($config_dir_files_tmp);

if (count($config_dir_files_tmp) > $fpp_backup_min_number_kept) {
$backups_to_delete = array_slice($config_dir_files_tmp, $fpp_backup_min_number_kept);
}

//tack on the plugin backups to be deleted
foreach ($plugin_backups_to_delete_tmp as $plugin_backups_to_delete_tmp_data) {
$backups_to_delete[] = $plugin_backups_to_delete_tmp_data;
}

//loop over the backup files we've found
//loop over the backup files we've found that are beyond our set limit, all these to be deleted
foreach ($backups_to_delete as $backup_file_index => $backup_file_meta_data) {
$backup_filename = $backup_file_meta_data['backup_filename'];

Expand Down
11 changes: 8 additions & 3 deletions www/common.php
Expand Up @@ -2508,20 +2508,25 @@ function read_directory_files($directory, $return_data = true, $sort_by_date = f
/**
* Makes a POST Call to the api/backups/configuration to generate a JSON Configuration backup with a option comment
* @param $backup_comment string Optional Comment that will be inserted into the JSON backup file
* @param $trigger_source string Optional Source that triggered the backup,
* @return bool
*/
function GenerateBackupViaAPI($backup_comment = "Created via API")
function GenerateBackupViaAPI($backup_comment = "Created via API", $trigger_source = null)
{
$url = 'http://localhost/api/backups/configuration';
$data = array($backup_comment);
$data = $backup_comment;

if (!is_null($trigger_source)) {
$data = json_encode(array('backup_comment' => $backup_comment, 'trigger_source' => $trigger_source));
}

//https://stackoverflow.com/questions/5647461/how-do-i-send-a-post-request-with-php
// use key 'http' even if you send the request to https://...
$options = array(
'http' => array(
'header' => "Content-type: text/plain",
'method' => 'POST',
'content' => $backup_comment,
'content' => $data,
),
);
$context = stream_context_create($options);
Expand Down

0 comments on commit 84e10ae

Please sign in to comment.