199 lines
6.2 KiB
Python
199 lines
6.2 KiB
Python
#!/bin/env python3
|
|
|
|
import os
|
|
import shutil
|
|
import argparse
|
|
import subprocess
|
|
from datetime import datetime
|
|
from dotenv import load_dotenv
|
|
from nextcloud import NextCloud # type: ignore
|
|
|
|
# Load environment variables from .env file
|
|
load_dotenv(os.curdir + "\\.env")
|
|
|
|
|
|
# --- CONFIGURATION FROM ENVIRONMENT ---
|
|
NC_URL = os.getenv("NC_URL")
|
|
NC_USER = os.getenv("NC_USER")
|
|
NC_PASS = os.getenv("NC_PASS")
|
|
TEMP_DOWNLOAD_NAME = os.getenv("NC_TEMP_DOWNLOAD", "\tmp\temp_download.7z")
|
|
TEMP_EXTRACT_PATH = os.getenv("TEMP_EXTRACT_PATH")
|
|
TEMP_BACKUP_PATH = os.getenv("TEMP_BACKUP_PATH")
|
|
PLUGIN_CONFIG_PATH = os.getenv("PLUGIN_CONFIGS_PATH")
|
|
FFXIV_CONFIGS_PATH = os.getenv("FFXIV_CONFIGS_PATH")
|
|
# --------------------------------------
|
|
|
|
|
|
def validate_environment():
|
|
"""Validates that all necessary Nextcloud environment variables are set."""
|
|
missing = []
|
|
if not NC_URL:
|
|
missing.append("NC_URL")
|
|
if not NC_USER:
|
|
missing.append("NC_USER")
|
|
if not NC_PASS:
|
|
missing.append("NC_PASS")
|
|
if not NC_USER:
|
|
missing.append("PLUGIN_CONFIGS_PATH")
|
|
if not NC_PASS:
|
|
missing.append("FFXIV_CONFIGS_PATH")
|
|
|
|
if missing:
|
|
raise ValueError(
|
|
f"Missing required environment variables in .env: {', '.join(missing)}"
|
|
)
|
|
|
|
|
|
def parse_arguments():
|
|
"""Defines and parses command-line arguments."""
|
|
parser = argparse.ArgumentParser(description="""
|
|
Download a .7z archive from Nextcloud, backup local files, and replace them.""")
|
|
parser.add_argument(
|
|
"-r",
|
|
"--remote",
|
|
required=True,
|
|
help="Path to the .7z archive file inside Nextcloud (e.g., 'backups\\data.7z')",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def download_backup(
|
|
remote_archive_path: str,
|
|
hostname=NC_URL,
|
|
user=NC_USER,
|
|
password=NC_PASS,
|
|
temp_download_path=TEMP_DOWNLOAD_NAME,
|
|
):
|
|
"""Downloads the requested backup archive from Nextcloud"""
|
|
# Connect to Nextcloud
|
|
print("Connecting to Nextcloud...")
|
|
nc = NextCloud(hostname, user=user, password=password)
|
|
|
|
# Download 7z archive from Nextcloud
|
|
print(f"Downloading '{remote_archive_path}' from Nextcloud...")
|
|
try:
|
|
downloaded_file = nc.get_file(remote_archive_path) # type: ignore
|
|
downloaded_file.download(target=temp_download_path)
|
|
except Exception as e:
|
|
print(f"Error downloading file: {e}")
|
|
return
|
|
|
|
|
|
def decompress_backup(
|
|
temp_extract_path=TEMP_EXTRACT_PATH,
|
|
temp_download_path=TEMP_DOWNLOAD_NAME,
|
|
):
|
|
"""Extract the files from the backup"""
|
|
# Uncompress the downloaded .7z archive into target directory
|
|
print(f"Extracting .7z files to:\n -> {temp_extract_path}")
|
|
# 7z flags used:
|
|
# 'x' = extract with full paths
|
|
# '-o...' = target output directory (no space between -o and the path)
|
|
# '-y' = assume Yes on all queries (overwrite prompts)
|
|
cmd = ["7z", "x", temp_download_path, f"-o{temp_extract_path}", "-y"]
|
|
|
|
try:
|
|
# Run command and capture output; raises CalledProcessError if return code != 0
|
|
subprocess.run(
|
|
cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
|
)
|
|
print("Extraction completed successfully!")
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error: 7z extraction failed. {e}")
|
|
return
|
|
|
|
finally:
|
|
# Clean up downloaded archive file from configured path
|
|
if os.path.exists(temp_download_path):
|
|
os.remove(temp_download_path)
|
|
|
|
|
|
def backup_and_restore(
|
|
backup_config_path: str,
|
|
restore_config_path: str,
|
|
is_ffxiv_config: bool,
|
|
temp_extract_path=TEMP_EXTRACT_PATH,
|
|
):
|
|
"""Create backup of current config and restore downloaded backup"""
|
|
# Create backup of FFXIV config directory if it exists and has content
|
|
print(backup_config_path)
|
|
if os.path.exists(restore_config_path) and os.listdir(restore_config_path):
|
|
print(
|
|
"Creating safety backup of "
|
|
+ f"{'ffxiv' if is_ffxiv_config else 'plugins'} configs at:"
|
|
+ f"-> {backup_config_path}\\{'game' if is_ffxiv_config else 'plugins'}"
|
|
)
|
|
try:
|
|
shutil.copytree(
|
|
restore_config_path,
|
|
f"{backup_config_path}\\{'game' if is_ffxiv_config else 'plugins'}",
|
|
dirs_exist_ok=True,
|
|
)
|
|
shutil.rmtree(restore_config_path)
|
|
except Exception as e:
|
|
print(
|
|
f"Failed during {'ffxiv' if is_ffxiv_config else 'plugins'} config backup creation phase: {e}"
|
|
)
|
|
return
|
|
|
|
# Restore FFXIV config backup files
|
|
try:
|
|
print(
|
|
f"Attempting to restore {'ffxiv' if is_ffxiv_config else 'plugins'} config backups"
|
|
)
|
|
shutil.copytree(
|
|
f"{temp_extract_path}\\{"game" if is_ffxiv_config else "plugins"}\\",
|
|
restore_config_path,
|
|
dirs_exist_ok=True,
|
|
)
|
|
except Exception as e:
|
|
print(f"Failed during FFXIV config restore phase: {e}")
|
|
return
|
|
|
|
|
|
def temp_cleanup(temp_extract_path=TEMP_EXTRACT_PATH):
|
|
# Remove temporary extract files
|
|
try:
|
|
shutil.rmtree(temp_extract_path) # type: ignore
|
|
except Exception as e:
|
|
print(f"Failed to remove temporary extracted backup files: {e}")
|
|
return
|
|
|
|
|
|
def create_backup_dir(temp_backup_path=TEMP_BACKUP_PATH) -> str:
|
|
"""Create the unique directory that will store the extracted backup files"""
|
|
# Generate a unique, timestamped backup folder name in the temp backup directory
|
|
parent_dir = os.path.dirname(temp_backup_path) # type: ignore
|
|
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
|
|
backup_dir = os.path.join(parent_dir, f"backup_{timestamp}")
|
|
os.makedirs(backup_dir, exist_ok=True)
|
|
return backup_dir
|
|
|
|
|
|
def main():
|
|
# Parse command-line inputs and validate env
|
|
args = parse_arguments()
|
|
remote_archive_path = args.remote
|
|
|
|
try:
|
|
validate_environment()
|
|
except ValueError as e:
|
|
print(f"Configuration Error: {e}")
|
|
return
|
|
|
|
download_backup(remote_archive_path)
|
|
|
|
backup_dir = create_backup_dir()
|
|
|
|
decompress_backup()
|
|
|
|
backup_and_restore(backup_dir, FFXIV_CONFIGS_PATH, True) # type: ignore
|
|
|
|
backup_and_restore(backup_dir, PLUGIN_CONFIG_PATH, False) # type: ignore
|
|
|
|
temp_cleanup()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|