系统设置增加备份恢复:凌晨3点自动备份,保留10天,含 env 与复盘图片。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-04 06:50:44 +08:00
parent 9d3effd6fc
commit f846f832de
12 changed files with 579 additions and 4 deletions
+71 -2
View File
@@ -1,11 +1,11 @@
"""实例系统设置 API:导航开关,env 读写,改密,PM2 重启."""
"""实例系统设置 API:导航开关,env 读写,改密,PM2 重启,备份恢复."""
from __future__ import annotations
import os
from functools import wraps
from typing import Any, Callable
from flask import jsonify, request, session
from flask import jsonify, request, send_file, session
from lib.env.env_file_lib import apply_env_updates
from lib.env.env_ui_manifest import (
@@ -83,10 +83,14 @@ def register_instance_settings_routes(
exchange_key: str,
username: str,
password: str,
db_path: str | None = None,
upload_folder: str | None = None,
) -> None:
env_path = os.path.join(base_dir, ".env")
example_path = os.path.join(base_dir, ".env.example")
api_auth = _api_login_required()
resolved_db = (db_path or "").strip() or os.path.join(base_dir, "crypto.db")
resolved_upload = (upload_folder or "").strip() or os.path.join(base_dir, "static", "images")
@app.route("/api/settings/display", methods=["GET", "POST"])
@api_auth
@@ -175,6 +179,71 @@ def register_instance_settings_routes(
def api_admin_health():
return jsonify({"ok": True, "status": "up"})
@app.route("/api/settings/backup/list", methods=["GET"])
@api_auth
def api_settings_backup_list():
from lib.instance.backup_lib import backup_dir, backup_keep_days, list_backups
return jsonify(
{
"ok": True,
"items": list_backups(),
"backup_dir": backup_dir(),
"keep_days": backup_keep_days(),
"auto_hour_bj": 3,
"includes": [".env", "database", "options_journal"],
}
)
@app.route("/api/settings/backup/create", methods=["POST"])
@api_auth
def api_settings_backup_create():
from lib.instance.backup_lib import create_backup
try:
result = create_backup(
base_dir=base_dir,
db_path=resolved_db,
upload_folder=resolved_upload,
label="manual",
)
return jsonify(result)
except Exception as exc:
return jsonify({"ok": False, "msg": str(exc) or "备份失败"}), 500
@app.route("/api/settings/backup/download/<name>", methods=["GET"])
@api_auth
def api_settings_backup_download(name: str):
from lib.instance.backup_lib import backup_path
path = 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/restore", methods=["POST"])
@api_auth
def api_settings_backup_restore():
from lib.instance.backup_lib import restore_backup
body = request.get_json(silent=True) or {}
name = str(body.get("name") or "").strip()
if not name:
return jsonify({"ok": False, "msg": "缺少备份文件名"}), 400
result = restore_backup(
name,
base_dir=base_dir,
db_path=resolved_db,
upload_folder=resolved_upload,
)
code = 200 if result.get("ok") else 400
return jsonify(result), code
def tab_allowed_fn(tab: str) -> bool:
prefs = get_display_prefs(get_db)
return tab_allowed(tab, prefs)