系统设置增加备份恢复:中控本机备份,并可下载各客户端备份。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-04 06:50:44 +08:00
parent 9f3a1f5b66
commit a983fff6e0
7 changed files with 720 additions and 2 deletions
+134 -1
View File
@@ -11,11 +11,13 @@ from pathlib import Path
from dotenv import load_dotenv
from flask import (
Flask,
Response,
flash,
jsonify,
redirect,
render_template,
request,
send_file,
session,
url_for,
)
@@ -27,8 +29,19 @@ if str(ROOT) not in sys.path:
ENV_PATH = ROOT / ".env"
load_dotenv(ENV_PATH)
from lib.backup_lib import (
backup_dir as hub_backup_dir,
backup_keep_days as hub_backup_keep_days,
backup_path as hub_backup_path,
create_backup as create_hub_backup,
list_backups as list_hub_backups,
restore_backup as restore_hub_backup,
start_backup_scheduler as start_hub_backup_scheduler,
)
from lib.bridge_client_lib import (
dispatch_cond_order_to_ids,
fetch_backup_file,
fetch_backup_list,
fetch_cond_orders,
post_close_full,
post_cond_cancel,
@@ -36,7 +49,7 @@ from lib.bridge_client_lib import (
pull_all_enabled,
)
from lib.cash_sample_lib import record_cash_samples_from_overview
from lib.db_lib import init_db
from lib.db_lib import db_path as hub_db_path, init_db
from lib.env_lib import apply_env_updates, env_get
from lib.health_watch_lib import inspect_overview_rows
from lib.instances_lib import (
@@ -309,6 +322,116 @@ def settings_page():
)
@app.route("/api/settings/backup/hub/list", methods=["GET"])
@login_required
def api_hub_backup_list():
return jsonify(
{
"ok": True,
"items": list_hub_backups(),
"backup_dir": hub_backup_dir(),
"keep_days": hub_backup_keep_days(),
"auto_hour_bj": 3,
"includes": [".env", "database"],
}
)
def _resolved_hub_db_path() -> str:
p = hub_db_path()
if os.path.isabs(p):
return p
return str(ROOT / p)
@app.route("/api/settings/backup/hub/create", methods=["POST"])
@login_required
def api_hub_backup_create():
try:
result = create_hub_backup(
root_dir=str(ROOT),
env_path=str(ENV_PATH),
db_path=_resolved_hub_db_path(),
label="manual",
)
return jsonify(result)
except Exception as exc:
return jsonify({"ok": False, "msg": str(exc) or "备份失败"}), 500
@app.route("/api/settings/backup/hub/download/<name>", methods=["GET"])
@login_required
def api_hub_backup_download(name: str):
path = hub_backup_path(name)
if not path or not os.path.isfile(path):
return jsonify({"ok": False, "msg": "备份不存在"}), 404
return send_file(
path,
mimetype="application/gzip",
as_attachment=True,
download_name=os.path.basename(path),
)
@app.route("/api/settings/backup/hub/restore", methods=["POST"])
@login_required
def api_hub_backup_restore():
data = request.get_json(silent=True) or {}
name = str(data.get("name") or "").strip()
if not name:
return jsonify({"ok": False, "msg": "缺少备份文件名"}), 400
result = restore_hub_backup(
name,
root_dir=str(ROOT),
env_path=str(ENV_PATH),
db_path=_resolved_hub_db_path(),
)
return jsonify(result), (200 if result.get("ok") else 400)
@app.route("/api/settings/backup/instances", methods=["GET"])
@login_required
def api_backup_instances():
items = [
{
"id": i["id"],
"name": i.get("name") or f"#{i['id']}",
"enabled": bool(i.get("enabled")),
}
for i in list_instances(enabled_only=False)
]
return jsonify({"ok": True, "items": items})
@app.route("/api/settings/backup/client/<int:iid>/list", methods=["GET"])
@login_required
def api_client_backup_list(iid: int):
inst = get_instance(iid)
if not inst:
return jsonify({"ok": False, "msg": "实例不存在", "items": []}), 404
result = fetch_backup_list(inst)
code = 200 if result.get("ok") else 502
return jsonify(result), code
@app.route("/api/settings/backup/client/<int:iid>/download/<name>", methods=["GET"])
@login_required
def api_client_backup_download(iid: int, name: str):
inst = get_instance(iid)
if not inst:
return jsonify({"ok": False, "msg": "实例不存在"}), 404
ok, content, meta = fetch_backup_file(inst, name)
if not ok or content is None:
return jsonify({"ok": False, "msg": meta or "下载失败"}), 502
return Response(
content,
mimetype="application/gzip",
headers={
"Content-Disposition": f'attachment; filename="{os.path.basename(meta)}"'
},
)
@app.route("/desk")
@login_required
def desk():
@@ -369,6 +492,16 @@ def start_health_watch() -> None:
start_health_watch()
try:
start_hub_backup_scheduler(
root_dir=str(ROOT),
env_path=str(ENV_PATH),
db_path_fn=_resolved_hub_db_path,
hour=3,
)
except Exception as _hub_bak_err:
print(f"[backup] scheduler: {_hub_bak_err}")
@app.route("/api/instances/<int:iid>/open_url")
@login_required