mirror of
https://github.com/Nighthawk42/plex-backup.git
synced 2026-08-30 09:22:27 +00:00
211 lines
9.3 KiB
Python
211 lines
9.3 KiB
Python
import argparse
|
|
import os
|
|
import subprocess
|
|
import datetime
|
|
import shutil
|
|
import zipfile
|
|
import py7zr
|
|
import patoolib
|
|
import winreg
|
|
import logging
|
|
from tqdm import tqdm
|
|
import yaml
|
|
|
|
# Setup logging
|
|
def setup_logging():
|
|
log_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'logs')
|
|
if not os.path.exists(log_dir):
|
|
os.makedirs(log_dir)
|
|
log_file = os.path.join(log_dir, f"plexbackup_{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.log")
|
|
logging.basicConfig(
|
|
filename=log_file,
|
|
level=logging.DEBUG,
|
|
format='%(asctime)s - %(levelname)s - %(message)s'
|
|
)
|
|
|
|
# Read configuration file
|
|
def read_config(config_path):
|
|
try:
|
|
with open(config_path, 'r') as file:
|
|
config = yaml.safe_load(file)
|
|
return config
|
|
except yaml.YAMLError as e:
|
|
logging.error(f"Error reading configuration file: {e}")
|
|
raise ValueError(f"Error reading configuration file: {e}")
|
|
except FileNotFoundError as e:
|
|
logging.error(f"Configuration file not found: {e}")
|
|
raise FileNotFoundError(f"Configuration file not found: {e}")
|
|
|
|
# Stop Plex services
|
|
def stop_plex_services():
|
|
services = ["PlexUpdateService", "PlexService"]
|
|
for service in services:
|
|
try:
|
|
logging.debug(f"Attempting to stop service: {service}")
|
|
result = subprocess.run(["sc", "stop", service], check=True, capture_output=True, text=True)
|
|
logging.info(f"Stopped service {service}: {result.stdout}")
|
|
except subprocess.CalledProcessError as e:
|
|
if "1060" in str(e):
|
|
logging.info(f"Service {service} does not exist, skipping. Error: {e.stderr}")
|
|
else:
|
|
logging.warning(f"Error stopping service {service}: {e.stderr}")
|
|
try:
|
|
result = subprocess.run(["taskkill", "/F", "/IM", "Plex Media Server.exe"], check=True, capture_output=True, text=True)
|
|
logging.info(f"Killed Plex Media Server process: {result.stdout}")
|
|
except subprocess.CalledProcessError as e:
|
|
logging.warning(f"Error killing Plex Media Server process: {e.stderr}")
|
|
|
|
# Start Plex services
|
|
def start_plex_services():
|
|
services = ["PlexUpdateService", "PlexService"]
|
|
for service in services:
|
|
try:
|
|
logging.debug(f"Attempting to start service: {service}")
|
|
result = subprocess.run(["sc", "start", service], check=True, capture_output=True, text=True)
|
|
logging.info(f"Started service {service}: {result.stdout}")
|
|
except subprocess.CalledProcessError as e:
|
|
if "1060" in str(e):
|
|
logging.info(f"Service {service} does not exist, skipping. Error: {e.stderr}")
|
|
else:
|
|
logging.warning(f"Error starting service {service}: {e.stderr}")
|
|
|
|
# Get Plex installation path from the registry
|
|
def get_plex_install_path():
|
|
try:
|
|
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"SOFTWARE\Plex, Inc.\Plex Media Server") as key:
|
|
install_path, _ = winreg.QueryValueEx(key, "InstallFolder")
|
|
return install_path
|
|
except FileNotFoundError:
|
|
logging.error("Plex install path not found in registry.")
|
|
raise FileNotFoundError("Plex install path not found in registry.")
|
|
|
|
# Backup Plex registry entries
|
|
def backup_registry(backup_zip):
|
|
registry_backup_file = "plex_registry_backup.reg"
|
|
subprocess.run(["reg", "export", r"HKEY_CURRENT_USER\SOFTWARE\Plex, Inc.\Plex Media Server", registry_backup_file], check=True)
|
|
with zipfile.ZipFile(backup_zip, 'a') as zipf:
|
|
zipf.write(registry_backup_file, arcname=registry_backup_file)
|
|
os.remove(registry_backup_file)
|
|
logging.info(f"Registry backed up to {backup_zip}")
|
|
|
|
# Compress directory to various archive formats
|
|
def compress_directory(src_dir, archive_path, format='zip', compression_level=5, exclude_folders=[]):
|
|
total_files = sum([len(files) for r, d, files in os.walk(src_dir) if os.path.basename(r) not in exclude_folders])
|
|
if format == 'zip':
|
|
with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
with tqdm(total=total_files, desc="Compressing Backup") as pbar:
|
|
for root, dirs, files in os.walk(src_dir):
|
|
dirs[:] = [d for d in dirs if d not in exclude_folders]
|
|
for file in files:
|
|
file_path = os.path.join(root, file)
|
|
arcname = os.path.relpath(file_path, src_dir)
|
|
zipf.write(file_path, arcname, compress_type=zipfile.ZIP_DEFLATED, compresslevel=compression_level)
|
|
pbar.update(1)
|
|
elif format == '7z':
|
|
with py7zr.SevenZipFile(archive_path, 'w', filters=[{"id": py7zr.FILTER_LZMA2, "preset": compression_level}]) as archive:
|
|
with tqdm(total=total_files, desc="Compressing Backup") as pbar:
|
|
for root, dirs, files in os.walk(src_dir):
|
|
dirs[:] = [d for d in dirs if d not in exclude_folders]
|
|
for file in files:
|
|
file_path = os.path.join(root, file)
|
|
arcname = os.path.relpath(file_path, src_dir)
|
|
archive.write(file_path, arcname)
|
|
pbar.update(1)
|
|
else:
|
|
temp_folder = os.path.join(os.path.dirname(archive_path), "temp_backup_folder")
|
|
os.makedirs(temp_folder, exist_ok=True)
|
|
shutil.copytree(src_dir, temp_folder, dirs_exist_ok=True)
|
|
patoolib.create_archive(archive_path, (temp_folder,), verbosity=1)
|
|
shutil.rmtree(temp_folder)
|
|
logging.info(f"Compressed {src_dir} to {archive_path} in {format} format")
|
|
|
|
# Extract archive to destination directory
|
|
def extract_archive(archive_path, dest_dir, format='zip'):
|
|
if format == 'zip':
|
|
with zipfile.ZipFile(archive_path, 'r') as zip_ref:
|
|
total_files = len(zip_ref.namelist())
|
|
with tqdm(total=total_files, desc="Extracting Backup") as pbar:
|
|
for file in zip_ref.namelist():
|
|
zip_ref.extract(file, dest_dir)
|
|
pbar.update(1)
|
|
elif format == '7z':
|
|
with py7zr.SevenZipFile(archive_path, 'r') as archive:
|
|
total_files = len(archive.getnames())
|
|
with tqdm(total=total_files, desc="Extracting Backup") as pbar:
|
|
archive.extractall(dest_dir)
|
|
pbar.update(1)
|
|
else:
|
|
patoolib.extract_archive(archive_path, outdir=dest_dir, verbosity=1)
|
|
logging.info(f"Extracted {archive_path} to {dest_dir} in {format} format")
|
|
|
|
# Restore Plex registry entries from backup
|
|
def restore_registry(backup_zip):
|
|
with zipfile.ZipFile(backup_zip, 'r') as zipf:
|
|
zipf.extract("plex_registry_backup.reg")
|
|
subprocess.run(["reg", "import", "plex_registry_backup.reg"], check=True)
|
|
os.remove("plex_registry_backup.reg")
|
|
logging.info(f"Registry restored from {backup_zip}")
|
|
|
|
# Main function
|
|
def main(mode='backup', config_path=None):
|
|
setup_logging()
|
|
logging.info(f"Starting {mode} process")
|
|
|
|
# Determine config file path
|
|
if config_path is None:
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
config_path = os.path.join(script_dir, 'config.yaml')
|
|
|
|
print(f"Using configuration file: {config_path}")
|
|
logging.info(f"Using configuration file: {config_path}")
|
|
|
|
# Read config file
|
|
config = read_config(config_path)
|
|
|
|
# Get configuration settings
|
|
backup_dir = config.get('backup_dir', 'C:/Backups')
|
|
exclude_folders = config.get('exclude_folders', ['Diagnostics', 'Crash Reports', 'Updates', 'Logs'])
|
|
archive_format = config.get('archive_format', 'zip')
|
|
compression_level = config.get('compression_level', 5)
|
|
|
|
# Determine Plex data and install paths
|
|
plex_data_path = os.path.join(os.environ['LOCALAPPDATA'], 'Plex Media Server')
|
|
plex_install_path = get_plex_install_path()
|
|
|
|
# Create timestamped archive file name
|
|
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
|
archive_path = os.path.join(backup_dir, f'plex_backup_{timestamp}.{archive_format}')
|
|
|
|
# Perform backup or restore
|
|
if mode == 'backup':
|
|
stop_plex_services()
|
|
|
|
compress_directory(plex_data_path, archive_path, format=archive_format, compression_level=compression_level, exclude_folders=exclude_folders)
|
|
backup_registry(archive_path)
|
|
|
|
start_plex_services()
|
|
|
|
print("Backup process completed.")
|
|
logging.info("Backup process completed.")
|
|
elif mode == 'restore':
|
|
latest_backup_archive = sorted([f for f in os.listdir(backup_dir) if f.endswith(f'.{archive_format}')])[-1]
|
|
latest_backup_archive_path = os.path.join(backup_dir, latest_backup_archive)
|
|
|
|
stop_plex_services()
|
|
|
|
extract_archive(latest_backup_archive_path, plex_data_path, format=archive_format)
|
|
restore_registry(latest_backup_archive_path)
|
|
|
|
start_plex_services()
|
|
|
|
print("Restore process completed.")
|
|
logging.info("Restore process completed.")
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description='Plex Backup and Restore Script')
|
|
parser.add_argument('mode', choices=['backup', 'restore'], help='Mode to run the script in: backup or restore')
|
|
parser.add_argument('--config', default=None, help='Path to the config file')
|
|
|
|
args = parser.parse_args()
|
|
main(args.mode, args.config)
|