175 lines
5.3 KiB
Python
175 lines
5.3 KiB
Python
#!/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()
|