From 71dd9d7e8f1b2e66b4ef17606aaaa1e8dab8470d Mon Sep 17 00:00:00 2001 From: panda <1415243231@qq.com> Date: Mon, 25 May 2026 08:19:40 +0800 Subject: [PATCH] Add polling auto-deploy script for NAS without SSH --- auto_deploy.py | 172 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 auto_deploy.py diff --git a/auto_deploy.py b/auto_deploy.py new file mode 100644 index 0000000..8d58bf4 --- /dev/null +++ b/auto_deploy.py @@ -0,0 +1,172 @@ +#!/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 + api_url = f"{repo_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()