Files
piano-plan/docs/DEPLOYMENT_SOP.md
T

24 KiB
Raw Blame History

钢琴练习方案系统 - 部署 SOP

版本:v1.5.7 日期:2026-05-05 核心原则:不删除,只备份后新增/替换


重要更新(v1.5.7

🔧 动态API提供商管理

新增 API 提供商的新增、编辑、删除功能,替代原有的硬编码列表:

功能特点:

  • 新增提供商:填写ID/名称/Endpoint/模型列表
  • 编辑提供商:修改名称/Endpoint/模型列表(ID不可改)
  • 删除提供商:移除不再使用的提供商
  • 多模型支持:每个提供商可配置多个模型(>1个显示下拉框)

OpenCode Go 预置配置:

  • Endpointhttps://opencode.ai/zen/go/v1
  • 模型:deepseek-v4-prodeepseek-v4-flashqwen3.6-plus

API 接口:

  • GET /api/config/providers - 列出所有提供商
  • POST /api/config/providers - 新增提供商
  • PUT /api/config/providers/<id> - 编辑提供商
  • DELETE /api/config/providers/<id> - 删除提供商

🐛 Bug 修复

  • 问题删除字段名错误(problem_db_idproblem_id
  • 学员模型回退属性不存在(problem_name → "未知问题"
  • 删除问题提示文案过时
  • API Key 留空时保存报错(已有 key 则复用)
  • AI 失败时仍弹"方案生成成功"
  • API 超时 60s → 180smax_tokens 2000 → 4096

📊 调试增强

  • AI 生成日志显示 finish_reason、输入/输出 token 数
  • API 返回非 JSON 时显示原始内容和请求 URL
  • API 返回空内容时显示详细诊断信息

重要更新(v1.5.6

🔒 安全防护:Nginx 限流配置

新增 Nginx 限流规则,防止爬虫和恶意请求:

限流规则:

路由 限流规则 说明
/ (全站) 30请求/秒, burst=50 正常用户够用
/api/generate-plan 10请求/秒, burst=20 重点防护(AI生成接口)

超过限制返回: 429 Too Many Requests

配置文件:

  • 主配置:/etc/nginx/nginx.conf(限流zone定义)
  • 站点配置:/etc/nginx/conf.d/piano.yoin.fun.conf(限流规则应用)

完整的 nginx.conf 限流部分(http{} 块中添加):

    # 限流配置 - 防止爬虫和恶意请求
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=general_limit:10m rate=30r/s;
    limit_req_log_level warn;
    limit_req_status 429;

完整的站点配置(piano.yoin.fun.conf):

server {
    listen 80;
    server_name piano.yoin.fun;
    return 301 https://$server_name$request_uri;
}

server {
    server_name piano.yoin.fun;
    location / {
        limit_req zone=general_limit burst=50 nodelay;
        proxy_pass http://172.17.0.1:5001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_buffering off;
        proxy_cache off;
        tcp_nodelay on;
        proxy_http_version 1.1;
        proxy_set_header Connection '';
    }
    location /api/generate-plan {
        limit_req zone=api_limit burst=20 nodelay;
        proxy_pass http://172.17.0.1:5001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_buffering off;
        proxy_cache off;
        tcp_nodelay on;
        proxy_http_version 1.1;
        proxy_set_header Connection '';
        proxy_buffers 8 32k;
        proxy_buffer_size 32k;
        proxy_max_temp_file_size 1024m;
    }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/piano.yoin.fun/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/piano.yoin.fun/privkey.pem;
}

部署/修改方法:

# 1. 备份当前配置
docker exec nginx_server cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
docker exec nginx_server cp /etc/nginx/conf.d/piano.yoin.fun.conf /etc/nginx/conf.d/piano.yoin.fun.conf.bak

# 2. 使用 sed 或直接编辑修改配置
# 主配置:在 http{} 块中插入限流zone定义
# 站点配置:在 location{} 块中添加 limit_req 指令

# 3. 测试配置
docker exec nginx_server nginx -t -c /etc/nginx/nginx.conf

# 4. 重载nginx
docker exec nginx_server nginx -s reload

常用命令:

# 查看限流配置
docker exec nginx_server cat /etc/nginx/conf.d/piano.yoin.fun.conf | grep limit_req

# 检查nginx状态
docker exec nginx_server nginx -t

# 重载配置
docker exec nginx_server nginx -s reload

# 查看nginx日志(限流日志)
docker exec nginx_server tail -50 /var/log/nginx/error.log

💾 数据备份:每日自动备份

新增服务器本地每日自动备份机制:

备份配置:

  • 备份路径:/opt/backups/piano-db/
  • 执行时间:每天凌晨 3:00
  • 保留期限:30 天
  • 备份方式:SQLite .backup 命令(保证一致性)
  • 服务状态:crond 运行中

备份脚本位置: /opt/backups/backup_piano_db.sh(宿主机,不受容器重建影响)

完整脚本内容:

#!/bin/bash
# Piano Plan 数据库每日备份脚本
# 保留30天
# 自包含:部署后依然有效,不依赖容器内预存文件

BACKUP_DIR="/opt/backups/piano-db"
RETENTION_DAYS=30
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="piano_plans_${TIMESTAMP}.db"
SCRIPT_FILE="/tmp/backup_piano_$$.py"

mkdir -p "$BACKUP_DIR"

# 创建临时Python脚本(写在宿主机,然后复制到容器)
cat > "$SCRIPT_FILE" << 'PYEOF'
import sqlite3
conn = sqlite3.connect('/app/data/piano_plans.db')
backup = sqlite3.connect('/tmp/piano_backup.db')
conn.backup(backup)
backup.close()
conn.close()
PYEOF

# 复制到容器并执行
docker cp "$SCRIPT_FILE" piano-plan:/tmp/backup_piano.py
rm -f "$SCRIPT_FILE"
docker exec piano-plan python3 /tmp/backup_piano.py

# 复制备份到宿主机
docker cp piano-plan:/tmp/piano_backup.db "$BACKUP_DIR/$BACKUP_FILE"

# 清理容器内文件
docker exec piano-plan rm -f /tmp/backup_piano.py /tmp/piano_backup.db

# 验证
if [ -f "$BACKUP_DIR/$BACKUP_FILE" ]; then
    SIZE=$(du -h "$BACKUP_DIR/$BACKUP_FILE" | cut -f1)
    echo "[$(date)] Backup OK: $BACKUP_FILE ($SIZE)"

    # 删除30天前的备份
    find "$BACKUP_DIR" -name "piano_plans_*.db" -mtime +${RETENTION_DAYS} -delete
    echo "[$(date)] Cleanup done (retained $RETENTION_DAYS days)"
else
    echo "[$(date)] Backup FAILED"
    exit 1
fi

ls -lh "$BACKUP_DIR"

设置 cron 任务:

# 添加到 crontab
(crontab -l 2>/dev/null; echo '0 3 * * * /opt/backups/backup_piano_db.sh >> /opt/backups/backup.log 2>&1') | crontab -

# 验证
crontab -l

常用命令:

# 查看备份
ls -lh /opt/backups/piano-db/

# 手动执行备份
/opt/backups/backup_piano_db.sh

# 查看 cron 配置
crontab -l

# 删除旧的 cron 任务(如果需要)
crontab -e  # 编辑模式删除对应行

备份恢复:

# 停止容器
docker stop piano-plan

# 复制备份文件到容器
docker cp /opt/backups/piano-db/piano_plans_XXXXXXXX_XXXXXX.db piano-plan:/app/data/piano_plans.db

# 启动容器
docker start piano-plan

重要更新(v1.5.2

导出预览功能

在方案详情页新增「预览」按钮,点击弹出模态框展示套用模板导出后的最终效果(所见即所得)。

功能特点:

  • 预览内容与 PDF 导出效果一致(字体、标题层级、表格样式)
  • 支持水印预览(如已配置)
  • 内容超出时可滚动查看
  • 支持 <para alignment="center"> 居中语法(预览和 PDF 导出均支持)

模板居中写法:

<para alignment="center">【{generated_by} 撰写于 {generated_at}】</para>

技术实现:

  • 后端:GET /api/plans/<id>/preview 返回渲染后的 HTML
  • 前端:Bootstrap 模态框 + CSS 镜像 PDF 样式
  • 新增依赖:markdown Python 包(Markdown → HTML 转换)

重要更新(v1.5.1

⚠️ 问题文件已迁移到数据库

历史/app/个性化方案/*.md15个问题文件)

现状:所有问题数据已迁移到 problems 表,不再需要挂载问题文件目录。

影响

  • 部署时不再检查问题文件数量
  • 不再需要 /opt/piano-plan/个性化方案 挂载
  • 验证清单中"问题文件数量"检查已废弃

⚠️ Docker 构建需要代理

本地代理端口15000

构建命令:

$env:HTTP_PROXY="http://127.0.0.1:15000"
$env:HTTPS_PROXY="http://127.0.0.1:15000"
docker build -t piano-plan:latest .

⚠️ PDF 水印配置

PDF 水印文本在 API 设置页面(/api-settings)中配置,存储在 api_config.jsonwatermark_text 字段。

  • 留空 = 不显示水印
  • 填写文字 = 每页中央斜向显示该文字(56pt,25%透明度)

⚠️ 中文字体

Docker 镜像已内置 fonts-wqy-microhei,无需额外挂载。


一、部署原则(铁律)

操作 允许? 说明
删除容器 禁止 停止即可,容器配置是资产
删除 volume 禁止 数据资产,不可恢复
删除 host 文件 禁止 先备份到 /tmp/backup_YYYYMMDD/
覆盖文件 ⚠️ 先备份 任何覆盖操作前必须先备份
停止容器 允许 stop 是安全的
启动新容器 允许 配合正确的挂载配置

脚本优先原则(铁律)

使脚本执行失败时:修复脚本,而非绕过脚本。

双备份先行原则(铁律 - 新增)

在双备份(本地 releases/v{version}/bk/ + 服务器 /opt/piano-plan/releases/v{version}/bk/)全部完成之前,禁止执行任何实质性的部署操作。

以下操作在双备份完成前严禁执行:

  • 更新/修改生产数据库 schema
  • 停止旧 Docker 容器
  • 启动新 Docker 容器
  • 上传/覆盖生产环境数据
  • 加载新镜像到服务器

正确流程:

  1. 完成本地 releases/v{version}/bk/ 备份
  2. 完成服务器 /opt/piano-plan/releases/v{version}/bk/ 备份
  3. 验证两份备份均存在且完整
  4. 方可执行部署操作
错误行为 正确行为
脚本报错 → docker rm 手动清理 脚本报错 → 查看日志 → 修复脚本问题 → 重跑脚本
挂载丢失 → 手动指定新挂载 挂载丢失 → 更新脚本的挂载配置 → 重跑脚本
镜像加载失败 → docker rmi 清理 镜像加载失败 → 检查错误 → 重跑脚本
容器启动失败 → docker rm 重来 容器启动失败 → 查看 docker logs → 修复配置 → 重跑脚本

一旦开始用脚本部署,就要坚持用到底,中途放弃脚本去做手动操作,等于打开了破坏系统的潘多拉魔盒。


二、部署前检查清单

[ ] 确认本地代码已验证通过
[ ] 确认无未提交的代码
[ ] 确认获得用户的明确同意(用户说"部署吧"或"可以部署")
[ ] 确认需要保留的挂载点列表(见下方)
[ ] 确认 Docker Desktop 已启动(Windows

必须保留的挂载点

类型 容器内 说明
Volume piano-plan-data /app/data SQLite 数据库
Volume piano-plan-output /app/output PDF 输出
Bind Mount /opt/piano-plan/config /app/config API 配置

三、本地发布包准备

3.1 版本目录结构

每个版本发布前,在本地 releases/ 目录下创建:

releases/
└── v1.3.0/
    ├── bk/                    # 上次生产环境备份(来自服务器)
    │   ├── piano_plans.db     # 上次生产数据库
    │   ├── docker/            # 上次版本的 docker 镜像 tar
    │   │   └── piano-plan.tar
    │   └── config/           # 上次版本的配置备份
    │       └── api_config.json
    └── toRelease/             # 本次发布文件
        ├── program/           # 本次发布的程序包
        │   └── piano-plan.tar
        ├── schema.sql        # 数据库 schema(仅结构,不含数据)
        ├── scripts/           # 发布脚本
        │   └── deploy.sh
        └── config/            # 本次配置(如有变更)
            └── api_config.json

3.2 生成 schema.sql(仅结构)

cd "D:\F\NewI\opencode\daily-workspace\projects\青年钢琴集体课\练习方案系统"

# 进入版本目录
mkdir -p releases/v1.3.0/toRelease/scripts

# 导出数据库结构(不含数据)
docker exec piano-plan-for-export sqlite3 /app/data/piano_plans.db ".schema" > releases/v1.3.0/toRelease/schema.sql

# 或使用 python
python -c "
import sqlite3
conn = sqlite3.connect('data/piano_plans.db')
with open('releases/v1.3.0/toRelease/schema.sql', 'w', encoding='utf-8') as f:
    for line in conn.iterdump():
        if 'CREATE' in line or 'CREATE' in line.upper():
            f.write(line + '\n')
"

3.3 构建 Docker 镜像并打包

cd "D:\F\NewI\opencode\daily-workspace\projects\青年钢琴集体课\练习方案系统"

# 0. 配置代理(必须!)
$env:HTTP_PROXY="http://127.0.0.1:15000"
$env:HTTPS_PROXY="http://127.0.0.1:15000"

# 1. 构建镜像
docker build -t piano-plan:v1.4.0 .

# 2. 保存镜像
docker save piano-plan:v1.4.0 -o releases/v1.4.0/toRelease/program/piano-plan.tar

四、服务器部署步骤

4.1 SSH 到服务器

ssh -i ~/.ssh/id_rsa root@47.115.32.206

4.2 创建版本目录并备份上次生产环境

# 创建版本目录
mkdir -p /opt/piano-plan/releases/v1.3.0/bk
mkdir -p /opt/piano-plan/releases/v1.3.0/toRelease

# 备份当前生产环境到 bk/
# 1. 备份数据库
docker cp piano-plan:/app/data/piano_plans.db /opt/piano-plan/releases/v1.3.0/bk/piano_plans.db.$(date +%Y%m%d)

# 2. 备份 docker 镜像(如果存在)
docker save piano-plan:$(docker inspect piano-plan --format '{{.Config.Image}}' | sed 's/piano-plan://') -o /opt/piano-plan/releases/v1.3.0/bk/docker/piano-plan.tar 2>/dev/null || true

# 3. 备份配置
cp -r /opt/piano-plan/config /opt/piano-plan/releases/v1.3.0/bk/config.bak.$(date +%Y%m%d)

4.3 上传本次发布文件

# 在本地执行上传
exit  # 先退出服务器 SSH

# 上传发布包
scp -i ~/.ssh/id_rsa -r releases/v1.3.0/toRelease/* root@47.115.32.206:/opt/piano-plan/releases/v1.3.0/toRelease/

4.4 服务器执行部署

# SSH 再次连接
ssh -i ~/.ssh/id_rsa root@47.115.32.206

cd /opt/piano-plan/releases/v1.3.0

# 1. 停止旧容器
docker stop piano-plan
docker rm piano-plan

# 2. 加载新镜像
docker load -i toRelease/program/piano-plan.tar

# 3. 执行数据库迁移(仅 schema,不覆盖数据)
# 方法A:使用 sqlite3 直接执行 schema(如果有 ALTER TABLE 新增字段)
docker cp toRelease/schema.sql piano-plan:/tmp/schema.sql
docker exec piano-plan sqlite3 /app/data/piano_plans.db < /tmp/schema.sql

# 方法B:如果 schema 变更较大,先备份再重建表结构(不丢失数据)
# 具体看本次变更内容决定

# 4. 启动新容器
docker run -d \
  --name piano-plan \
  -p 5001:5001 \
  --restart unless-stopped \
  -e FLASK_ENV=production \
  -e TZ=Asia/Shanghai \
  -v piano-plan-data:/app/data \
  -v piano-plan-output:/app/output \
  -v /opt/piano-plan/config:/app/config \
  piano-plan:v1.3.0

⚠️ 重要TZ=Asia/Shanghai 必须设置,否则容器内 Python datetime.now() 返回 UTC 时间,比服务器时间早 8 小时,导致数据库记录的时间比实际时间晚 8 小时。

4.5 验证

# 检查容器状态
docker ps --filter name=piano-plan

# 检查日志
docker logs piano-plan --tail 30

# 验证数据库表结构
docker exec piano-plan sqlite3 /app/data/piano_plans.db ".tables"

# 验证服务
curl -I http://localhost:5001/

五、数据保护规范

5.1 必须保护的数据(绝对不删除)

数据类型 存储位置 说明
用户数据 piano-plan-data:/app/data users 表
学员数据 piano-plan-data:/app/data students, student_problems 表
班级数据 piano-plan-data:/app/data classes 表
练习方案 piano-plan-data:/app/data practice_plans 表
目标数据 piano-plan-data:/app/data goals, goal_relations, student_goals 表
问题数据 piano-plan-data:/app/data problems 表

5.2 本次发布 schema 变更

本次发布涉及新增目标管理模块,需要执行数据库迁移:

  • 新增 goals
  • 新增 goal_relations
  • 新增 student_goals
  • 修改 problems 表:更新分类体系
  • 修改 students 表:无变更

5.3 双备份保障

备份位置 内容 说明
/opt/piano-plan/releases/v1.3.0/bk/ 上次生产环境完整备份 版本回退用
/opt/piano-plan/releases/v1.3.1/bk/ 本次发布前备份 下次发布时自动创建

六、回滚流程

6.1 回滚到上一版本

# 停止当前容器
docker stop piano-plan
docker rm piano-plan

# 使用 bk 中的旧镜像(如果有)
docker load -i /opt/piano-plan/releases/v1.3.0/bk/docker/piano-plan.tar

# 启动旧容器
docker run -d \
  --name piano-plan \
  -p 5001:5001 \
  --restart unless-stopped \
  -e FLASK_ENV=production \
  -e TZ=Asia/Shanghai \
  -v piano-plan-data:/app/data \
  -v piano-plan-output:/app/output \
  -v /opt/piano-plan/config:/app/config \
  piano-plan:旧版本标签

6.2 数据库紧急回滚

# 停止容器
docker stop piano-plan

# 从 bk 恢复数据库
cp /opt/piano-plan/releases/v1.3.0/bk/piano_plans.db.上次的日期 piano-plan:/app/data/piano_plans.db

# 重启
docker start piano-plan

七、服务信息

项目
生产地址 https://piano.yoin.fun
SSH ssh -i ~/.ssh/id_rsa root@47.115.32.206
容器名 piano-plan
端口 5001
数据库位置 piano-plan-data volume
API配置位置 /opt/piano-plan/config
发布包位置 /opt/piano-plan/releases/

Volume 列表

Volume 容器内路径 说明
piano-plan-data /app/data SQLite 数据库
piano-plan-output /app/output PDF 导出

Bind Mount 列表

容器内路径 说明
/opt/piano-plan/config /app/config API 配置

八、API 配置说明

8.1 支持的 Provider

Provider Endpoint 模型
minimax https://api.minimaxi.com/anthropic/v1 MiniMax-M2.7-highspeed
volcengine https://ark.cn-beijing.volces.com/api/coding/v3 doubao-seed-2.0-pro
deepseek https://api.deepseek.com deepseek-chat

8.2 API 配置存储

  • 配置存储在 /opt/piano-plan/config/api_config.json
  • 每个 provider 的 key 存储在 api_keys 映射中
  • 切换 provider 时自动使用对应的 key

九、常见问题

Q: 数据库 schema 迁移失败?

A: 检查 sqlite3 版本兼容性,确保 .schema 导出的 SQL 兼容目标数据库版本

Q: 容器无法启动?

A: 检查日志 docker logs piano-plan,常见原因:端口被占用、volume 权限问题

Q: 目标管理功能报错?

A: 检查数据库是否成功执行了 schema 迁移,新增了 goals, goal_relations, student_goals 表

Q: SSE 不完整?

A: nginx 需要为 SSE 配置特定的代理设置,参考之前文档

Q: v1.3.2 部署后评估功能不工作?

A: 检查是否执行了 migrate_goals_v3.py 迁移脚本,该脚本创建 student_goal_evaluations 表


十、检查清单(部署完成后必填)

[ ] 容器状态:running
[ ] 服务响应:HTTP 200/302
[ ] 数据库表完整:users, students, classes, student_problems, practice_plans, templates, problems, goals, goal_relations, student_goals, student_goal_evaluations
[ ] practice_plans 表有新字段:created_by, updated_by, updated_at, template_id, is_typical
[ ] 目标管理功能正常:创建目标、分配目标、评估目标
[ ] 时间线正常显示阶段评估和最终评估
[ ] API 配置正确
[ ] 功能验证:能生成练习方案
[ ] 方案列表支持删除
[ ] 学员列表"暂无方案/问题"样式正常
[ ] PDF 水印功能正常(配置后导出可见)
[ ] 数据统计页面正常显示
[ ] 导出PDF时 {student_goals} 正常显示学员目标
[ ] 导出预览功能正常:预览按钮、模态框、水印显示
[ ] 模板支持 <para alignment="center"> 居中语法(预览和PDF均有效)

十一、版本历史

版本 日期 变更
v1.5.7 2026-05-05 动态API提供商管理(新增/编辑/删除/多模型);OpenCode Go集成;Bug修复(问题删除、空key保存、AI失败弹窗);调试增强
v1.5.6 2026-04-30 Nginx限流配置(防爬虫/恶意请求);每日自动备份(30天保留)
v1.5.5 2026-04-28 修复容器时区(TZ=Asia/Shanghai);学员列表"x个方案"可点击跳转最新方案详情
v1.5.4 2026-04-28 PDF正文字体12pt、表格11pt
v1.5.3 2026-04-28 PDF行间距修复(7mm→2mm
v1.5.2 2026-04-28 导出预览功能(预览按钮+模态框+水印);目标内容换行修复;支持居中语法;隐藏MD下载按钮
v1.5.1 2026-04-28 PDF水印配置保存修复(3处漏改);{student_goals}占位符修复;移除目标导出时的"内容:"标签
v1.5.0 2026-04-27 数据统计页面(问题/级别分布可视化);PDF水印配置(可自定义文本);编辑页按钮吸底;侧边栏顺序调整;Linux中文字体路径修复
v1.4.0 2026-04-27 典型方案采纳;推荐方案列表;方案编辑/详情页导航优化(bfcache处理);审计字段完善(created_by/updated_by/updated_at);方案列表支持删除;学员列表"暂无方案/问题"样式统一
v1.3.6 2026-04-24 方案详情导航优化;典型方案开关移至方案详情;方案列表显示问题级别+严重程度
v1.3.5 2026-04-24 班级班主任字段;用户姓名name字段;班级/学员/方案增加"我的"筛选
v1.3.4 2026-04-24 方案编辑按钮;问题增量添加;teachers API公开
v1.3.3 2026-04-24 评估日期编辑;最终评估关联 StudentGoal 同步
v1.3.2 2026-04-24 StudentGoal 新增 status 字段;新增 StudentGoalEvaluation 表
v1.3.1 2026-04-24 DRY 规范;Fragment 复用方案;班级批量分配目标
v1.3 2026-04-24 目标管理模块:Goal/GoalRelation/StudentGoal;问题分类重构
v1.2 2026-04-23 问题迁移到数据库;移除个性化方案挂载
v1.1 2026-04-20 模板管理;API配置界面
v1.0 2026-04-17 初始版本

十二、Ops 运维脚本

12.1 备份脚本 (scripts/ops/backup_piano_db.sh)

位置scripts/ops/backup_piano_db.sh(项目目录)

上传到服务器

scp -i ~/.ssh/id_rsa scripts/ops/backup_piano_db.sh root@47.115.32.206:/opt/backups/backup_piano_db.sh
chmod +x /opt/backups/backup_piano_db.sh

设置定时任务

ssh -i ~/.ssh/id_rsa root@47.115.32.206
(crontab -l 2>/dev/null; echo '0 3 * * * /opt/backups/backup_piano_db.sh >> /opt/backups/backup.log 2>&1') | crontab -

验证设置

crontab -l
ls -lh /opt/backups/piano-db/

12.2 脚本特性

  • 自包含:不依赖容器内预存文件
  • 幂等性:可重复执行
  • 验证:备份后自动验证文件存在
  • 清理:自动删除 30 天前的备份

12.3 恢复备份

# 1. 停止容器
docker stop piano-plan

# 2. 列出可用备份
ls -lh /opt/backups/piano-db/

# 3. 复制备份到容器
docker cp /opt/backups/piano-db/piano_plans_XXXXXXXX_XXXXXX.db piano-plan:/app/data/piano_plans.db

# 4. 启动容器
docker start piano-plan

最后更新2026-05-05 更新原因v1.5.7 - 动态API提供商管理;OpenCode Go集成;Bug修复