#!/usr/bin/env python3 """ Gitea Polling Auto-Deploy for publish project. Run via cron every N minutes on NAS host. Compares local git HEAD with remote, pulls + rebuilds if different. Usage: python3 auto_deploy.py --repo-url URL --branch BRANCH --work-dir DIR Add to crontab: */5 * * * * /usr/bin/python3 /path/to/auto_deploy.py ... """ import subprocess import os import sys import argparse import logging import json import urllib.request import urllib.error import time logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', handlers=[logging.StreamHandler(sys.stdout)] ) logger = logging.getLogger('auto_deploy') def get_remote_head(repo_url, branch): """Fetch the latest commit hash from Gitea API.""" # repo_url like: https://www.1415243231.top:8418/panda/daily_publish # Use HTTP to avoid SSL issues on NAS http_url = repo_url.replace('https://', 'http://') api_url = f"{http_url}/raw/commit/{branch}" try: req = urllib.request.Request(api_url, headers={'User-Agent': 'auto-deploy'}) with urllib.request.urlopen(req, timeout=10) as resp: data = json.loads(resp.read()) return data.get('id', '') except urllib.error.HTTPError as e: logger.warning(f"Gitea API error {e.code}: {e.reason}") return None except Exception as e: logger.warning(f"Failed to fetch remote HEAD: {e}") return None def get_local_head(work_dir): """Get local git HEAD commit hash.""" try: result = subprocess.run( ['git', 'rev-parse', 'HEAD'], cwd=work_dir, capture_output=True, text=True, timeout=10 ) return result.stdout.strip() if result.returncode == 0 else None except Exception as e: logger.warning(f"Failed to get local HEAD: {e}") return None def git_pull(work_dir): """Run git pull in the repo directory.""" logger.info(f"Git pull in {work_dir}") result = subprocess.run( ['git', 'pull', 'origin', 'master'], cwd=work_dir, capture_output=True, text=True, timeout=60 ) if result.returncode != 0: logger.error(f"Git pull failed: {result.stderr}") return False if 'up-to-date' in result.stdout.lower() or 'already up to date' in result.stdout.lower(): logger.info("Already up to date") return False logger.info(f"Git pull result: {result.stdout.strip()}") return True def build_and_up(work_dir): """Run npm install + build + docker-compose up --build.""" logger.info("Starting build and deploy...") # npm install + vite build build_result = subprocess.run( ['npm', 'install'], cwd=work_dir, capture_output=True, text=True, timeout=300 ) if build_result.returncode != 0: logger.error(f"npm install failed: {build_result.stderr}") return False logger.info("npm install done") build_result2 = subprocess.run( ['npm', 'run', 'build'], cwd=work_dir, capture_output=True, text=True, timeout=300 ) if build_result2.returncode != 0: logger.error(f"npm run build failed: {build_result2.stderr}") return False logger.info("vite build done") # docker-compose up --build -d compose_result = subprocess.run( ['docker-compose', '-f', 'docker-compose.yml', 'up', '--build', '-d'], cwd=work_dir, capture_output=True, text=True, timeout=600 ) if compose_result.returncode != 0: logger.error(f"docker-compose failed: {compose_result.stderr}") return False logger.info("Deploy completed successfully") return True def main(): parser = argparse.ArgumentParser(description='Gitea Auto-Deploy Polling') parser.add_argument('--repo-url', type=str, default='https://www.1415243231.top:8418/panda/daily_publish', help='Gitea repo URL') parser.add_argument('--branch', type=str, default='master', help='Branch to track') parser.add_argument('--work-dir', type=str, default='/vol1/1000/docker/publish', help='Local git repository path') parser.add_argument('--poll-interval', type=int, default=300, help='Polling interval in seconds (for info only)') args = parser.parse_args() logger.info(f"Checking {args.repo_url}/tree/{args.branch}") remote_head = get_remote_head(args.repo_url, args.branch) if remote_head is None: logger.error("Could not fetch remote HEAD, skipping deploy") sys.exit(1) local_head = get_local_head(args.work_dir) if local_head is None: logger.error("Could not get local HEAD, make sure it's a git repo") sys.exit(1) if remote_head == local_head: logger.info(f"No update (local={local_head[:8]}, remote={remote_head[:8]})") sys.exit(0) logger.info(f"Update detected! local={local_head[:8]}, remote={remote_head[:8]}") if not git_pull(args.work_dir): sys.exit(0) # Already up to date if build_and_up(args.work_dir): logger.info("All done!") else: logger.error("Deploy failed!") sys.exit(1) if __name__ == '__main__': main()