├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── edit_preferences.sh ├── images └── how_to_download_generic.png ├── my-other-scripts.md ├── plex_rsync_exclude.txt ├── plex_server_sync.config ├── plex_server_sync.sh └── plex_server_sync_logo.png /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: 007revad 2 | buy_me_a_coffee: 007revad 3 | custom: ["https://www.paypal.me/007revad"] 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 007revad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plex Server Sync 2 | Sync main Plex server database & metadata to a backup Plex server 3 | 4 | 5 | 6 | [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/paypalme/007revad) 7 | [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/007revad) 8 | [![committers.top badge](https://user-badge.committers.top/australia/007revad.svg)](https://user-badge.committers.top/australia/007revad) 9 | 10 |

11 | 12 | ### Description 13 | 14 | Plex Server Sync is a bash script to sync one Plex Media Server to another Plex Media Server, **including played status, play progress, posters, metadata, ratings and settings**. The only things not synced are settings specific to each Plex Media Server (like server ID, friendly name, public port etc), and files and folders listed in the plex_rsync_exclude.txt file. 15 | 16 |
17 | 18 | ***NEW*** Version 2 and later also support Plex in docker. 19 | 20 |
21 | 22 | This script was written for people who: 23 | 24 | * Have setup a clean installation of Plex Media Server on a different device and want to migrate their Plex settings, meta data, database, played status and played progress to the new device. 25 | * Have a main Plex server and a backup Plex server and want to keep the backup server in sync with the main server. 26 | * Have a Plex server at home and a Plex server at their holiday house and want to sync to their holiday house Plex server before leaving home, and then sync back to their home Plex server before leaving the holiday house to return home. 27 | 28 | The script needs to run on the source plex server machine. 29 | 30 | Tested on Synolgy DSM 7, DSM 6 and Asustor ADM. It should also work on Linux. 31 | 32 | #### What the script does 33 | 34 | * Gets the Plex version from both Plex servers. 35 | * Stops both the source and destination Plex servers. 36 | * Backs up the destination Plex server's Preferences.xml file. 37 | * Copies all newer data files from the source Plex server to the destination Plex server. 38 | * Files listed in the exclude file will not be copied. 39 | * Optionally deletes any extra files in the destination Plex server's data folder. 40 | * Files listed in the exclude file will not be deleted. 41 | * Restores the destination Plex server's machine specific settings in Preferences.xml. 42 | * Starts both Plex servers. 43 | 44 | Everything is saved to a log file, and any errors are also saved to an error log file. 45 | 46 | #### What the script does NOT do 47 | 48 | It does **not** do a 2-way sync. It only syncs one Plex server to another Plex server. 49 | 50 | ### Download the script 51 | 52 | 1. Download the latest version _Source code (zip)_ from https://github.com/007revad/Plex_Server_Sync/releases 53 | 2. Save the download zip file to a folder on the Synology. 54 | 3. Unzip the zip file. 55 | 56 | ### Requirements 57 | 58 | 1. **The script needs to run on the source Plex Media Server machine.** 59 | 60 | 2. **The following files must be in the same folder as plex_server_sync.sh** 61 | 62 | ```YAML 63 | plex_server_sync.config 64 | edit_preferences.sh 65 | plex_rsync_exclude.txt 66 | ``` 67 | 68 | 3. **Both Plex servers must be running the same Plex Media Server version** 69 | 70 | 4. **Both Plex servers must have the same library path** 71 | 72 | If the source Plex server accesses it's media libraries at "/volume1/videos" and "/volume1/music" then the destination server also needs to access it's media libraries at "/volume1/videos" and "/volume1/music" 73 | 74 | 5. **SSH Keys and sudoers** 75 | 76 | If you want to schedule the script to run unattended, as a scheduled cron job, the users need to have sudoers and SSH keys setup so that the SSH, SCP and rsync commands can access the remote server without you entering the user's password. 77 | 78 | See https://blog.golimb.com/2020/10/03/synology-ssh-key-authentication/ for steps on setting up SSH key authentication. 79 | 80 | **Asustor NAS requirements** 81 | 82 | Because the Asustor only has Busybox ash and this script requires bash you'll need to instal bash. 83 | 84 | To install bash on your Asustor: 85 | 86 | 1. First install Entware from App Central. 87 | 88 | 2. Then run the following commands via SSH. You can run the commands in "Shell In A Box" from App Central, or use PuTTY. 89 | 90 | ```YAML 91 | opkg update && opkg upgrade 92 | opkg install bash 93 | ``` 94 | 95 | ### Settings 96 | 97 | You need to set the source and destination settings in the **plex_server_sync.config** file. There are also a few optional settings in the plex_server_sync.config file. 98 | 99 | **Examples:** 100 | 101 | **Source and destination both Plex package:** 102 | ```YAML 103 | src_IP=192.168.0.70 104 | src_OS=DSM7 105 | src_Docker=no 106 | src_Docker_plex_name= 107 | src_Directory="/volume1/PlexMediaServer/AppData/Plex Media Server" 108 | src_User=Bob 109 | 110 | dst_IP=192.168.0.60 111 | dst_OS=DSM6 112 | dst_Docker=no 113 | dst_Docker_plex_name= 114 | dst_Directory="/volume1/Plex/Library/Application Support/Plex Media Server" 115 | dst_User=Bob 116 | dst_SshPort=22 117 | 118 | Delete=yes 119 | DryRun=no 120 | LogPath=~/plex_server_sync_logs 121 | ``` 122 | 123 | **Source and destination both Plex in docker:** 124 | ```YAML 125 | src_IP=192.168.0.70 126 | src_OS=DSM7 127 | dst_Docker=yes 128 | dst_Docker_plex_name="plexinc-pms-docker-1" 129 | src_Directory="/volume1/docker/plex/Library/Application Support" 130 | src_User=Bob 131 | 132 | dst_IP=192.168.0.60 133 | dst_OS=DSM6 134 | dst_Docker=yes 135 | dst_Docker_plex_name="plexinc-pms-docker-1" 136 | dst_Directory="/volume1/docker/plex/Library/Application Support" 137 | dst_User=Bob 138 | dst_SshPort=22 139 | 140 | Delete=yes 141 | DryRun=no 142 | LogPath=~/plex_server_sync_logs 143 | ``` 144 | 145 | ### Default contents of plex_rsync_exclude.txt 146 | 147 | Any files or folders listed in plex_rsync_exclude.txt will **not** be synced. The first 4 files listed must never be synced from one server to another. The folders listed are optional. 148 | 149 | **Contents of plex_rsync_exclude.txt** 150 | 151 | ```YAMLedit_preferences.sh 152 | Preferences.bak 153 | .LocalAdminToken 154 | plexmediaserver.pid 155 | Cache 156 | Codecs 157 | Crash Reports 158 | Diagnostics 159 | Drivers 160 | Logs 161 | Updates 162 | ``` 163 | -------------------------------------------------------------------------------- /edit_preferences.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #------------------------------------------------------------------------- 3 | # Companion script to Plex_Server_Sync.sh 4 | # 5 | # https://github.com/007revad/Plex_Server_Sync 6 | # Script verified at https://www.shellcheck.net/ 7 | #-------------------------------------------------------------------------- 8 | 9 | cd "$(dirname "$0")" || { echo "cd $(dirname "$0") failed!"; exit 1; } 10 | #echo $PWD # debug 11 | 12 | if [[ ! -f Preferences.bak ]]; then 13 | echo "Preferences.bak not found! Aborting." 14 | exit 1 15 | elif [[ ! -f Preferences.xml ]]; then 16 | echo "Preferences.xml not found! Aborting." 17 | exit 1 18 | fi 19 | 20 | 21 | # Assign Pref_keys string array 22 | Pref_keys=("AnonymousMachineIdentifier" "CertificateUUID" "FriendlyName" "LastAutomaticMappedPort") 23 | # Append more elements to the the array 24 | Pref_keys+=("MachineIdentifier" "ManualPortMappingPort" "PlexOnlineToken" "ProcessedMachineIdentifier") 25 | 26 | # Padding var for formatting 27 | padding=" " 28 | 29 | # Get length of Pref_keys 30 | Len=${#Pref_keys[@]} 31 | 32 | # Get backup Preferences.bak file's ID values 33 | echo -e "\nPreferences.bak" 34 | declare -A Pref_bak 35 | Num="0" 36 | while [[ $Num -lt "$Len" ]]; do 37 | Pref_bak[$Num]=$(grep -oP "(?<=\b${Pref_keys[$Num]}=\").*?(?=(\" |\"/>))" "Preferences.bak") 38 | #echo "${Pref_keys[$Num]} = ${Pref_bak[$Num]}" 39 | echo "${Pref_keys[$Num]}${padding:${#Pref_keys[$Num]}} = ${Pref_bak[$Num]}" 40 | Num=$((Num +1)) 41 | done 42 | 43 | # Get synced Preferences.xml file's ID values (so we can replace them) 44 | echo -e "\nPreferences.xml" 45 | declare -A Pref_new 46 | Num="0" 47 | while [[ $Num -lt "$Len" ]]; do 48 | Pref_new[$Num]=$(grep -oP "(?<=\b${Pref_keys[$Num]}=\").*?(?=(\" |\"/>))" "Preferences.xml") 49 | #echo "${Pref_keys[$Num]} = ${Pref_new[$Num]}" 50 | echo "${Pref_keys[$Num]}${padding:${#Pref_keys[$Num]}} = ${Pref_new[$Num]}" 51 | Num=$((Num +1)) 52 | done 53 | echo 54 | 55 | # Change synced Preferences.xml ID values to backed up ID values 56 | changed=0 57 | Num="0" 58 | while [[ $Num -lt "$Len" ]]; do 59 | if [[ ${Pref_new[$Num]} ]] && [[ ${Pref_bak[$Num]} ]]; then 60 | if [[ ${Pref_new[$Num]} != "${Pref_bak[$Num]}" ]]; then 61 | echo "Updating ${Pref_keys[$Num]}" 62 | sed -i "s/ ${Pref_keys[$Num]}=\"${Pref_new[$Num]}/ ${Pref_keys[$Num]}=\"${Pref_bak[$Num]}/g" "Preferences.xml" 63 | changed=$((changed+1)) 64 | fi 65 | fi 66 | Num=$((Num +1)) 67 | done 68 | 69 | 70 | # VaapiDriver in Preferences.bak 71 | VaapiDriver=$(grep -oP '(?<=\bVaapiDriver=").*?(?=(" |"/>))' "Preferences.bak") 72 | echo -e "Back_VaapiDriver = $VaapiDriver\n" 73 | 74 | # VaapiDriver in Preferences.xml 75 | Main_VaapiDriver=$(grep -oP '(?<=\bVaapiDriver=").*?(?=(" |"/>))' "Preferences.xml") 76 | echo -e "Main_VaapiDriver = $Main_VaapiDriver\n" 77 | 78 | # VaapiDriver 79 | if [[ $Main_VaapiDriver ]] && [[ $VaapiDriver ]]; then 80 | if [[ $Main_VaapiDriver != "$VaapiDriver" ]]; then 81 | #echo -e "Updating VaapiDriver\n" 82 | echo "Updating VaapiDriver" 83 | sed -i "s/ VaapiDriver=\"${Main_VaapiDriver}/ VaapiDriver=\"${VaapiDriver}/g" "Preferences.xml" 84 | changed=$((changed+1)) 85 | else 86 | #echo -e "Same VaapiDriver already\n" 87 | echo "Same VaapiDriver already" 88 | fi 89 | elif [[ $VaapiDriver ]]; then 90 | # Insert VaapiDriver="i965" or VaapiDriver="iHD" at the end, before /> 91 | #echo -e "Adding VaapiDriver\n" 92 | echo "Adding VaapiDriver" 93 | sed -i "s/\/>/ VaapiDriver=\"${VaapiDriver}\"\/>/g" "Preferences.xml" 94 | changed=$((changed+1)) 95 | elif [[ $Main_VaapiDriver ]]; then 96 | # Delete VaapiDriver="i965" or VaapiDriver="iHD" 97 | #echo -e "Deleting VaapiDriver\n" 98 | echo "Deleting VaapiDriver" 99 | sed -i "s/ VaapiDriver=\"${Main_VaapiDriver}\"//g" "Preferences.xml" 100 | changed=$((changed+1)) 101 | fi 102 | 103 | 104 | if [[ $changed -eq "1" ]]; then 105 | echo -e "\n$changed change made in Preferences.xml" 106 | elif [[ $changed -gt "0" ]]; then 107 | echo -e "\n$changed changes made in Preferences.xml" 108 | else 109 | echo -e "\nNo changes needed in Preferences.xml" 110 | fi 111 | 112 | exit 113 | 114 | -------------------------------------------------------------------------------- /images/how_to_download_generic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/007revad/Plex_Server_Sync/fcd8369a324a82be906e71832304fd627dbb126b/images/how_to_download_generic.png -------------------------------------------------------------------------------- /my-other-scripts.md: -------------------------------------------------------------------------------- 1 | ## All my scripts 2 | 3 | #### Contents 4 | - [Plex](#plex) 5 | - [Synology docker](#synology-docker) 6 | - [Synology recovery](#synology-recovery) 7 | - [Other Synology scripts](#other-synology-scripts) 8 | - [Synology hardware restrictions](#synology-hardware-restrictions) 9 | - [How To Guides](#how-to-guides) 10 | - [Synology dev](#synology-dev) 11 | ## 12 | 13 | ### Plex 14 | 15 | - **Synology_Plex_Backup** 16 | - A script to backup Plex to a tgz file foror DSM 7 and DSM 6. 17 | - Works for Plex Synology package and Plex in docker. 18 | 19 | - **Asustor_Plex_Backup** 20 | - Backup your Asustor's Plex Media Server settings and database. 21 | 22 | - **Linux_Plex_Backup** 23 | - Backup your Linux Plex Media Server's settings and database. 24 | 25 | - **Plex_Server_Sync** 26 | - Sync your main Plex server database & metadata to a backup Plex server. 27 | - Works for Synology, Asustor, Linux and supports Plex package or Plex in docker. 28 | 29 |               [Back to Contents](#contents) 30 | 31 | ### Synology docker 32 | 33 | - **Synology_Docker_export** 34 | - Export all Synology Container Manager or Docker containers' settings as json files to your docker shared folder. 35 | 36 | - **Synology_ContainerManager_IPv6** 37 | - Enable IPv6 for Container Manager's bridge network. 38 | 39 | - **ContainerManager_for_all_armv8** 40 | - Script to install Container Manager on a RS819, DS119j, DS418, DS418j, DS218, DS218play or DS118. 41 | 42 | - **Docker_Autocompose** 43 | - Create .yml files from your docker existing containers. 44 | 45 | - **Synology_docker_cleanup** 46 | - Remove orphan docker btrfs subvolumes and images in Synology DSM 7 and DSM 6. 47 | 48 |               [Back to Contents](#contents) 49 | 50 | ### Synology recovery 51 | 52 | - **Synology_DSM_reinstall** 53 | - Easily re-install the same DSM version without losing any data or settings. 54 | 55 | - **Synology_Recover_Data** 56 | - A script to make it easy to recover your data from your Synology's drives using a computer. 57 | 58 | - **Synology clear drive error** 59 | - Clear drive critical errors so DSM will let you use the drive. 60 | 61 | - **Synology_DSM_Telnet_Password** 62 | - Synology DSM Recovery Telnet Password of the Day generator. 63 | 64 | - **Syno_DSM_Extractor_GUI** 65 | - Windows GUI for extracting Synology DSM 7 pat files and spk package files. 66 | 67 |               [Back to Contents](#contents) 68 | 69 | ### Other Synology scripts 70 | 71 | - **Synology_app_mover** 72 | - Easily move Synology packages from one volume to another volume. 73 | 74 | - **Video_Station_for_DSM_722** 75 | - Script to install Video Station in DSM 7.2.2 76 | 77 | - **SS_Motion_Detection** 78 | - Installs previous Surveillance Station and Advanced Media Extensions versions so motion detection and HEVC are supported. 79 | 80 | - **Synology_Config_Backup** 81 | - Backup and export your Synology DSM configuration. 82 | 83 | - **Synology_CPU_temperature** 84 | - Get and log Synology NAS CPU temperature via SSH. 85 | 86 | - **Synology_SMART_info** 87 | - Show Synology smart test progress or smart health and attributes. 88 | 89 | - **Synology_Cleanup_Coredumps** 90 | - Cleanup memory core dumps from crashed processes. 91 | 92 | - **Synology_toggle_reset_button** 93 | - Script to disable or enable the reset button and show current setting. 94 | 95 | - **Synology_Download_Station_Chrome_Extension** 96 | - Download Station Chrome Extension. 97 | 98 | - **Seagate_lowCurrentSpinup** 99 | - This script avoids the need to buy and install a higher wattage power supply when using multiple large Seagate SATA HDDs. 100 | 101 |               [Back to Contents](#contents) 102 | 103 | ### Synology hardware restrictions 104 | 105 | - **Synology_HDD_db** 106 | - Add your SATA or SAS HDDs and SSDs plus SATA and NVMe M.2 drives to your Synology's compatible drive databases, including your Synology M.2 PCIe card and Expansion Unit databases. 107 | 108 | - **Synology_enable_M2_volume** 109 | - Enable creating volumes with non-Synology M.2 drives. 110 | - Enable Health Info for non-Synology NVMe drives (not in DSM 7.2.1 or later). 111 | 112 | - **Synology_M2_volume** 113 | - Easily create an M.2 volume on Synology NAS. 114 | 115 | - **Synology_enable_M2_card** 116 | - Enable Synology M.2 PCIe cards in Synology NAS that don't officially support them. 117 | 118 | - **Synology_enable_eunit** 119 | - Enable an unsupported Synology eSATA Expansion Unit models. 120 | 121 | - **Synology_enable_Deduplication** 122 | - Enable deduplication with non-Synology SSDs and unsupported NAS models. 123 | 124 | - **Synology_SHR_switch** 125 | - Easily switch between SHR and RAID Groups, or enable RAID F1. 126 | 127 | - **Synology_enable_sequential_IO** 128 | - Enables sequential I/O for your SSD caches, like DSM 6 had. 129 | 130 | - **Synology_Information_Wiki** 131 | - Information about Synology hardware. 132 | 133 |               [Back to Contents](#contents) 134 | 135 | ### How To Guides 136 | 137 | - **Synology_SSH_key_setup** 138 | - How to setup SSH key authentication for your Synology. 139 | 140 |               [Back to Contents](#contents) 141 | 142 | ### Synology dev 143 | 144 | - **Download_Synology_Archive** 145 | - Download all or part of the Synology archive. 146 | 147 | - **Syno_DSM_Extractor_GUI** 148 | - Windows GUI for extracting Synology DSM 7 pat files and spk package files. 149 | 150 | - **ScriptNotify** 151 | - DSM 7 package to allow your scripts to send DSM notifications. 152 | 153 | - **DTC_GUI_for_Windows** 154 | - GUI for DTC.exe for Windows. 155 | 156 |               [Back to Contents](#contents) 157 | -------------------------------------------------------------------------------- /plex_rsync_exclude.txt: -------------------------------------------------------------------------------- 1 | edit_preferences.sh 2 | Preferences.bak 3 | .LocalAdminToken 4 | plexmediaserver.pid 5 | Cache 6 | Codecs 7 | Crash Reports 8 | Diagnostics 9 | Drivers 10 | Logs 11 | Updates 12 | -------------------------------------------------------------------------------- /plex_server_sync.config: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------- 2 | # User settings config file for Plex_Server_Sync.sh 3 | # 4 | # https://github.com/007revad/Plex_Server_Sync 5 | #------------------------------------------------------------------------- 6 | 7 | # Local machine's IP or hostname and OS 8 | # OS can be ADM, DSM6, DSM7 or Linux (required to stop/start local Plex) 9 | src_IP=192.168.0.70 10 | src_OS=DSM7 11 | 12 | # Local Plex is docker - set to no or blank if not Plex docker 13 | src_Docker=no 14 | 15 | # Local Plex container name 16 | src_Docker_plex_name="plexinc-pms-docker-1" 17 | 18 | # Location of local Plex data folder 19 | #src_Directory="/volume1/docker/plex/Library/Application Support" 20 | src_Directory="/volume1/PlexMediaServer/AppData/Plex Media Server" 21 | 22 | # Local user with SSH keys and sudoers setup 23 | src_User=Bob 24 | 25 | 26 | # Destination machine's IP or hostname and OS 27 | # OS can be ADM, DSM6, DSM7 or Linux (required to stop/start remote Plex) 28 | dst_IP=192.168.0.60 29 | dst_OS=DSM6 30 | 31 | # Destination Plex is docker - set to no or blank if not Plex docker 32 | dst_Docker=no 33 | 34 | # Destination Plex container name 35 | dst_Docker_plex_name="plexinc-pms-docker-1" 36 | 37 | # Location of destination Plex data folder 38 | #dst_Directory="/volume1/docker/plex/Library/Application Support" 39 | dst_Directory="/volume1/Plex/Library/Application Support/Plex Media Server" 40 | 41 | # Remote user with SSH keys and sudoers setup 42 | dst_User=Bob 43 | 44 | # Remote SSH port, if blank the default port 22 is used 45 | dst_SshPort=22 46 | 47 | 48 | # rsync delete extra files from destination [yes/no] 49 | # If left blank you'll be asked "Delete yes/no?" 50 | # If you don't answer within 10 seconds it defaults to no 51 | Delete=yes 52 | 53 | # Do an rsync dry run to check results are as expected [yes/no] 54 | # If left blank you'll be asked "Dry Run yes/no?" 55 | # If you don't answer within 10 seconds it defaults to no 56 | DryRun=no 57 | 58 | 59 | # Set path to save log file. Directory must exist already 60 | # If blank the logs are saved in script location 61 | LogPath=~/plex_server_sync_logs 62 | -------------------------------------------------------------------------------- /plex_server_sync.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #----------------------------------------------------------------------------- 3 | # Script to sync Plex server database & metadata to backup Plex server. 4 | # 5 | # It also syncs your settings, though you can disable this by adding the 6 | # following to the included plex_rsync_exclude.txt 7 | # Preferences.xml 8 | # 9 | # Requirements: 10 | # The script MUST be run on the device where the main Plex server is. 11 | # The following files must be in the same folder as Plex_Server_Sync.sh 12 | # 1. plex_server_sync.config 13 | # 2. plex_rsync_exclude.txt 14 | # 3. edit_preferences.sh 15 | # 16 | # If you want to schedule this script to run as a user: 17 | # 1. You need SSH keys setup so SCP and rsync can connect without passwords. 18 | # 2. You also need your user to be able to sudo without a password prompt. 19 | # 20 | # https://github.com/007revad/Plex_Server_Sync 21 | # Script verified at https://www.shellcheck.net/ 22 | #------------------------------------------------------------------------------ 23 | 24 | scriptver="v2.0.7" 25 | script=Plex_Server_Sync 26 | repo="007revad/Plex_Server_Sync" 27 | scriptname=plex_server_sync 28 | 29 | # Show script version 30 | echo -e "$script $scriptver\n" 31 | 32 | 33 | # Check if script is running in GNU bash and not BusyBox ash 34 | Shell=$(/proc/self/exe --version 2>/dev/null | grep "GNU bash" | cut -d "," -f1) 35 | if [ "$Shell" != "GNU bash" ]; then 36 | echo -e "You need to install bash to be able to run this script." 37 | echo -e "\nIf running this script on an ASUSTOR:" 38 | echo "1. Install Entware from App Central" 39 | echo "2. Run the following commands in a shell:" 40 | echo "opkg update && opkg upgrade" 41 | echo -e "opkg install bash\n" 42 | exit 1 43 | fi 44 | 45 | 46 | # Read variables from plex_server_sync.config 47 | if [[ -f $(dirname -- "$0";)/plex_server_sync.config ]]; then 48 | # shellcheck disable=SC1090,SC1091 49 | while read -r var; do 50 | if [[ $var =~ ^[a-zA-Z0-9_]+=.* ]]; then export "$var"; fi 51 | done < "$(dirname -- "$0";)"/plex_server_sync.config 52 | else 53 | echo "plex_server_sync.config file missing!" 54 | exit 1 55 | fi 56 | 57 | 58 | #src_Directory="/volume1/plex_test/AppData/Plex Media Server" # test, delete later ########## 59 | #dst_Directory="/volume1/plex_test/Library/Plex Media Server" # test, delete later ########## 60 | 61 | #dst_Directory="/volume1/plex_test/From DSM7" # test, delete later ########## 62 | 63 | 64 | #----------------------------------------------------- 65 | # Set date and time variables 66 | 67 | # Timer variable to log time taken to sync PMS 68 | start="${SECONDS}" 69 | 70 | # Get Start Time and Date 71 | Started=$( date ) 72 | 73 | 74 | #----------------------------------------------------- 75 | # Set log file name 76 | 77 | if [[ ! -d $LogPath ]]; then 78 | LogPath=$( dirname -- "$0"; ) 79 | fi 80 | Log="$LogPath/$( date '+%Y%m%d')_Plex_Server_Sync.log" 81 | if [[ -f $Log ]]; then 82 | # Include hh-mm if log file already exists (already run today) 83 | Log="$LogPath/$( date '+%Y%m%d-%H%M')_Plex_Server_Sync.log" 84 | fi 85 | ErrLog="${Log%.*}_ERRORS.log" 86 | 87 | # Log header 88 | CYAN='\e[0;36m' 89 | WHITE='\e[0;37m' 90 | echo -e "${CYAN}--- Plex Server Sync ---${WHITE}" # shell only 91 | echo -e "--- Plex Server Sync ---\n" 1>> "$Log" # log only 92 | echo -e "Syncing $src_IP to $dst_IP\n" |& tee -a "$Log" 93 | 94 | 95 | #----------------------------------------------------- 96 | # Initial checks 97 | 98 | # Convert hostnames to lower case 99 | src_IP=${src_IP,,} 100 | dst_IP=${dst_IP,,} 101 | 102 | 103 | if [[ -z $dst_SshPort ]]; then dst_SshPort=22; fi 104 | 105 | if [[ ! $dst_SshPort =~ ^[0-9]+$ ]]; then 106 | echo "Aborting! Destination SSH Port is not numeric: $dst_SshPort" |& tee -a "$Log" 107 | exit 1 108 | fi 109 | 110 | Exclude_File="$( dirname -- "$0"; )/plex_rsync_exclude.txt" 111 | if [[ ! -f $Exclude_File ]]; then 112 | echo -e "Aborting! Exclude_File not found: \n$Exclude_File" |& tee -a "$Log" 113 | exit 1 114 | fi 115 | 116 | edit_preferences="$( dirname -- "$0"; )/edit_preferences.sh" 117 | if [[ ! -f $edit_preferences ]]; then 118 | echo -e "Aborting! edit_preferences.sh not found: \n$edit_preferences" |& tee -a "$Log" 119 | exit 1 120 | fi 121 | 122 | # Check script is running on the source device 123 | host=$(hostname) # for comparability 124 | #ip=$(ip route get 1 | sed 's/^.*src \([^ ]*\).*$/\1/;q') # for comparability 125 | ip=$(ip -o route get $dst_IP | sed 's/^.*src \([^ ]*\).*$/\1/;q') # Issue #6 126 | sed 's/^.*src \([^ ]*\).*$/\1/;q') 127 | if [[ $src_IP != "${host,,}" ]] && [[ $src_IP != "$ip" ]]; then 128 | echo "Aborting! Script is not running on source device: $src_IP" |& tee -a "$Log" 129 | exit 1 130 | fi 131 | 132 | echo "Source: $src_Directory" |& tee -a "$Log" 133 | echo "Destination: $dst_Directory" |& tee -a "$Log" 134 | 135 | 136 | if [[ ${Delete,,} != "yes" ]] && [[ ${Delete,,} != "no" ]]; then 137 | echo -e "\nDelete extra files on destination? [y/n]:" |& tee -a "$Log" 138 | read -r -t 10 answer 139 | if [[ ${answer,,} == y ]]; then 140 | Delete=yes 141 | echo yes 1>> "$Log" 142 | else 143 | echo no 1>> "$Log" 144 | fi 145 | answer= 146 | fi 147 | 148 | 149 | if [[ ${DryRun,,} != "yes" ]] && [[ ${DryRun,,} != "no" ]]; then 150 | echo -e "\nDo a dry run test? [y/n]:" |& tee -a "$Log" 151 | read -r -t 10 answer 152 | if [[ ${answer,,} == y ]]; then 153 | DryRun=yes 154 | echo yes 1>> "$Log" 155 | else 156 | echo no 1>> "$Log" 157 | fi 158 | answer= 159 | fi 160 | 161 | 162 | #----------------------------------------------------- 163 | # Check host and destination are not the same 164 | 165 | # This function is also used by PlexVersion function 166 | Host2IP(){ 167 | if [[ $2 == "remote" ]]; then 168 | # Get remote IP from hostname 169 | ip=$(ssh "${dst_User}@${1,,}" -p "$dst_SshPort"\ 170 | "ip route get 1 | sed 's/^.*src \([^ ]*\).*$/\1/;q'") 171 | else 172 | # Get local IP from hostname 173 | ip=$(ip route get 1 | sed 's/^.*src \([^ ]*\).*$/\1/;q') 174 | fi 175 | echo "$ip" 176 | } 177 | 178 | # Check the source isn't also the target 179 | if [[ $src_IP == "$dst_IP" ]]; then 180 | echo -e "\nSource and Target are the same!" |& tee -a "$Log" 181 | echo "Source: $src_IP" |& tee -a "$Log" 182 | echo "Target: $dst_IP" |& tee -a "$Log" 183 | exit 1 184 | elif [[ $(Host2IP "$src_IP") == $(Host2IP "$dst_IP" remote) ]]; then 185 | echo -e "\nSource and Target are the same!" 186 | echo "Source: $src_IP" 187 | echo "Target: $dst_IP" 188 | exit 1 189 | fi 190 | 191 | 192 | #----------------------------------------------------- 193 | # Get Plex version BEFORE we stop both Plex servers 194 | 195 | # we can get the Plex version from Plex binary but location is OS dependant 196 | # so we'll use the independent method (but it requires Plex to be running) 197 | 198 | PlexVersion(){ 199 | if [[ $2 == "remote" ]]; then 200 | ip=$(Host2IP "$1" remote) 201 | else 202 | ip=$(Host2IP "$1") 203 | fi 204 | if [[ $ip ]]; then 205 | # Get Plex version from IP address 206 | Response=$(curl -s "http://${ip}:32400/identity") 207 | ver=$(printf %s "$Response" | grep '" version=' | awk -F= '$1=="version"\ 208 | {print $2}' RS=' ' | cut -d'"' -f2 | cut -d"-" -f1) 209 | echo "$ver" 210 | fi 211 | return 212 | } 213 | 214 | src_Version=$(PlexVersion "$src_IP") 215 | echo -e "\nSource Plex version: $src_Version" |& tee -a "$Log" 216 | 217 | dst_Version=$(PlexVersion "$dst_IP" remote) 218 | echo -e "Destination Plex version: $dst_Version\n" |& tee -a "$Log" 219 | 220 | if [[ ! $src_Version ]] || [[ ! $dst_Version ]]; then 221 | echo "WARN: Unable to get one or both Plex versions." |& tee -a "$Log" 222 | echo "One or both servers may be stopped already." |& tee -a "$Log" 223 | echo "Are both Plex versions the same? [y/n]" |& tee -a "$Log" 224 | read -r answer 225 | if [[ ${answer,,} != y ]]; then 226 | echo no 1>> "$Log" 227 | exit 1 228 | else 229 | echo yes 1>> "$Log" 230 | fi 231 | fi 232 | 233 | # Check both versions are the same 234 | if [[ $src_Version != "$dst_Version" ]]; then 235 | if [[ $answer != "y" ]]; then 236 | echo "Plex versions are different. Aborting." |& tee -a "$Log" 237 | echo -e "Source: $src_Version \nDestination: $dst_Version" |& tee -a "$Log" 238 | exit 1 239 | fi 240 | fi 241 | 242 | 243 | #----------------------------------------------------- 244 | # Plex Stop Start function 245 | 246 | PlexControl(){ 247 | if [[ $1 == "start" ]] || [[ $1 == "stop" ]]; then 248 | if [[ $2 == "local" ]]; then 249 | # stop or start local server 250 | if [[ $src_Docker == "yes" ]]; then 251 | if [[ ${src_OS,,} == "dsm7" ]] || [[ ${src_OS,,} == "dsm6" ]]; then 252 | # https://www.reddit.com/r/synology/comments/15h6dn3/how_to_stop_all_docker_containers_peacefully/ 253 | /usr/syno/bin/synowebapi --exec api=SYNO.Docker.Container method="$1" version=1 \ 254 | name="$src_Docker_plex_name" >/dev/null 255 | else 256 | # docker stop results in "Docker container stopped unexpectedly" alert and email. 257 | docker "$1" "$(docker ps -qf name=^"$src_Docker_plex_name"$)" >/dev/null 258 | fi 259 | else 260 | case ${src_OS,,} in 261 | dsm7) 262 | sudo /usr/syno/bin/synopkg "$1" PlexMediaServer >/dev/null 263 | ;; 264 | dsm6) 265 | sudo /usr/syno/bin/synopkg "$1" "Plex Media Server" 266 | ;; 267 | adm) 268 | sudo /usr/local/AppCentral/plexmediaserver/CONTROL/start-stop.sh "$1" 269 | ;; 270 | linux) 271 | # UNTESTED 272 | #sudo systemctl "$1" plexmediaserver 273 | sudo service plexmediaserver "$1" 274 | ;; 275 | *) 276 | echo "Unknown local OS type. Cannot $1 Plex." |& tee -a "$Log" 277 | exit 1 278 | ;; 279 | esac 280 | fi 281 | elif [[ $2 == "remote" ]]; then 282 | # stop or start remote server 283 | if [[ $src_Docker == "yes" ]]; then 284 | if [[ ${src_OS,,} == "dsm7" ]] || [[ ${src_OS,,} == "dsm6" ]]; then 285 | # https://www.reddit.com/r/synology/comments/15h6dn3/how_to_stop_all_docker_containers_peacefully/ 286 | ssh "${dst_User}@${dst_IP}" -p "$dst_SshPort" \ 287 | "sudo /usr/syno/bin/synowebapi --exec api=SYNO.Docker.Container method=$1 version=1" \ 288 | "name=$dst_Docker_plex_name" >/dev/null 289 | else 290 | # docker stop results in "Docker container stopped unexpectedly" alert and email. 291 | ssh "${dst_User}@${dst_IP}" -p "$dst_SshPort" \ 292 | "sudo docker $1 $(docker ps -qf name=^"$dst_Docker_plex_name"$)" >/dev/null 293 | fi 294 | else 295 | case ${dst_OS,,} in 296 | dsm7) 297 | ssh "${dst_User}@${dst_IP}" -p "$dst_SshPort" \ 298 | "sudo /usr/syno/bin/synopkg $1 PlexMediaServer" >/dev/null 299 | ;; 300 | dsm6) 301 | ssh "${dst_User}@${dst_IP}" -p "$dst_SshPort" \ 302 | "sudo /usr/syno/bin/synopkg $1 Plex\ Media\ Server" 303 | ;; 304 | adm) 305 | ssh "${dst_User}@${dst_IP}" -p "$dst_SshPort" \ 306 | "sudo /usr/local/AppCentral/plexmediaserver/CONTROL/start-stop.sh $1" 307 | ;; 308 | linux) 309 | # UNTESTED 310 | #ssh "${dst_User}@${dst_IP}" -p "$dst_SshPort" "sudo systemctl $1 plexmediaserver" 311 | ssh "${dst_User}@${dst_IP}" -p "$dst_SshPort" "sudo service plexmediaserver $1" 312 | ;; 313 | *) 314 | echo "Unknown remote OS type. Cannot $1 Plex." |& tee -a "$Log" 315 | exit 1 316 | ;; 317 | esac 318 | fi 319 | else 320 | echo "Invalid parameter #2: $2" |& tee -a "$Log" 321 | exit 1 322 | fi 323 | if [[ $1 == "stop" ]]; then 324 | sleep 5 # Give sockets a moment to close 325 | fi 326 | else 327 | echo "Invalid parameter #1: $1" |& tee -a "$Log" 328 | exit 1 329 | fi 330 | return 331 | } 332 | 333 | 334 | #----------------------------------------------------- 335 | # Stop both Plex servers 336 | 337 | echo "Stopping Plex on $src_IP" |& tee -a "$Log" 338 | PlexControl stop local |& tee -a "$Log" 339 | echo -e "\nStopping Plex on $dst_IP" |& tee -a "$Log" 340 | PlexControl stop remote |& tee -a "$Log" 341 | echo >> "$Log" 342 | 343 | 344 | #----------------------------------------------------- 345 | # Check both servers have stopped 346 | 347 | # not the best way to get Plex status but other ways are OS dependant 348 | 349 | abort= 350 | if [[ $(PlexVersion "$src_IP") ]]; then 351 | echo "Source Plex $src_IP is still running!" |& tee -a "$Log" 352 | abort=1 353 | fi 354 | if [[ $(PlexVersion "$dst_IP" remote) ]]; then 355 | echo "Destination Plex $dst_IP is still running!" |& tee -a "$Log" 356 | abort=1 357 | fi 358 | if [[ $abort ]]; then 359 | echo "Aborting!" |& tee -a "$Log" 360 | exit 1 361 | fi 362 | 363 | 364 | #----------------------------------------------------- 365 | # Backup destination Preferences.xml 366 | 367 | # Backup Preferences.xml to Preferences.bak 368 | ssh "${dst_User}@${dst_IP}" -p "$dst_SshPort" \ 369 | "cp -u '${dst_Directory}/Preferences.xml' '${dst_Directory}/Preferences.bak'" |& tee -a "$Log" 370 | 371 | 372 | #----------------------------------------------------- 373 | # Sync source to destination with rsync 374 | 375 | cd / || { echo "cd / failed!" |& tee -a "$Log"; exit 1; } 376 | echo "" 377 | 378 | # ------ rsync flags used ------ 379 | # --rsh destination shell to use 380 | # -r recursive 381 | # -l copy symlinks as symlinks 382 | # -h human readable 383 | # -p preserver permissions <-- FAILED to set permissions. Operation not permitted. Need to test more. 384 | # -t preserve modification times 385 | # -O don't keep directory's mtime (with -t) 386 | # --progress show progress during transfer 387 | # --stats give some file-transfer stats 388 | # 389 | # ------ optional rsync flags ------ 390 | # --delete delete extraneous files from destination dirs 391 | # -n, --dry-run perform a trial run with no changes made 392 | 393 | 394 | # Unset any existing arguments 395 | while [[ $1 ]]; do shift; done 396 | 397 | if [[ ${DryRun,,} == yes ]]; then 398 | # Set --dry-run flag for rsync 399 | set -- "$@" "--dry-run" 400 | echo Running an rsync dry-run test |& tee -a "$Log" 401 | fi 402 | if [[ ${Delete,,} == yes ]]; then 403 | # Set --delete flag for rsync 404 | set -- "$@" "--delete" 405 | echo Running rsync with delete flag |& tee -a "$Log" 406 | fi 407 | 408 | # --delete doesn't delete if you have * wildcard after source directory path 409 | rsync --rsh="ssh -p$dst_SshPort" -rlhtO "$@" --progress --stats \ 410 | --exclude-from="$Exclude_File" "$src_Directory/" "$dst_IP":"$dst_Directory" |& tee -a "$Log" 411 | 412 | 413 | #----------------------------------------------------- 414 | # Restore unique IDs to destination's Preferences.xml 415 | 416 | echo -e "\nCopying edit_preferences.sh to destination" |& tee -a "$Log" 417 | 418 | if [[ $src_OS == DSM7 ]]; then 419 | # -O flag is required if DSM7 is the source or SCP defaults to SFTP 420 | sudo -u "$src_User" scp -O -P "$dst_SshPort" "$(dirname "$0")/edit_preferences.sh" \ 421 | "$dst_User"@"$dst_IP":"'${dst_Directory}/'" |& tee -a "$Log" 422 | else 423 | # Prepend spaces in destination path with \\ 424 | spath=$(dirname "$0") 425 | sudo -u "$src_User" scp -P "$dst_SshPort" "${spath}/edit_preferences.sh" \ 426 | "$dst_User"@"$dst_IP":"${dst_Directory// /\\ }/" |& tee -a "$Log" 427 | fi 428 | 429 | echo -e "\nRunning $dst_Directory/edit_preferences.sh" |& tee -a "$Log" 430 | ssh "${dst_User}@${dst_IP}" -p "$dst_SshPort" "'${dst_Directory}/edit_preferences.sh'" |& tee -a "$Log" 431 | 432 | 433 | #----------------------------------------------------- 434 | # Start both Plex servers 435 | 436 | echo -e "\nStarting Plex on $src_IP" |& tee -a "$Log" 437 | PlexControl start local |& tee -a "$Log" 438 | echo -e "\nStarting Plex on $dst_IP" |& tee -a "$Log" 439 | PlexControl start remote |& tee -a "$Log" 440 | 441 | 442 | #----------------------------------------------------- 443 | # Check if there errors from rsync, scp or cp 444 | 445 | if [[ -f $Log ]]; then 446 | tmp=$(awk '/^(rsync|cp|scp|\*\*\*|IO error).*/' "$Log") 447 | if [[ -n $tmp ]]; then 448 | echo "$tmp" >> "$ErrLog" 449 | fi 450 | fi 451 | if [[ -f $ErrLog ]]; then 452 | echo -e "\n${CYAN}Some errors occurred!${WHITE} See:" # shell only 453 | echo -e "\nSome errors occurred! See:" >> "$Log" # log only 454 | echo "$ErrLog" |& tee -a "$Log" 455 | fi 456 | 457 | 458 | #-------------------------------------------------------------------------- 459 | # Append the time taken to stdout 460 | 461 | # End Time and Date 462 | Finished=$( date ) 463 | 464 | # bash timer variable to log time taken 465 | end="${SECONDS}" 466 | 467 | # Elapsed time in seconds 468 | Runtime=$(( end - start )) 469 | 470 | # Append start and end date/time and runtime 471 | echo -e "\nPlex Sync Started: " "${Started}" |& tee -a "$Log" 472 | echo "Plex Sync Finished:" "${Finished}" |& tee -a "$Log" 473 | # Append days, hours, minutes and seconds from $Runtime 474 | printf "Plex Sync Duration: " |& tee -a "$Log" 475 | printf '%dd:%02dh:%02dm:%02ds\n' \ 476 | $((Runtime/86400)) $((Runtime%86400/3600)) $((Runtime%3600/60)) $((Runtime%60)) |& tee -a "$Log" 477 | echo "" |& tee -a "$Log" 478 | 479 | 480 | exit 481 | 482 | -------------------------------------------------------------------------------- /plex_server_sync_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/007revad/Plex_Server_Sync/fcd8369a324a82be906e71832304fd627dbb126b/plex_server_sync_logo.png --------------------------------------------------------------------------------