File: //home/gisha985666/htdocs/BackupSystem/wp_restore.py
#!/usr/bin/env python3
import os
import sys
import zipfile
import shutil
import subprocess
import logging
from pathlib import Path
import configparser
from datetime import datetime
try:
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
from google.oauth2.service_account import Credentials
except ImportError:
print("Error: Google API client libraries not installed.")
print("Run: pip install google-api-python-client google-auth")
sys.exit(1)
class WordPressRestore:
def __init__(self, config_file='config.ini'):
self.config = configparser.ConfigParser()
self.config.read(config_file)
# Setup logging
log_level = self.config.get('general', 'log_level', fallback='INFO')
logging.basicConfig(
level=getattr(logging, log_level),
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('wp_restore.log'),
logging.StreamHandler()
]
)
self.logger = logging.getLogger(__name__)
# Configuration
self.wp_path = self.config.get('wordpress', 'wp_path')
self.backup_dir = self.config.get('general', 'backup_dir', fallback='/tmp/wp_backups')
# Database config
self.db_host = self.config.get('database', 'host')
self.db_name = self.config.get('database', 'name')
self.db_user = self.config.get('database', 'user')
self.db_password = self.config.get('database', 'password')
# Google Drive config
self.credentials_file = self.config.get('google_drive', 'credentials_file')
self.drive_folder_id = self.config.get('google_drive', 'folder_id', fallback=None)
# Ensure backup directory exists
Path(self.backup_dir).mkdir(parents=True, exist_ok=True)
# Initialize Google Drive service
self.drive_service = self._init_drive_service()
def _init_drive_service(self):
"""Initialize Google Drive API service"""
try:
self.logger.info("Initializing Google Drive connection...")
credentials = Credentials.from_service_account_file(
self.credentials_file,
scopes=['https://www.googleapis.com/auth/drive.file']
)
service = build('drive', 'v3', credentials=credentials)
self.logger.info("✓ Connected to Google Drive successfully")
return service
except Exception as e:
self.logger.error(f"✗ Failed to initialize Google Drive service: {e}")
return None
def list_backups(self):
"""List all backup files from Google Drive"""
if not self.drive_service:
self.logger.error("✗ Google Drive service not initialized")
return None
try:
self.logger.info("Fetching backup list from Google Drive...")
query = f"'{self.drive_folder_id}' in parents and trashed=false"
results = self.drive_service.files().list(
q=query,
fields="files(id, name, createdTime, size)",
orderBy="createdTime desc",
supportsAllDrives=True,
includeItemsFromAllDrives=True
).execute()
files = results.get('files', [])
if not files:
self.logger.info("No backup files found in Google Drive")
return []
# Separate files and database backups
file_backups = []
db_backups = []
for f in files:
if f['name'].startswith('wp_files_backup_'):
file_backups.append(f)
elif f['name'].startswith('wp_db_backup_'):
db_backups.append(f)
self.logger.info(f"✓ Found {len(file_backups)} file backups and {len(db_backups)} database backups")
return {'files': file_backups, 'databases': db_backups}
except Exception as e:
self.logger.error(f"✗ Failed to list backups: {e}")
return None
def download_backup(self, file_id, file_name):
"""Download backup file from Google Drive"""
if not self.drive_service:
self.logger.error("✗ Google Drive service not initialized")
return None
try:
self.logger.info(f"Downloading {file_name}...")
request = self.drive_service.files().get_media(
fileId=file_id,
supportsAllDrives=True
)
file_path = os.path.join(self.backup_dir, file_name)
with open(file_path, 'wb') as f:
downloader = MediaIoBaseDownload(f, request)
done = False
while not done:
status, done = downloader.next_chunk()
if status:
progress = int(status.progress() * 100)
self.logger.info(f" Download progress: {progress}%")
self.logger.info(f"✓ Downloaded successfully to {file_path}")
return file_path
except Exception as e:
self.logger.error(f"✗ Failed to download file: {e}")
return None
def delete_wordpress_files(self):
"""Delete all files in WordPress directory"""
self.logger.warning(f"⚠ DELETING all files in {self.wp_path}")
try:
if not os.path.exists(self.wp_path):
self.logger.info(f"Directory {self.wp_path} does not exist, creating it...")
os.makedirs(self.wp_path)
return True
# Count files before deletion
file_count = sum(len(files) for _, _, files in os.walk(self.wp_path))
self.logger.info(f"Found {file_count} files to delete")
for item in os.listdir(self.wp_path):
item_path = os.path.join(self.wp_path, item)
if os.path.isfile(item_path):
os.remove(item_path)
elif os.path.isdir(item_path):
shutil.rmtree(item_path)
self.logger.info(f"✓ Successfully deleted all files from {self.wp_path}")
return True
except Exception as e:
self.logger.error(f"✗ Failed to delete WordPress files: {e}")
return False
def restore_wordpress_files(self, backup_file):
"""Extract WordPress files from backup"""
self.logger.info(f"Restoring WordPress files from {backup_file}...")
try:
self.logger.info("Extracting backup archive...")
with zipfile.ZipFile(backup_file, 'r') as zip_ref:
file_list = zip_ref.namelist()
total_files = len(file_list)
self.logger.info(f" Archive contains {total_files} files")
for i, file in enumerate(file_list, 1):
zip_ref.extract(file, self.wp_path)
if i % 100 == 0 or i == total_files:
progress = int((i / total_files) * 100)
self.logger.info(f" Extraction progress: {progress}% ({i}/{total_files} files)")
self.logger.info(f"✓ Successfully restored {total_files} files to {self.wp_path}")
# Fix file ownership
self.fix_file_ownership()
return True
except Exception as e:
self.logger.error(f"✗ Failed to restore WordPress files: {e}")
return False
def fix_file_ownership(self):
"""Fix file ownership after restore"""
self.logger.info("Fixing file ownership...")
try:
# Get the owner of the parent directory
parent_dir = os.path.dirname(self.wp_path)
stat_info = os.stat(parent_dir)
uid = stat_info.st_uid
gid = stat_info.st_gid
# Get username from uid
import pwd
import grp
username = pwd.getpwuid(uid).pw_name
groupname = grp.getgrgid(gid).gr_name
self.logger.info(f" Setting ownership to {username}:{groupname}")
# Change ownership recursively
cmd = ['chown', '-R', f'{username}:{groupname}', self.wp_path]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode == 0:
self.logger.info(f"✓ File ownership fixed: {username}:{groupname}")
return True
else:
self.logger.warning(f"⚠ Failed to change ownership: {result.stderr}")
return False
except Exception as e:
self.logger.warning(f"⚠ Could not fix file ownership: {e}")
self.logger.info(f" You may need to manually run: chown -R user:group {self.wp_path}")
return False
def database_exists(self):
"""Check if database exists"""
try:
cmd = [
'mysql',
f'--host={self.db_host}',
f'--user={self.db_user}',
f'--password={self.db_password}',
'-e',
f"SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '{self.db_name}'"
]
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
return self.db_name in result.stdout
except subprocess.CalledProcessError:
return False
def create_database(self):
"""Create database if it doesn't exist"""
self.logger.info(f"Creating database '{self.db_name}'...")
try:
cmd = [
'mysql',
f'--host={self.db_host}',
f'--user={self.db_user}',
f'--password={self.db_password}',
'-e',
f"CREATE DATABASE IF NOT EXISTS `{self.db_name}` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"
]
subprocess.run(cmd, check=True, capture_output=True)
self.logger.info(f"✓ Database '{self.db_name}' created successfully")
return True
except subprocess.CalledProcessError as e:
self.logger.error(f"✗ Failed to create database: {e}")
return False
def restore_database(self, backup_file):
"""Restore database from backup"""
self.logger.info(f"Restoring database from {backup_file}...")
try:
# Extract SQL file from zip
self.logger.info("Extracting database backup archive...")
with zipfile.ZipFile(backup_file, 'r') as zip_ref:
sql_files = [f for f in zip_ref.namelist() if f.endswith('.sql')]
if not sql_files:
self.logger.error("✗ No SQL file found in backup archive")
return False
sql_file = sql_files[0]
self.logger.info(f" Found SQL file: {sql_file}")
extract_path = os.path.join(self.backup_dir, sql_file)
zip_ref.extract(sql_file, self.backup_dir)
self.logger.info(f"✓ Extracted to {extract_path}")
# Check if database exists, create if not
if not self.database_exists():
self.logger.warning(f"⚠ Database '{self.db_name}' does not exist")
if not self.create_database():
return False
else:
self.logger.info(f"✓ Database '{self.db_name}' exists")
# Drop all tables in database
self.logger.info("Dropping all existing tables...")
try:
cmd = [
'mysql',
f'--host={self.db_host}',
f'--user={self.db_user}',
f'--password={self.db_password}',
self.db_name,
'-e',
"SET FOREIGN_KEY_CHECKS = 0; "
"SET @tables = NULL; "
"SELECT GROUP_CONCAT(table_name) INTO @tables FROM information_schema.tables WHERE table_schema = DATABASE(); "
"SET @tables = CONCAT('DROP TABLE IF EXISTS ', @tables); "
"PREPARE stmt FROM @tables; "
"EXECUTE stmt; "
"DEALLOCATE PREPARE stmt; "
"SET FOREIGN_KEY_CHECKS = 1;"
]
subprocess.run(cmd, check=True, capture_output=True)
self.logger.info("✓ Dropped all existing tables")
except subprocess.CalledProcessError as e:
self.logger.warning(f"⚠ Warning during table drop: {e}")
# Restore database
self.logger.info("Importing database backup...")
with open(extract_path, 'r') as f:
cmd = [
'mysql',
f'--host={self.db_host}',
f'--user={self.db_user}',
f'--password={self.db_password}',
self.db_name
]
subprocess.run(cmd, stdin=f, check=True, capture_output=True)
# Clean up extracted SQL file
os.remove(extract_path)
self.logger.info(f"✓ Database restored successfully to '{self.db_name}'")
return True
except subprocess.CalledProcessError as e:
self.logger.error(f"✗ Failed to restore database: {e}")
return False
except Exception as e:
self.logger.error(f"✗ Unexpected error during database restore: {e}")
return False
def cleanup_downloaded_files(self, files):
"""Remove downloaded backup files"""
self.logger.info("Cleaning up downloaded files...")
for file_path in files:
try:
if os.path.exists(file_path):
os.remove(file_path)
self.logger.info(f" ✓ Removed {file_path}")
except Exception as e:
self.logger.warning(f" ⚠ Failed to remove {file_path}: {e}")
def run_restore(self):
"""Run complete restore process"""
self.logger.info("=" * 60)
self.logger.info("WORDPRESS RESTORE PROCESS STARTED")
self.logger.info("=" * 60)
# List available backups
backups = self.list_backups()
if not backups or (not backups['files'] and not backups['databases']):
self.logger.error("✗ No backups found. Exiting.")
return False
# Display file backups
print("\n" + "=" * 60)
print("AVAILABLE FILE BACKUPS:")
print("=" * 60)
for i, backup in enumerate(backups['files'], 1):
size_mb = int(backup['size']) / (1024 * 1024)
created = backup['createdTime'][:19].replace('T', ' ')
print(f"{i}. {backup['name']}")
print(f" Size: {size_mb:.2f} MB | Created: {created}")
# Display database backups
print("\n" + "=" * 60)
print("AVAILABLE DATABASE BACKUPS:")
print("=" * 60)
for i, backup in enumerate(backups['databases'], 1):
size_mb = int(backup['size']) / (1024 * 1024)
created = backup['createdTime'][:19].replace('T', ' ')
print(f"{i}. {backup['name']}")
print(f" Size: {size_mb:.2f} MB | Created: {created}")
# Get user selection for file backup
print("\n" + "=" * 60)
file_choice = input("Enter the FILE backup number to restore (or 0 to skip): ").strip()
selected_file_backup = None
if file_choice != '0':
try:
idx = int(file_choice) - 1
if 0 <= idx < len(backups['files']):
selected_file_backup = backups['files'][idx]
else:
self.logger.error("✗ Invalid file backup selection")
return False
except ValueError:
self.logger.error("✗ Invalid input")
return False
# Get user selection for database backup
db_choice = input("Enter the DATABASE backup number to restore (or 0 to skip): ").strip()
selected_db_backup = None
if db_choice != '0':
try:
idx = int(db_choice) - 1
if 0 <= idx < len(backups['databases']):
selected_db_backup = backups['databases'][idx]
else:
self.logger.error("✗ Invalid database backup selection")
return False
except ValueError:
self.logger.error("✗ Invalid input")
return False
if not selected_file_backup and not selected_db_backup:
self.logger.warning("⚠ No backups selected. Exiting.")
return False
# Confirm restore
print("\n" + "=" * 60)
print("⚠ WARNING: This will DELETE and REPLACE existing data!")
print("=" * 60)
if selected_file_backup:
print(f"File backup: {selected_file_backup['name']}")
print(f"Restore to: {self.wp_path}")
if selected_db_backup:
print(f"Database backup: {selected_db_backup['name']}")
print(f"Restore to database: {self.db_name}")
print("=" * 60)
confirm = input("Type 'YES' to confirm restore: ").strip()
if confirm != 'YES':
self.logger.info("Restore cancelled by user")
return False
downloaded_files = []
success = True
# Restore files
if selected_file_backup:
self.logger.info("\n" + "=" * 60)
self.logger.info("STARTING FILE RESTORE")
self.logger.info("=" * 60)
file_path = self.download_backup(
selected_file_backup['id'],
selected_file_backup['name']
)
if file_path:
downloaded_files.append(file_path)
if self.delete_wordpress_files():
if not self.restore_wordpress_files(file_path):
success = False
else:
success = False
else:
success = False
# Restore database
if selected_db_backup:
self.logger.info("\n" + "=" * 60)
self.logger.info("STARTING DATABASE RESTORE")
self.logger.info("=" * 60)
db_path = self.download_backup(
selected_db_backup['id'],
selected_db_backup['name']
)
if db_path:
downloaded_files.append(db_path)
if not self.restore_database(db_path):
success = False
else:
success = False
# Cleanup
self.cleanup_downloaded_files(downloaded_files)
# Final status
print("\n" + "=" * 60)
if success:
self.logger.info("✓ RESTORE PROCESS COMPLETED SUCCESSFULLY")
else:
self.logger.error("✗ RESTORE PROCESS COMPLETED WITH ERRORS")
self.logger.info("=" * 60)
return success
def main():
config_file = sys.argv[1] if len(sys.argv) > 1 else 'config.ini'
if not os.path.exists(config_file):
print(f"Error: Configuration file '{config_file}' not found")
sys.exit(1)
restore = WordPressRestore(config_file)
success = restore.run_restore()
sys.exit(0 if success else 1)
if __name__ == '__main__':
main()