#!/bin/bash { #//////////////////////////////////// # DietPi Sync # #//////////////////////////////////// # Created by Daniel Knight / daniel.knight@dietpi.com / dietpi.com # #//////////////////////////////////// # # Info: # - filename /DietPi/dietpi/dietpi-sync # - Allows user to sync a Source and Target directory. # # Usage: # - /DietPi/dietpi/dietpi-sync 0 = Menu # - /DietPi/dietpi/dietpi-sync 1 = Sync #//////////////////////////////////// #Grab Input (valid interger) INPUT=0 if [[ $1 =~ ^-?[0-9]+$ ]]; then INPUT=$1 fi #Import DietPi-Globals --------------------------------------------------------------- . /DietPi/dietpi/func/dietpi-globals G_CHECK_ROOT_USER G_CHECK_ROOTFS_RW export G_PROGRAM_NAME='DietPi-Sync' #Import DietPi-Globals --------------------------------------------------------------- #///////////////////////////////////////////////////////////////////////////////////// # MENUS #///////////////////////////////////////////////////////////////////////////////////// WHIP_BACKTITLE='DietPi-Sync' WHIP_TITLE='' CHOICE=0 OPTION=0 TARGETMENUID=0 FTP_MOUNT_AVAILABLE=0 FTP_MOUNT_TEXT='Not available' SAMBA_MOUNT_AVAILABLE=0 SAMBA_MOUNT_TEXT='Not available' SYNC_DRY_RUN=0 SYNC_MODE_TEXT='Disabled' SYNC_COMPRESSION_TEXT='Disabled' SYNC_CRONDAILY_TEXT='Disabled' #TARGETMENUID=0 Menu_Main(){ SYNC_MODE_TEXT='Disabled' if (( $SYNC_DELETE_MODE == 1 )); then SYNC_MODE_TEXT='Enabled' fi SYNC_COMPRESSION_TEXT='Disabled' if (( $SYNC_COMPRESSION == 1 )); then SYNC_COMPRESSION_TEXT='Enabled' fi SYNC_CRONDAILY_TEXT='Disabled' if (( $SYNC_CRONDAILY == 1 )); then SYNC_CRONDAILY_TEXT='Enabled' fi local sync_last_completed='No previous sync found in target directory.' if [ -f "$FP_TARGET/$SYNC_STATS_FILENAME" ]; then sync_last_completed=$(grep '^Sync completed' "$FP_TARGET/$SYNC_STATS_FILENAME" | tail -1 | awk '{print $3}') fi WHIP_TITLE='DietPi-Sync' OPTION=$(whiptail --title "$WHIP_TITLE" --menu "Source location:\n $FP_SOURCE\n\nTarget location:\n $FP_TARGET\n\nMost recent successful sync date:\n $sync_last_completed" --cancel-button "Exit" --backtitle "$WHIP_BACKTITLE" 23 73 8 \ "Help" "What does DietPi-Sync do?" \ "Source Location" "Change the Source directory." \ "Target Location" "Change the Target directory." \ "Delete Mode" "$SYNC_MODE_TEXT" \ "Compression" "$SYNC_COMPRESSION_TEXT" \ "Sync: Daily" "$SYNC_CRONDAILY_TEXT" \ "Sync: Dry Run" "Run a test Sync without modifying any data." \ "Sync: Now" "Sync the Source location to the Target location." 3>&1 1>&2 2>&3) CHOICE=$? if (( $CHOICE == 0 )); then case "$OPTION" in "Source Location") TARGETMENUID=2 ;; "Target Location") TARGETMENUID=1 ;; Help) whiptail --title "DietPi-Sync Help" --msgbox "DietPi-Sync is a program that allows you to duplicate a directory from one location (Source) to another (Target).\n\nFor example: If we want to duplicate (sync) the data on our external USB HDD to another location, we simply select the USB HDD as the source, then, select a target location. The target location can be anything from a networked samba fileserver, or even a FTP server.\n\nIf you would like to test a sync without modifiying any data, simply select Dry Run.\n\nMore information:\n - http://dietpi.com/phpbb/viewtopic.php?f=8&t=5&p=256#p256" --backtitle "$WHIP_BACKTITLE" 19 75 ;; "Delete Mode") TARGETMENUID=3 ;; Compression) TARGETMENUID=4 ;; "Sync: Daily") TARGETMENUID=5 ;; "Sync: Dry Run") WHIP_TITLE=' Test Dry Run Sync? ' whiptail --title "$WHIP_TITLE" --yesno "Source location:\n$FP_SOURCE/*\n\nTarget location:\n$FP_TARGET/*\n\nThis is a Dry Run for testing. No data will be modified.\n\nDo you wish to continue?" --yes-button "Ok" --no-button "Back" --defaultno --backtitle "$WHIP_BACKTITLE" 16 72 CHOICE=$? if (( $CHOICE == 0 )); then SYNC_DRY_RUN=1 Run_Sync fi ;; "Sync: Now") WHIP_TITLE=' Start Sync? ' whiptail --title "$WHIP_TITLE" --yesno "Source location:\n$FP_SOURCE/*\n\nTarget location:\n$FP_TARGET/*\n\nA copy of all the files and folders inside your Source location, will be created at the Target location.\n\nDo you wish to continue?" --yes-button "Ok" --no-button "Back" --defaultno --backtitle "$WHIP_BACKTITLE" 16 72 CHOICE=$? if (( $CHOICE == 0 )); then SYNC_DRY_RUN=0 Run_Sync fi ;; esac else Menu_Exit fi } Menu_Exit(){ WHIP_TITLE=' Exit DietPi-Sync? ' whiptail --title "$WHIP_TITLE" --yesno "$WHIP_TITLE" --yes-button "Ok" --no-button "Back" --defaultno --backtitle "$WHIP_BACKTITLE" 10 50 CHOICE=$? #Exit if (( $CHOICE == 0 )); then TARGETMENUID=-1 fi } #TARGETMENUID=1 && TARGETMENUID=2 Menu_Set_Directories(){ #TARGETMENUID #2=Source | 1=Target local current_directory="$FP_TARGET" local current_mode_text="Target" local whip_description_text="Please select the $current_mode_text location.\nA copy of all the files and folders in the Source location will be created here.\n\nCurrent Target location:\n$FP_TARGET" WHIP_TITLE='Select your Sync Target location' if (( $TARGETMENUID == 2 )); then current_directory="$FP_SOURCE" current_mode_text="Source" whip_description_text="Please select the $current_mode_text location.\nA copy of all the files and folder in this Source location, will be created at the Target location.\n\nCurrent Source location:\n$FP_SOURCE" WHIP_TITLE='Select your Sync Source location' fi Check_Available_DietPi_Mounts OPTION=$(whiptail --title "$WHIP_TITLE" --menu "$whip_description_text" --cancel-button "Back" --backtitle "$WHIP_BACKTITLE" 17 75 4 \ "Manual" "Manually type your $current_mode_text directory." \ "List" "Select from a list of available mounts/drives" \ "Samba Client" "$SAMBA_MOUNT_TEXT" \ "FTP Client" "$FTP_MOUNT_TEXT" 3>&1 1>&2 2>&3) CHOICE=$? if (( $CHOICE == 0 )); then case "$OPTION" in List) /DietPi/dietpi/dietpi-drive_manager 1 local return_value=$(cat /tmp/dietpi-drive_manager_selmnt) if [ "$return_value" = "/" ]; then return_value='/mnt' fi if (( $TARGETMENUID == 2 )); then FP_SOURCE="$return_value" else FP_TARGET="$return_value/dietpi-sync" fi ;; Manual) Input_User_Directory ;; "Samba Client") if (( $SAMBA_MOUNT_AVAILABLE == 1 )); then if (( $TARGETMENUID == 2 )); then FP_SOURCE="$FP_SAMBA_MOUNT" else FP_TARGET="$FP_SAMBA_MOUNT/dietpi-sync" fi else Prompt_Setup_Samba_Mount if (( $TARGETMENUID == 2 )); then FP_SOURCE="$current_directory" else FP_TARGET="$current_directory" fi fi ;; "FTP Client") if (( $FTP_MOUNT_AVAILABLE == 1 )); then if (( $TARGETMENUID == 2 )); then FP_SOURCE="$FP_FTP_MOUNT" else FP_TARGET="$FP_FTP_MOUNT/dietpi-sync" fi else Prompt_Setup_FTP_Mount if (( $TARGETMENUID == 2 )); then FP_SOURCE="$current_directory" else FP_TARGET="$current_directory" fi fi ;; esac else #Return to main menu TARGETMENUID=0 fi } #TARGETMENUID=3 Menu_Set_Sync_Delete_Mode(){ WHIP_TITLE=' Sync Delete Mode ' OPTION=$(whiptail --title "$WHIP_TITLE" --menu "Please select the Sync delete mode.\n\nDisabled: (safe)\nIf files and folders exist in the Target location, that are not in the Source, they will be left alone.\n\nEnabled: (WARNING, if in doubt, DO NOT enable)\nAn exact copy of the Source location will be created at the Target location. If files are in the Target location that dont exist in the Source, they will be DELETED." --cancel-button "Back" --default-item "$SYNC_MODE_TEXT" --backtitle "$WHIP_BACKTITLE" 19 75 2 \ "Disabled" "Ignores data that exists at Target Location and not Source." \ "Enabled" "Deletes data at Target location if not in Source Location." 3>&1 1>&2 2>&3) CHOICE=$? if (( $CHOICE == 0 )); then case "$OPTION" in Disabled) SYNC_DELETE_MODE=0 ;; Enabled) SYNC_DELETE_MODE=1 ;; esac fi #Return to main menu TARGETMENUID=0 } #TARGETMENUID=4 Menu_Set_Compression(){ WHIP_TITLE=' Sync Compression Mode ' OPTION=$(whiptail --title "$WHIP_TITLE" --menu "Please select the compression mode.\n\nDisabled:\nNo compression will be used during data transfer.\n\nEnabled:\nData will be compressed when its being transfered. Useful for slow connections, however, its CPU intensive." --cancel-button "Back" --default-item "$SYNC_COMPRESSION_TEXT" --backtitle "$WHIP_BACKTITLE" 17 75 2 \ "Disabled" "Transfer data in its original state." \ "Enabled" "Compress the data during transfer." 3>&1 1>&2 2>&3) CHOICE=$? if (( $CHOICE == 0 )); then case "$OPTION" in Disabled) SYNC_COMPRESSION=0 ;; Enabled) SYNC_COMPRESSION=1 ;; esac fi #Return to main menu TARGETMENUID=0 } #TARGETMENUID=5 Menu_Set_CronDaily(){ WHIP_TITLE=' Sync Cron Daily ' OPTION=$(whiptail --title "$WHIP_TITLE" --menu "Disabled:\nThe user must manually sync using dietpi-sync.\n\nEnabled:\nA cron job will be created that automatically runs dietpi-sync, once a day.\n\n(NOTICE):\nBefore enabling this feature, please run a test sync (Dry Run) to verify what will happen." --cancel-button "Back" --default-item "$SYNC_CRONDAILY_TEXT" --backtitle "$WHIP_BACKTITLE" 19 75 2 \ "Disabled" "Manual sync." \ "Enabled" "Automatically sync once a day." 3>&1 1>&2 2>&3) CHOICE=$? if (( $CHOICE == 0 )); then case "$OPTION" in Disabled) SYNC_CRONDAILY=0 ;; Enabled) SYNC_CRONDAILY=1 ;; esac fi #Return to main menu TARGETMENUID=0 } Input_User_Directory(){ #TARGETMENUID #2=Source | 1=Target #Target if (( $TARGETMENUID == 1 )); then OPTION=$(whiptail --inputbox "Please enter the filepath to your Target directory. \neg: /mnt/target" 8 70 "$FP_TARGET" --title "Manual sync directory" --backtitle "$WHIP_BACKTITLE" 3>&1 1>&2 2>&3) CHOICE=$? if (( $CHOICE == 0 )); then FP_TARGET="$OPTION" fi #Source elif (( $TARGETMENUID == 2 )); then OPTION=$(whiptail --inputbox "Please enter the filepath to your Source directory. \neg: /mnt/source" 8 70 "$FP_SOURCE" --title "Manual sync directory" --backtitle "$WHIP_BACKTITLE" 3>&1 1>&2 2>&3) CHOICE=$? if (( $CHOICE == 0 )); then FP_SOURCE="$OPTION" fi fi } Prompt_Setup_Samba_Mount(){ whiptail --title "Not available. Setup Now?" --yesno "$SAMBA_MOUNT_TEXT\n\nWould you like to run DietPi-Config and setup your Samba Client Mount now?" --yes-button "Ok" --no-button "Back" --defaultno --backtitle "$WHIP_BACKTITLE" 11 70 CHOICE=$? if (( $CHOICE == 0 )); then #Run configure /DietPi/dietpi/dietpi-config 16 1 fi } Prompt_Setup_FTP_Mount(){ whiptail --title "Not available. Setup Now?" --yesno "$FTP_MOUNT_TEXT\n\nWould you like to run DietPi-Config and setup your FTP Client Mount now?" --yes-button "Ok" --no-button "Back" --defaultno --backtitle "$WHIP_BACKTITLE" 11 70 CHOICE=$? if (( $CHOICE == 0 )); then #Run configure /DietPi/dietpi/dietpi-config 16 1 fi } #///////////////////////////////////////////////////////////////////////////////////// # Sync System #///////////////////////////////////////////////////////////////////////////////////// #LOGFILE LOGFILE='/var/log/dietpi-sync.log' #Sync Filepaths FP_SOURCE='/mnt/Source' FP_TARGET='/mnt/Target' #DietPi Mounts FP_FTP_MOUNT='/mnt/ftp_client' FP_SAMBA_MOUNT='/mnt/samba' #file applied to successful Sync (stored in $FP_TARGET/$SYNC_STATS_FILENAME) SYNC_STATS_FILENAME='.dietpi-sync_stats' #Exclude/include file FP_EXCLUDE_GLOBAL='/tmp/.dietpi-sync_exclude' FP_INCLUDE_GLOBAL='/tmp/.dietpi-sync_include' FP_EXCLUDE_USER='/DietPi/dietpi/.dietpi-sync_exclude' FP_INCLUDE_USER='/DietPi/dietpi/.dietpi-sync_include' #Extra options SYNC_DELETE_MODE=0 SYNC_COMPRESSION=0 SYNC_CRONDAILY=0 Create_Exclude_File(){ #Generate new rm "$FP_EXCLUDE_GLOBAL" &> /dev/null #Global - Folders echo -e "$FP_TARGET" >> "$FP_EXCLUDE_GLOBAL" echo -e "/boot/dietpi/*" >> "$FP_EXCLUDE_GLOBAL" echo -e "/DietPi/*" >> "$FP_EXCLUDE_GLOBAL" echo -e "/dev/*" >> "$FP_EXCLUDE_GLOBAL" echo -e "/proc/*" >> "$FP_EXCLUDE_GLOBAL" echo -e "/sys/*" >> "$FP_EXCLUDE_GLOBAL" echo -e "/tmp/*" >> "$FP_EXCLUDE_GLOBAL" echo -e "/run/*" >> "$FP_EXCLUDE_GLOBAL" #Global - Files echo -e "$LOGFILE" >> "$FP_EXCLUDE_GLOBAL" echo -e "$FP_DIETPISYNC_SETTINGS" >> "$FP_EXCLUDE_GLOBAL" echo -e "$FP_EXCLUDE_USER" >> "$FP_EXCLUDE_GLOBAL" echo -e "$FP_INCLUDE_USER" >> "$FP_EXCLUDE_GLOBAL" echo -e "$SYNC_STATS_FILENAME" >> "$FP_EXCLUDE_GLOBAL" echo -e "/var/swap" >> "$FP_EXCLUDE_GLOBAL" echo -e "*.tmp" >> "$FP_EXCLUDE_GLOBAL" # - MS Windows specific echo -e "Thumbs.db" >> "$FP_EXCLUDE_GLOBAL" echo -e "desktop.ini" >> "$FP_EXCLUDE_GLOBAL" echo -e "SyncToy*" >> "$FP_EXCLUDE_GLOBAL" # MS SyncToy echo -e "System Volume Information" >> "$FP_EXCLUDE_GLOBAL" # - causes error code 23 (permission denied) #Add users additional list if [ -f "$FP_EXCLUDE_USER" ]; then cat "$FP_EXCLUDE_USER" >> "$FP_EXCLUDE_GLOBAL" fi } Create_Include_File(){ #Generate new rm "$FP_INCLUDE_GLOBAL" &> /dev/null #Global - Folders echo -e "" > "$FP_INCLUDE_GLOBAL" #Global - Files #Add users additional list if [ -f "$FP_INCLUDE_USER" ]; then cat "$FP_INCLUDE_USER" >> "$FP_INCLUDE_GLOBAL" fi } Services_Stop(){ /DietPi/dietpi/dietpi-services stop } Services_Start(){ /DietPi/dietpi/dietpi-services start } Banner_Start(){ local mode='Sync' #Dry Run? if (( $SYNC_DRY_RUN == 1 )); then mode='Dry Run' fi G_DIETPI-NOTIFY 3 DietPi-Sync "$mode" } Run_Sync(){ Banner_Start #Generate Target dir. mkdir -p "$FP_TARGET" #Error: Folder not found if [ ! -d "$FP_TARGET" ]; then if (( $G_USER_INPUTS )); then whiptail --title "Sync failed." --msgbox "Unable to create Target directory $FP_TARGET" --backtitle "$WHIP_BACKTITLE" 10 60 fi #Error: Rsync is already running elif (( $(ps aux | grep -ci -m1 "[r]sync") == 1 )); then echo -e "Sync failed: $(date +"%d-%m-%Y_%H%M"). Rsync is already running." >> "$FP_TARGET/$SYNC_STATS_FILENAME" #Menu if (( $G_USER_INPUTS )); then whiptail --title "Sync Error" --msgbox "A sync job could not be started as rsync is already running." --backtitle "$WHIP_BACKTITLE" 13 60 fi #Start sync else #Generate Exclude/Include lists Create_Exclude_File Create_Include_File #Archive mode local rync_options="-a" #verbose rync_options+="v" #Keep partial files + Progress. (eg: if file is interupted, it will resume where it left off, rather than resending the whole file. rync_options+="P" #Prefer IPv4 rync_options+="4" #Compression? if (( $SYNC_COMPRESSION == 1 )); then rync_options+="z" fi #Dry Run? if (( $SYNC_DRY_RUN == 1 )); then rync_options+="n" fi #Delete mode? if (( $SYNC_DELETE_MODE == 1 )); then rync_options+=" --delete" fi #Write new logfile echo -e "DietPi-Sync Log File.\n$(date +%d-%m-%Y_%H%M)\nSYNC_DRY_RUN=$SYNC_DRY_RUN\n\n" > "$LOGFILE" #Services_Stop #Sync rsync $rync_options --log-file=$LOGFILE --exclude-from="$FP_EXCLUDE_GLOBAL" --include-from="$FP_INCLUDE_GLOBAL" "$FP_SOURCE"/ "$FP_TARGET"/ #Services_Start #Success if (( $? == 0 )); then echo -e "Sync completed: $(date +"%d-%m-%Y_%H%M")" >> "$FP_TARGET/$SYNC_STATS_FILENAME" #Menu if (( $G_USER_INPUTS )); then if (( $SYNC_DRY_RUN == 0 )); then whiptail --title "Sync completed" --msgbox "$FP_SOURCE\n\nHas been synced to:\n$FP_TARGET\n\nLog file: $LOGFILE" --backtitle "$WHIP_BACKTITLE" 13 60 else whiptail --title "Dry Run Sync completed" --msgbox "$FP_SOURCE\n\nHas been synced with a Dry Run (NO modifications) to:\n$FP_TARGET\n\nLog file: $LOGFILE" --backtitle "$WHIP_BACKTITLE" 13 60 fi fi #Failed else echo -e "Sync failed: $(date +"%d-%m-%Y_%H%M")" >> "$FP_TARGET/$SYNC_STATS_FILENAME" #Menu if (( $G_USER_INPUTS )); then if (( $SYNC_DRY_RUN == 0 )); then whiptail --title "Sync failed" --msgbox "Your sync has failed. Please see the Log file for more information:\n\n$LOGFILE" --backtitle "$WHIP_BACKTITLE" 13 60 else whiptail --title "Dry Run Sync failed" --msgbox "Your sync has failed. Please see the Log file for more information:\n\n$LOGFILE" --backtitle "$WHIP_BACKTITLE" 13 60 fi fi fi #return to main menu TARGETMENUID=0 fi } Check_Install_PreReqs(){ G_AG_CHECK_INSTALL_PREREQ rsync } Check_Available_DietPi_Mounts(){ local temp_file_mounts="/tmp/.dietpi-sync_dietpi_mounts" df -h > "$temp_file_mounts" #Samba Client SAMBA_MOUNT_AVAILABLE=0 SAMBA_MOUNT_TEXT="Not mounted ($FP_SAMBA_MOUNT). Select to setup." if (( $(cat "$temp_file_mounts" | grep -ci -m1 "/mnt/samba") == 1 )); then SAMBA_MOUNT_AVAILABLE=1 SAMBA_MOUNT_TEXT="Size: $(df -h | grep /mnt/samba | awk '{print $2}')B | Available: $(df -h | grep /mnt/samba | awk '{print $4}')B" fi #FTP Client FTP_MOUNT_AVAILABLE=0 FTP_MOUNT_TEXT="Not mounted ($FP_FTP_MOUNT). Select to setup." if (( $(cat "$temp_file_mounts" | grep -ci -m1 "/mnt/ftp_client") == 1 )); then FTP_MOUNT_AVAILABLE=1 FTP_MOUNT_TEXT="Mounted and online." fi rm "$temp_file_mounts" } #///////////////////////////////////////////////////////////////////////////////////// # Settings File #///////////////////////////////////////////////////////////////////////////////////// #Settings File FP_DIETPISYNC_SETTINGS="/DietPi/dietpi/.dietpi-sync_settings" Write_Settings_File(){ rm "$FP_DIETPISYNC_SETTINGS" &> /dev/null echo -e "$FP_SOURCE" >> "$FP_DIETPISYNC_SETTINGS" echo -e "$FP_TARGET" >> "$FP_DIETPISYNC_SETTINGS" echo -e "$SYNC_DELETE_MODE" >> "$FP_DIETPISYNC_SETTINGS" echo -e "$SYNC_COMPRESSION" >> "$FP_DIETPISYNC_SETTINGS" echo -e "$SYNC_CRONDAILY" >> "$FP_DIETPISYNC_SETTINGS" } Read_Settings_File(){ if [ -f "$FP_DIETPISYNC_SETTINGS" ]; then local sed_index=1 FP_SOURCE=$(sed -n "$sed_index"p "$FP_DIETPISYNC_SETTINGS");((sed_index++)) FP_TARGET=$(sed -n "$sed_index"p "$FP_DIETPISYNC_SETTINGS");((sed_index++)) SYNC_DELETE_MODE=$(sed -n "$sed_index"p "$FP_DIETPISYNC_SETTINGS");((sed_index++)) SYNC_COMPRESSION=$(sed -n "$sed_index"p "$FP_DIETPISYNC_SETTINGS");((sed_index++)) SYNC_CRONDAILY=$(sed -n "$sed_index"p "$FP_DIETPISYNC_SETTINGS");((sed_index++)) fi } #///////////////////////////////////////////////////////////////////////////////////// # Main Loop #///////////////////////////////////////////////////////////////////////////////////// #pre-reqs, install if required. Check_Install_PreReqs #Read settings file Read_Settings_File #----------------------------------------------------------------------------- #Run Sync if (( $INPUT == 1 )); then Run_Sync #----------------------------------------------------------------------------- #Run menu elif (( $INPUT == 0 )); then while (( $TARGETMENUID > -1 )); do clear if (( $TARGETMENUID == 0 )); then Menu_Main elif (( $TARGETMENUID == 1 )) || (( $TARGETMENUID == 2 )); then Menu_Set_Directories elif (( $TARGETMENUID == 3 )); then Menu_Set_Sync_Delete_Mode elif (( $TARGETMENUID == 4 )); then Menu_Set_Compression elif (( $TARGETMENUID == 5 )); then Menu_Set_CronDaily fi done #Save settings Write_Settings_File fi #----------------------------------------------------------------------------------- #Cleaup left over tmp files rm "$FP_INCLUDE_GLOBAL" &> /dev/null rm "$FP_EXCLUDE_GLOBAL" &> /dev/null #----------------------------------------------------------------------------------- exit 0 #----------------------------------------------------------------------------------- }