Add polling auto-deploy script for NAS without SSH

This commit is contained in:
2026-05-25 08:19:40 +08:00
parent d4b217f60f
commit 71dd9d7e8f
+172
View File
@@ -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()