Add polling auto-deploy script for NAS without SSH
This commit is contained in:
+172
@@ -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()
|
||||
Reference in New Issue
Block a user