├── .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 | [](https://www.paypal.com/paypalme/007revad)
7 | [](https://github.com/sponsors/007revad)
8 | [](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
--------------------------------------------------------------------------------