添加自动更新功能

This commit is contained in:
2025-12-15 14:34:08 +08:00
parent 809c735498
commit 0d1397debe
26 changed files with 1021 additions and 52 deletions

View File

@@ -8,4 +8,4 @@ admin_api_bp = Blueprint("admin_api", __name__, url_prefix="/yuyx/api")
# Import side effects: register routes on blueprint
from routes.admin_api import core as _core # noqa: F401
from routes.admin_api import update as _update # noqa: F401

146
routes/admin_api/update.py Normal file
View File

@@ -0,0 +1,146 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import os
import uuid
from flask import jsonify, request, session
from routes.admin_api import admin_api_bp
from routes.decorators import admin_required
from services.time_utils import get_beijing_now
from services.update_files import (
ensure_update_dirs,
get_update_job_log_path,
get_update_request_path,
get_update_result_path,
get_update_status_path,
load_json_file,
sanitize_job_id,
tail_text_file,
write_json_atomic,
)
def _request_ip() -> str:
try:
return request.headers.get("X-Forwarded-For", "").split(",")[0].strip() or request.remote_addr or ""
except Exception:
return ""
def _make_job_id(prefix: str = "upd") -> str:
now_str = get_beijing_now().strftime("%Y%m%d_%H%M%S")
rand = uuid.uuid4().hex[:8]
return f"{prefix}_{now_str}_{rand}"
def _has_pending_request() -> bool:
try:
return os.path.exists(get_update_request_path())
except Exception:
return False
@admin_api_bp.route("/update/status", methods=["GET"])
@admin_required
def get_update_status_api():
"""读取宿主机 Update-Agent 写入的 update/status.json。"""
ensure_update_dirs()
status_path = get_update_status_path()
data, err = load_json_file(status_path)
if err:
return jsonify({"ok": False, "error": f"读取 status 失败: {err}", "data": data}), 200
if not data:
return jsonify({"ok": False, "error": "未发现更新状态Update-Agent 可能未运行)"}), 200
data.setdefault("update_available", False)
return jsonify({"ok": True, "data": data}), 200
@admin_api_bp.route("/update/result", methods=["GET"])
@admin_required
def get_update_result_api():
"""读取 update/result.json最近一次更新执行结果"""
ensure_update_dirs()
result_path = get_update_result_path()
data, err = load_json_file(result_path)
if err:
return jsonify({"ok": False, "error": f"读取 result 失败: {err}", "data": data}), 200
if not data:
return jsonify({"ok": True, "data": None}), 200
return jsonify({"ok": True, "data": data}), 200
@admin_api_bp.route("/update/log", methods=["GET"])
@admin_required
def get_update_log_api():
"""读取 update/jobs/<job_id>.log 的末尾内容(用于后台展示进度)。"""
ensure_update_dirs()
job_id = sanitize_job_id(request.args.get("job_id"))
if not job_id:
# 若未指定,则尝试用 result.json 的 job_id
result_data, _ = load_json_file(get_update_result_path())
job_id = sanitize_job_id(result_data.get("job_id") if isinstance(result_data, dict) else None)
if not job_id:
return jsonify({"ok": True, "job_id": None, "log": "", "truncated": False}), 200
max_bytes = request.args.get("max_bytes", "200000")
try:
max_bytes_i = int(max_bytes)
except Exception:
max_bytes_i = 200_000
max_bytes_i = max(10_000, min(2_000_000, max_bytes_i))
log_path = get_update_job_log_path(job_id)
text, truncated = tail_text_file(log_path, max_bytes=max_bytes_i)
return jsonify({"ok": True, "job_id": job_id, "log": text, "truncated": truncated}), 200
@admin_api_bp.route("/update/check", methods=["POST"])
@admin_required
def request_update_check_api():
"""请求宿主机 Update-Agent 立刻执行一次检查更新。"""
ensure_update_dirs()
if _has_pending_request():
return jsonify({"error": "已有更新请求正在处理中,请稍后再试"}), 409
job_id = _make_job_id(prefix="chk")
payload = {
"job_id": job_id,
"action": "check",
"requested_at": get_beijing_now().strftime("%Y-%m-%d %H:%M:%S"),
"requested_by": session.get("admin_username") or "",
"requested_ip": _request_ip(),
}
write_json_atomic(get_update_request_path(), payload)
return jsonify({"success": True, "job_id": job_id}), 200
@admin_api_bp.route("/update/run", methods=["POST"])
@admin_required
def request_update_run_api():
"""请求宿主机 Update-Agent 执行一键更新并重启服务。"""
ensure_update_dirs()
if _has_pending_request():
return jsonify({"error": "已有更新请求正在处理中,请稍后再试"}), 409
job_id = _make_job_id(prefix="upd")
payload = {
"job_id": job_id,
"action": "update",
"requested_at": get_beijing_now().strftime("%Y-%m-%d %H:%M:%S"),
"requested_by": session.get("admin_username") or "",
"requested_ip": _request_ip(),
}
write_json_atomic(get_update_request_path(), payload)
return jsonify(
{
"success": True,
"job_id": job_id,
"message": "已提交更新请求服务将重启页面可能短暂不可用请等待1-2分钟后刷新",
}
), 200