Collapse dashboard server status into top bar and include .env in backup restore.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -48,6 +48,7 @@ RESTORE_MD = """# qihuo 备份恢复说明
|
||||
| `futures.db` | SQLite 主库(仅 SQLite 模式备份) |
|
||||
| `postgres_dump.sql` | PostgreSQL 逻辑备份(仅 PostgreSQL 模式) |
|
||||
| `uploads/` | 复盘截图与 K 线图(若备份时存在) |
|
||||
| `.env` | 环境配置(`config/.env` 或根目录 `.env`) |
|
||||
| `manifest.json` | 备份元数据(含 `backend` 字段) |
|
||||
| `restore.sh` | 一键恢复脚本 |
|
||||
|
||||
@@ -73,7 +74,7 @@ RESTORE_DIR=/opt/qihuo ./restore.sh
|
||||
```
|
||||
|
||||
3. 在新服务器部署 qihuo 代码与 Python 环境(见 `docs/POSTGRES.md` / `docs/DEPLOY.md`)
|
||||
4. 配置 `.env`(`DATABASE_URL` 或 SQLite、`SECRET_KEY`、CTP 账号等)
|
||||
4. 若包内含 `.env`,`restore.sh` 会自动恢复到应用目录(无需手工复制)
|
||||
5. 重启服务:`pm2 restart qihuo`
|
||||
|
||||
## PostgreSQL 恢复
|
||||
@@ -101,7 +102,7 @@ cp -a uploads/. /opt/qihuo/uploads/
|
||||
## 注意
|
||||
|
||||
- 恢复前请停止 qihuo 进程
|
||||
- `.env` 含敏感信息,请单独安全传输
|
||||
- `.env` 含敏感信息,请妥善保管备份包
|
||||
- 详见 `docs/POSTGRES.md` 与 `docs/BACKUP.md`
|
||||
"""
|
||||
|
||||
@@ -176,7 +177,25 @@ def _backup_postgres(dst_path: str) -> None:
|
||||
raise RuntimeError(f"pg_dump 失败: {proc.stderr.strip() or proc.stdout.strip()}")
|
||||
|
||||
|
||||
def _write_restore_script(dest: Path, *, backend: str) -> None:
|
||||
def _env_backup_info() -> tuple[Optional[Path], str]:
|
||||
"""返回 (源 .env 路径, 恢复到应用目录的相对路径)。"""
|
||||
from modules.core.paths import CONFIG_DIR, LEGACY_ENV_FILE, ROOT, resolve_env_file
|
||||
|
||||
src = Path(resolve_env_file())
|
||||
if not src.is_file():
|
||||
return None, "config/.env"
|
||||
try:
|
||||
if src.resolve().parent == CONFIG_DIR.resolve():
|
||||
return src, "config/.env"
|
||||
if src.resolve().parent == ROOT.resolve() and src.name == ".env":
|
||||
if LEGACY_ENV_FILE.is_file() and not (CONFIG_DIR / ".env").is_file():
|
||||
return src, ".env"
|
||||
except Exception:
|
||||
pass
|
||||
return src, "config/.env"
|
||||
|
||||
|
||||
def _write_restore_script(dest: Path, *, backend: str, env_restore_path: str = "") -> None:
|
||||
pg_block = ""
|
||||
if backend == "postgres":
|
||||
pg_block = """
|
||||
@@ -201,13 +220,23 @@ if [ -f "$SCRIPT_DIR/postgres_dump.sql" ]; then
|
||||
psql "$DATABASE_URL" -f "$SCRIPT_DIR/postgres_dump.sql"
|
||||
echo "PostgreSQL 导入完成"
|
||||
fi
|
||||
"""
|
||||
env_block = ""
|
||||
if env_restore_path:
|
||||
env_block = f"""
|
||||
if [ -f "$SCRIPT_DIR/.env" ]; then
|
||||
ENV_DEST="$RESTORE_DIR/{env_restore_path}"
|
||||
mkdir -p "$(dirname "$ENV_DEST")"
|
||||
cp -f "$SCRIPT_DIR/.env" "$ENV_DEST"
|
||||
echo "已复制 .env -> $ENV_DEST"
|
||||
fi
|
||||
"""
|
||||
script = f"""#!/bin/bash
|
||||
set -euo pipefail
|
||||
RESTORE_DIR="${{RESTORE_DIR:-{default_restore_dir()}}}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
mkdir -p "$RESTORE_DIR/uploads"
|
||||
{pg_block}
|
||||
{pg_block}{env_block}
|
||||
if [ -f "$SCRIPT_DIR/futures.db" ]; then
|
||||
cp -f "$SCRIPT_DIR/futures.db" "$RESTORE_DIR/futures.db"
|
||||
echo "已复制 futures.db -> $RESTORE_DIR/futures.db"
|
||||
@@ -218,7 +247,7 @@ if [ -d "$SCRIPT_DIR/uploads" ]; then
|
||||
fi
|
||||
echo ""
|
||||
echo "恢复完成。目标目录: $RESTORE_DIR"
|
||||
echo "下一步: 确认 .env、pm2 restart qihuo"
|
||||
echo "下一步: pm2 restart qihuo"
|
||||
echo "详见 RESTORE.md 与 docs/POSTGRES.md"
|
||||
"""
|
||||
dest.write_text(script, encoding="utf-8")
|
||||
@@ -251,12 +280,20 @@ def create_backup(*, include_uploads: bool = True) -> tuple[str, str]:
|
||||
if include_uploads and upload_src.is_dir():
|
||||
shutil.copytree(upload_src, work / "uploads", dirs_exist_ok=True)
|
||||
|
||||
env_src, env_restore_path = _env_backup_info()
|
||||
includes_env = False
|
||||
if env_src and env_src.is_file():
|
||||
shutil.copy2(env_src, work / ".env")
|
||||
includes_env = True
|
||||
|
||||
manifest = {
|
||||
"app": "qihuo",
|
||||
"backend": backend,
|
||||
"created_at": datetime.now(TZ).isoformat(timespec="seconds"),
|
||||
"db_path": DB_PATH if backend == "sqlite" else (os.getenv("DATABASE_URL") or ""),
|
||||
"includes_uploads": include_uploads and upload_src.is_dir(),
|
||||
"includes_env": includes_env,
|
||||
"env_restore_path": env_restore_path if includes_env else "",
|
||||
"default_restore_dir": default_restore_dir(),
|
||||
"files": sorted(p.name for p in work.iterdir()),
|
||||
}
|
||||
@@ -265,7 +302,11 @@ def create_backup(*, include_uploads: bool = True) -> tuple[str, str]:
|
||||
encoding="utf-8",
|
||||
)
|
||||
(work / "RESTORE.md").write_text(RESTORE_MD, encoding="utf-8")
|
||||
_write_restore_script(work / "restore.sh", backend=backend)
|
||||
_write_restore_script(
|
||||
work / "restore.sh",
|
||||
backend=backend,
|
||||
env_restore_path=env_restore_path if includes_env else "",
|
||||
)
|
||||
|
||||
with tarfile.open(out_path, "w:gz") as tar:
|
||||
tar.add(work, arcname=folder_name)
|
||||
|
||||
Reference in New Issue
Block a user