#!/usr/bin/env python3 import os import json import ftplib import sys import hashlib from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent PROJECT_DIR = SCRIPT_DIR.parent CONFIG_FILE = SCRIPT_DIR / 'deploy_config.json' CACHE_FILE = SCRIPT_DIR / '.deploy_cache.json' IGNORE_PATTERNS = { '.git', '.gitignore', 'scripts', '.deploy_cache.json', 'Serverdaten.txt', 'Serverdaten.txt.bak' } def load_config(): if not CONFIG_FILE.exists(): print(f"Error: Config file {CONFIG_FILE} does not exist.") sys.exit(1) with open(CONFIG_FILE, 'r', encoding='utf-8') as f: return json.load(f) def load_cache(): if CACHE_FILE.exists(): try: with open(CACHE_FILE, 'r', encoding='utf-8') as f: return json.load(f) except Exception: return {} return {} def save_cache(cache): with open(CACHE_FILE, 'w', encoding='utf-8') as f: json.dump(cache, f, indent=2) def compute_hash(filepath): h = hashlib.md5() with open(filepath, 'rb') as f: while chunk := f.read(8192): h.update(chunk) return h.hexdigest() def ensure_remote_dir(ftp, remote_path): dirs = [d for d in remote_path.strip('/').split('/') if d] current = '' for d in dirs: current += '/' + d try: ftp.cwd(current) except ftplib.error_perm: try: ftp.mkd(current) print(f"Created remote directory: {current}") except Exception as e: print(f"Warning: Could not create directory {current}: {e}") def should_ignore(rel_path): parts = Path(rel_path).parts if not parts: return False if parts[0] in IGNORE_PATTERNS: return True for part in parts: if part == '.htaccess': continue if part.startswith('.'): return True return False def deploy(): config = load_config() cache = load_cache() new_cache = dict(cache) print(f"Connecting to FTP {config['host']}...") try: if config.get('secure', False): ftp = ftplib.FTP_TLS() ftp.connect(config['host'], config.get('port', 21)) ftp.login(config['user'], config['pass']) ftp.prot_p() else: ftp = ftplib.FTP() ftp.connect(config['host'], config.get('port', 21)) ftp.login(config['user'], config['pass']) print("Logged in successfully.") except Exception as e: print(f"FTP Connection failed: {e}") sys.exit(1) files_to_upload = [] for root, dirs, files in os.walk(PROJECT_DIR): rel_dir = os.path.relpath(root, PROJECT_DIR) if rel_dir == '.': rel_dir = '' dirs[:] = [d for d in dirs if not should_ignore(os.path.join(rel_dir, d))] for f in files: rel_file = os.path.normpath(os.path.join(rel_dir, f)).replace('\\', '/') if should_ignore(rel_file): continue full_path = os.path.join(root, f) file_hash = compute_hash(full_path) if cache.get(rel_file) != file_hash: files_to_upload.append((rel_file, full_path, file_hash)) if not files_to_upload: print("No changed files to upload. Remote is up to date!") ftp.quit() return print(f"Found {len(files_to_upload)} file(s) to upload:") for rel_file, full_path, file_hash in files_to_upload: print(f" -> {rel_file}") for rel_file, full_path, file_hash in files_to_upload: remote_file_path = f"/{rel_file}" remote_dir = os.path.dirname(remote_file_path).replace('\\', '/') if remote_dir and remote_dir != '/': ensure_remote_dir(ftp, remote_dir) ftp.cwd('/') print(f"Uploading {rel_file} ...") with open(full_path, 'rb') as f: ftp.storbinary(f"STOR {remote_file_path}", f) new_cache[rel_file] = file_hash save_cache(new_cache) ftp.quit() print("Deployment completed successfully!") if __name__ == '__main__': deploy()