diff --git a/configs/backup/backup_general b/configs/backup/backup_general index 285cffd..5dfdba3 100755 --- a/configs/backup/backup_general +++ b/configs/backup/backup_general @@ -2,4 +2,3 @@ # Backup General - Scheduling # ================================================================================ CFG_BACKUP_CRONTAB_APP="0 5 * * *" # App Backup Schedule - Crontab schedule for application backups -CFG_BACKUP_CRONTAB_APP_INTERVAL=3 # App Backup Interval - Minutes between app backup checks diff --git a/containers/libreportal/frontend/js/components/config/config-shared.js b/containers/libreportal/frontend/js/components/config/config-shared.js index 6a57ad5..6dd6421 100755 --- a/containers/libreportal/frontend/js/components/config/config-shared.js +++ b/containers/libreportal/frontend/js/components/config/config-shared.js @@ -650,13 +650,6 @@ class ConfigShared { fieldHTML += ` `; - } else if (key === 'CFG_BACKUP_CRONTAB_APP_INTERVAL') { - fieldHTML += ` -
- - minutes -
- `; } else if (/^CFG_BACKUP(_LOC_[0-9]+)?_KEEP_(LAST|DAILY|WEEKLY|MONTHLY|YEARLY)$/.test(key)) { const unitLabel = key.endsWith('_KEEP_LAST') ? 'snapshots' : key.endsWith('_KEEP_DAILY') ? 'days' : diff --git a/scripts/backup/app/backup_schedule_all.sh b/scripts/backup/app/backup_schedule_all.sh new file mode 100644 index 0000000..bdccb1e --- /dev/null +++ b/scripts/backup/app/backup_schedule_all.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Enqueue a backup for every installed app that has backups enabled. Invoked +# once daily by the backup scheduler crontab entry. Each app is handed to +# backupAppSchedule, which queues a task for the processor (WebUI installs) or +# runs the backup inline (terminal-only installs). +backupScheduleEnabledApps() +{ + isHeader "Scheduling backups for enabled applications" + + if [ ! -f "$docker_dir/$db_file" ]; then + isError "Database not found: $docker_dir/$db_file" + return 1 + fi + + local app_names=() + while IFS= read -r name; do + [[ -z "$name" ]] && continue + app_names+=("$name") + done < <(sudo sqlite3 "$docker_dir/$db_file" "SELECT name FROM apps WHERE status = 1;") + + local queued=0 + for name in "${app_names[@]}"; do + local backup_flag="CFG_${name^^}_BACKUP" + if [[ "${!backup_flag}" == "true" ]]; then + backupAppSchedule "$name" + ((queued++)) + fi + done + + isSuccessful "Backup scheduling complete — $queued app(s) queued" +} diff --git a/scripts/cli/commands/backup/cli_backup_commands.sh b/scripts/cli/commands/backup/cli_backup_commands.sh index 2e93182..c93f5e2 100755 --- a/scripts/cli/commands/backup/cli_backup_commands.sh +++ b/scripts/cli/commands/backup/cli_backup_commands.sh @@ -54,6 +54,9 @@ cliHandleBackupCommands() all) backupAllApps ;; + scheduled) + backupScheduleEnabledApps + ;; location) case "$action" in add) diff --git a/scripts/cli/commands/backup/cli_backup_header.sh b/scripts/cli/commands/backup/cli_backup_header.sh index 4b5ad02..95a060a 100755 --- a/scripts/cli/commands/backup/cli_backup_header.sh +++ b/scripts/cli/commands/backup/cli_backup_header.sh @@ -21,6 +21,9 @@ cliShowBackupHelp() echo "backup all" echo " Snapshot every installed app." echo "" + echo "backup scheduled" + echo " Queue a backup for every app with backups enabled (daily cron entry)." + echo "" echo "backup location add [type]" echo " Add a new backup location. Type defaults to 'local'." echo " Types: local, sftp, rest, s3, b2, gs, azure, rclone" diff --git a/scripts/config/application/application_menu_apps.sh b/scripts/config/application/application_menu_apps.sh index 8c7ad7c..f7e5d7f 100755 --- a/scripts/config/application/application_menu_apps.sh +++ b/scripts/config/application/application_menu_apps.sh @@ -40,7 +40,7 @@ viewAppConfigs() isNotice "Exiting..." echo "" checkConfigFilesMissingVariables true - crontabSetupAllAppBackups true + crontabSetupBackupScheduler fi elif [[ "$selected_app_number" =~ ^[0-9]+$ ]] && [ "$selected_app_number" -ge 1 ] && [ "$selected_app_number" -le ${#installed_apps[@]} ]; then local index=$((selected_app_number - 1)) diff --git a/scripts/config/core/config_manage_menu.sh b/scripts/config/core/config_manage_menu.sh index e89f029..76317ce 100755 --- a/scripts/config/core/config_manage_menu.sh +++ b/scripts/config/core/config_manage_menu.sh @@ -102,7 +102,7 @@ viewLibrePortalConfigs() isNotice "Exiting..." echo "" checkConfigFilesMissingVariables true; - crontabSetupAllAppBackups true; + crontabSetupBackupScheduler; fi elif [[ "$selected_number" =~ ^[0-9]+$ ]] && [ "$selected_number" -ge 1 ] && [ "$selected_number" -le ${#config_files[@]} ]; then local index=$((selected_number - 1)) diff --git a/scripts/crontab/app/crontab_backup_all_apps.sh b/scripts/crontab/app/crontab_backup_all_apps.sh deleted file mode 100755 index 6051aae..0000000 --- a/scripts/crontab/app/crontab_backup_all_apps.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash - -crontabSetupAllAppBackups() -{ - local show_header=$1 - local ISCRON=$( (sudo -u $sudo_user_name crontab -l) 2>/dev/null ) - - # Check to see if installed - if [[ "$ISCRON" == *"command not found"* ]]; then - isNotice "Crontab is not found. Unable to set up backups." - fi - - # Check to see if crontab is not installed - if ! sudo -u $sudo_user_name crontab -l 2>/dev/null | grep -q "cron is set up for $sudo_user_name" > /dev/null 2>&1; then - isNotice "Crontab is not set up, skipping until it's found." - fi - - # Check if the database file exists - if [ ! -f "$docker_dir/$db_file" ]; then - isNotice "Database file not found: $docker_dir/$db_file" - fi - - if [[ $show_header != "false" ]]; then - isHeader "Backup Crontab Install" - fi - - local app_names=() - while IFS= read -r name; do - local app_names+=("$name") - done < <(sudo sqlite3 "$docker_dir/$db_file" "SELECT name FROM apps WHERE status = 1;") - - # Check if sqlite3 is available - if ! command -v sudo sqlite3 &> /dev/null; then - isNotice "sqlite3 command not found. Make sure it's installed." - fi - - # Remove crontab entries for applications with status = 0 (uninstalled) - while IFS= read -r name; do - local uninstalled_apps+=("$name") - done < <(sudo sqlite3 "$docker_dir/$db_file" "SELECT name FROM apps WHERE status = 0;") - - for name in "${uninstalled_apps[@]}"; do - removeBackupCrontabAppFolderRemoved $name - done - - # Setup crontab entries for installed applications - for name in "${app_names[@]}"; do - checkBackupCrontabApp $name - done - - crontabClean; - isSuccessful "Setting up Crontab backups for application(s) completed." -} - diff --git a/scripts/crontab/app/crontab_backup_scheduler.sh b/scripts/crontab/app/crontab_backup_scheduler.sh new file mode 100644 index 0000000..573d918 --- /dev/null +++ b/scripts/crontab/app/crontab_backup_scheduler.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Install (or refresh) the single daily crontab entry that drives application +# backups. The entry runs `libreportal backup scheduled`, which enqueues a +# backup task per enabled app for the processor to drain serially. +crontabSetupBackupScheduler() +{ + local ISCRON=$( (sudo -u $sudo_user_name crontab -l) 2>/dev/null ) + + if [[ "$ISCRON" == *"command not found"* ]]; then + isNotice "Crontab is not found. Unable to set up the backup scheduler." + return 0 + fi + + if ! sudo -u $sudo_user_name crontab -l 2>/dev/null | grep -q "cron is set up for $sudo_user_name"; then + isNotice "Crontab is not set up, skipping backup scheduler until it's found." + return 0 + fi + + local marker="# CRONTAB BACKUP SCHEDULER" + local scheduler_entry="$CFG_BACKUP_CRONTAB_APP libreportal backup scheduled $marker" + + # Drop any previous scheduler entry, then re-add the current one so a + # changed schedule (CFG_BACKUP_CRONTAB_APP) always takes effect. + local result=$(sudo -u $sudo_user_name crontab -l 2>/dev/null | grep -v "$marker" | sudo -u $sudo_user_name crontab -) + local result=$( (sudo -u $sudo_user_name crontab -l 2>/dev/null; echo "$scheduler_entry") | sudo -u $sudo_user_name crontab - ) + checkSuccess "Installing the daily backup scheduler entry" + + local schedule_time=$(echo "$CFG_BACKUP_CRONTAB_APP" | cut -d' ' -f2) + isSuccessful "Enabled apps will be queued for backup daily at ${schedule_time}:00" +} diff --git a/scripts/crontab/app/crontab_check_backup_app.sh b/scripts/crontab/app/crontab_check_backup_app.sh deleted file mode 100755 index 3f59ab7..0000000 --- a/scripts/crontab/app/crontab_check_backup_app.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -checkBackupCrontabApp() -{ - local name="$1" - local config_variable="CFG_${name^^}_BACKUP" - local desired="${!config_variable}" - local has_cron=0 - if sudo -u $sudo_user_name crontab -l 2>/dev/null | grep -q "$name"; then - has_cron=1 - fi - - if [[ "$desired" == "true" && $has_cron -eq 0 ]]; then - installSetupCrontab "$name" - databaseCronJobsInsert "$name" - isSuccessful "Backup crontab enabled for $name." - elif [[ "$desired" == "false" && $has_cron -eq 1 ]]; then - removeBackupCrontabApp "$name" - isSuccessful "Backup crontab removed for $name (disabled in config)." - fi -} diff --git a/scripts/crontab/app/crontab_remove_backup_app.sh b/scripts/crontab/app/crontab_remove_backup_app.sh deleted file mode 100755 index 2257d73..0000000 --- a/scripts/crontab/app/crontab_remove_backup_app.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -removeBackupCrontabApp() -{ - local name="$1" - # Remove the crontab entry for the specified application - sudo -u $sudo_user_name crontab -l 2>/dev/null | grep -v "$name" | sudo -u $sudo_user_name crontab - - isSuccessful "Automatic backups for $name have been removed." -} diff --git a/scripts/crontab/app/crontab_remove_folder.sh b/scripts/crontab/app/crontab_remove_folder.sh deleted file mode 100755 index 283bc71..0000000 --- a/scripts/crontab/app/crontab_remove_folder.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -removeBackupCrontabAppFolderRemoved() -{ - local name="$1" - - # Check if the crontab entry exists for the specified application - if sudo -u $sudo_user_name crontab -l 2>/dev/null | grep -q "$name"; then - echo "" - isNotice "Application $name is no longer installed." - while true; do - isQuestion "Do you want to remove automatic backups for $name (y/n): " - read -rp "" removecrontab - if [[ "$removecrontab" =~ ^[yYnN]$ ]]; then - break - fi - isNotice "Please provide a valid input (y/n)." - done - if [[ "$removecrontab" =~ ^[yY]$ ]]; then - removeBackupCrontabApp $name; - fi - fi -} diff --git a/scripts/crontab/app/install/crontab_setup.sh b/scripts/crontab/app/install/crontab_setup.sh deleted file mode 100755 index 72d3392..0000000 --- a/scripts/crontab/app/install/crontab_setup.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# Function to set up the backup entry in crontab -installSetupCrontab() -{ - local entry_name="$1" - - isHeader "Adding $entry_name to Crontab" - - # Check to see if already instealled - if ! sudo -u $sudo_user_name crontab -l 2>/dev/null | grep -q "cron is set up for $sudo_user_name"; then - isError "Crontab is not setup" - fi - - local crontab_entry="$CFG_BACKUP_CRONTAB_APP libreportal backup app schedule $entry_name" - local apps_comment="# CRONTAB BACKUP APPS" - local existing_crontab=$(sudo -u $sudo_user_name crontab -l 2>/dev/null) - - # Check if the apps comment exists in the crontab - if ! echo "$existing_crontab" | grep -q "$apps_comment"; then - existing_crontab=$(echo -e "$existing_crontab\n$apps_comment") - checkSuccess "Insert the apps comment header" - fi - existing_crontab=$(echo "$existing_crontab" | sed "/$apps_comment/a\\ -$crontab_entry") - checkSuccess "Insert the backup entry after the apps comment" - - local result=$(echo "$existing_crontab" | sudo -u $sudo_user_name crontab -) - checkSuccess "Set the updated crontab" - - crontab_full_value=$(echo "$CFG_BACKUP_CRONTAB_APP" | cut -d' ' -f2) - isSuccessful "$entry_name will be backed up every day at $crontab_full_value:am" -} diff --git a/scripts/crontab/app/install/crontab_timing.sh b/scripts/crontab/app/install/crontab_timing.sh deleted file mode 100755 index c815715..0000000 --- a/scripts/crontab/app/install/crontab_timing.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -# Function to update a specific line in the crontab -installSetupCrontabTiming() -{ - local entry_name=$1 - ISCRON=$( (sudo -u $sudo_user_name crontab -l) 2>/dev/null ) - - # Check to see if installed - if [[ "$ISCRON" == *"command not found"* ]]; then - isError "Cron is not installed." - fi - - # Check to see if already setup - if ! sudo -u $sudo_user_name crontab -l 2>/dev/null | grep -q "cron is set up for $sudo_user_name"; then - isError "Crontab is not setup" - fi - - # Check if sqlite3 is available - if ! command -v sqlite3 &> /dev/null; then - isNotice "sqlite3 command not found. Make sure it's installed." - fi - - # Ensure the database file exists - if [ ! -f "$docker_dir/$db_file" ]; then - isNotice "Database file not found: $docker_dir/$db_file" - fi - - # Step 1: Retrieve the necessary information from the database - db_entry=$(sqlite3 "$docker_dir/$db_file" "SELECT id, name FROM cron_jobs WHERE name='$entry_name';") - IFS='|' read -r id name <<< "$db_entry" - - # Check if the entry exists in the database - if [[ -z "$id" ]]; then - isNotice "Entry '$entry_name' not found in the database." - fi - - # Calculate the new minute value based on the ID - new_minute_value=$((id * $CFG_BACKUP_CRONTAB_APP_INTERVAL)) - - # Step 2: Locate the existing crontab entry in the crontab file - crontab_entry_to_update=$(sudo -u $sudo_user_name crontab -l 2>/dev/null | grep "$entry_name") - - # Check if the entry exists in the crontab - if [[ -z "$crontab_entry_to_update" ]]; then - isError "Entry '$entry_name' not found in the crontab." - fi - - # Extract the existing minute value from the current crontab entry - current_minute_value=$(echo "$crontab_entry_to_update" | awk '{print $1}') - - # Step 3: Update the minute value in the identified crontab entry - updated_crontab_entry="${crontab_entry_to_update/$current_minute_value/$new_minute_value}" - - # Assuming CFG_BACKUP_CRONTAB_APP is set to "0 5 * * *" - crontab_app_value=$(echo "$CFG_BACKUP_CRONTAB_APP" | cut -d' ' -f2) - - local result=$(sudo -u $sudo_user_name crontab -l 2>/dev/null | grep -v "$entry_name" | sudo -u $sudo_user_name crontab - ) - checkSuccess "Remove the existing crontab entry" - local result=$( (sudo -u $sudo_user_name crontab -l 2>/dev/null; echo "$updated_crontab_entry") | sudo -u $sudo_user_name crontab - ) - checkSuccess "Add the updated crontab entry" - - isSuccessful "Crontab entry for '$entry_name' updated successfully." - isSuccessful "$entry_name will be backed up every day at $crontab_app_value:${new_minute_value}am" -} diff --git a/scripts/crontab/crontab_refresh.sh b/scripts/crontab/crontab_refresh.sh index f2286e7..6115c73 100755 --- a/scripts/crontab/crontab_refresh.sh +++ b/scripts/crontab/crontab_refresh.sh @@ -9,7 +9,7 @@ crontabRefresh() crontabSetup; # Rebuild from scratch - crontabSetupAllAppBackups "false"; + crontabSetupBackupScheduler; #crontabSetupTaskProcessor # Switched to Systemd crontabSetupSystemInfoUpdater; diff --git a/scripts/database/insert/db_insert_cron_jobs.sh b/scripts/database/insert/db_insert_cron_jobs.sh deleted file mode 100755 index 09b0c5b..0000000 --- a/scripts/database/insert/db_insert_cron_jobs.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -databaseCronJobsInsert() -{ - local app_name="$1" - local table_name=cron_jobs - local key_in_db=$(sudo sqlite3 "$docker_dir/$db_file" "SELECT COUNT(*) FROM $table_name WHERE name = '$app_name';") - - if [ "$key_in_db" != "" ]; then - if [ "$key_in_db" -eq 0 ]; then - local result=$(sudo sqlite3 "$docker_dir/$db_file" "INSERT INTO $table_name (name, date, time) VALUES ('$app_name', '$current_date', '$current_time');") - checkSuccess "Adding $app_name to the $table_name table." - else - local result=$(sudo sqlite3 "$docker_dir/$db_file" "UPDATE $table_name SET name = '$app_name', date = '$current_date', time = '$current_time' WHERE name = '$app_name';") - checkSuccess "$app_name already added to the $table_name table. Updating date/time." - fi - #isNotice "app_name is empty, unable to insert" - fi -} diff --git a/scripts/database/tables/db_create_tables.sh b/scripts/database/tables/db_create_tables.sh index 5840dd9..e29d676 100755 --- a/scripts/database/tables/db_create_tables.sh +++ b/scripts/database/tables/db_create_tables.sh @@ -67,13 +67,6 @@ databaseCreateTables() checkSuccess "Creating $setup_table_name table" fi - setup_table_name=cron_jobs - if ! sqlite3 "$docker_dir/$db_file" ".tables" | grep -q "\b$setup_table_name\b"; then - # Table info here - local result=$(sqlite3 $docker_dir/$db_file "CREATE TABLE IF NOT EXISTS $setup_table_name (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, date DATE, time TIME);") - checkSuccess "Creating $setup_table_name table" - fi - setup_table_name=network_resources if ! sqlite3 "$docker_dir/$db_file" ".tables" | grep -q "\b$setup_table_name\b"; then # Simple unified network resources table - replaces all complex network tables diff --git a/scripts/menu/tools/manage_crontab.sh b/scripts/menu/tools/manage_crontab.sh index 45750f0..3127866 100755 --- a/scripts/menu/tools/manage_crontab.sh +++ b/scripts/menu/tools/manage_crontab.sh @@ -7,7 +7,7 @@ crontabToolsMenu() while true; do isHeader "Crontab Menu" - isOption "1. Scan apps for Crontab Backup" + isOption "1. Set up Backup Scheduler" isOption "2. Force Crontab Reinstall" isOption "x. Exit to Main Menu" echo "" diff --git a/scripts/source/files/arrays/files_backup.sh b/scripts/source/files/arrays/files_backup.sh index 5428f0a..0b25f76 100755 --- a/scripts/source/files/arrays/files_backup.sh +++ b/scripts/source/files/arrays/files_backup.sh @@ -9,6 +9,7 @@ backup_scripts=( "backup/app/backup_app_hooks.sh" "backup/app/backup_app_schedule.sh" "backup/app/backup_app_start.sh" + "backup/app/backup_schedule_all.sh" "backup/engine/backup_ssh.sh" "backup/engine/borg_backup.sh" "backup/engine/borg_check.sh" diff --git a/scripts/source/files/arrays/files_crontab.sh b/scripts/source/files/arrays/files_crontab.sh index 0ed1178..38e1e51 100755 --- a/scripts/source/files/arrays/files_crontab.sh +++ b/scripts/source/files/arrays/files_crontab.sh @@ -4,12 +4,7 @@ # Do not edit manually - run './scripts/source/files/generate_arrays.sh run' to regenerate crontab_scripts=( - "crontab/app/crontab_backup_all_apps.sh" - "crontab/app/crontab_check_backup_app.sh" - "crontab/app/crontab_remove_backup_app.sh" - "crontab/app/crontab_remove_folder.sh" - "crontab/app/install/crontab_setup.sh" - "crontab/app/install/crontab_timing.sh" + "crontab/app/crontab_backup_scheduler.sh" "crontab/crontab_clean.sh" "crontab/crontab_clear.sh" "crontab/crontab_install.sh" diff --git a/scripts/source/files/arrays/files_database.sh b/scripts/source/files/arrays/files_database.sh index 591a11c..899bf0b 100755 --- a/scripts/source/files/arrays/files_database.sh +++ b/scripts/source/files/arrays/files_database.sh @@ -14,7 +14,6 @@ database_scripts=( "database/check_os_update.sh" "database/delete_db_file.sh" "database/insert/db_insert_backups.sh" - "database/insert/db_insert_cron_jobs.sh" "database/insert/db_insert_option.sh" "database/insert/db_insert_port_open.sh" "database/insert/db_insert_port_used.sh" diff --git a/scripts/start/start_other.sh b/scripts/start/start_other.sh index 92a4d87..6b3ef6a 100755 --- a/scripts/start/start_other.sh +++ b/scripts/start/start_other.sh @@ -33,7 +33,7 @@ startOther() fi if [[ "$toolsstartcrontabsetup" == [yY] ]]; then - crontabSetupAllAppBackups + crontabSetupBackupScheduler fi if [[ "$toolinstallcrontab" == [yY] ]]; then diff --git a/scripts/webui/data/generators/config/webui_update_config.sh b/scripts/webui/data/generators/config/webui_update_config.sh index 31688a9..25e6487 100755 --- a/scripts/webui/data/generators/config/webui_update_config.sh +++ b/scripts/webui/data/generators/config/webui_update_config.sh @@ -72,7 +72,7 @@ webuiValidateConfigValue() { isError " Invalid crontab format for $var_name" fi ;; - CFG_BACKUP_KEEP_LAST|CFG_BACKUP_KEEP_DAILY|CFG_BACKUP_KEEP_WEEKLY|CFG_BACKUP_KEEP_MONTHLY|CFG_BACKUP_KEEP_YEARLY|CFG_BACKUP_VERIFY_DATA_PERCENT|CFG_BACKUP_CRONTAB_APP_INTERVAL|CFG_UPDATER_CHECK|CFG_SWAPFILE_SIZE|CFG_GENERATED_PASS_LENGTH|CFG_WEBUI_LOG_STREAM_IDLE_TIMEOUT_MINUTES|CFG_WEBUI_LOG_STREAM_MAX_DURATION_MINUTES|CFG_WEBUI_LOG_STREAM_MAX_LINES_PER_SEC) + CFG_BACKUP_KEEP_LAST|CFG_BACKUP_KEEP_DAILY|CFG_BACKUP_KEEP_WEEKLY|CFG_BACKUP_KEEP_MONTHLY|CFG_BACKUP_KEEP_YEARLY|CFG_BACKUP_VERIFY_DATA_PERCENT|CFG_UPDATER_CHECK|CFG_SWAPFILE_SIZE|CFG_GENERATED_PASS_LENGTH|CFG_WEBUI_LOG_STREAM_IDLE_TIMEOUT_MINUTES|CFG_WEBUI_LOG_STREAM_MAX_DURATION_MINUTES|CFG_WEBUI_LOG_STREAM_MAX_LINES_PER_SEC) # Validate numeric values if ! echo "$var_value" | grep -qE '^[0-9]+$'; then isError " $var_name must be a positive integer"