|
|
#!/bin/bash |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
set -euo pipefail |
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
|
|
BACKUP_DIR="${BACKUP_DIR:-/var/backups/ocngx}" |
|
|
BACKUP_DATE=$(date '+%Y%m%d_%H%M%S') |
|
|
BACKUP_ID="ocngx_backup_${BACKUP_DATE}" |
|
|
TEMP_BACKUP_DIR="/tmp/${BACKUP_ID}" |
|
|
|
|
|
|
|
|
detect_environment() { |
|
|
echo "🔍 检测运行环境..." |
|
|
|
|
|
if [[ -f /.dockerenv ]]; then |
|
|
ENVIRONMENT="docker" |
|
|
echo " 🐳 Docker 容器环境" |
|
|
elif [[ -d /app && -f /app/opencode.json ]]; then |
|
|
ENVIRONMENT="huggingface" |
|
|
echo " 🤗 HuggingFace Spaces 环境" |
|
|
else |
|
|
ENVIRONMENT="local" |
|
|
echo " 💻 本地环境" |
|
|
fi |
|
|
|
|
|
|
|
|
if command -v opencode &> /dev/null; then |
|
|
OPENCODE_CMD="opencode" |
|
|
OPENCODE_DATA_DIR="${HOME}/.opencode" |
|
|
elif npm list -g opencode-ai &> /dev/null; then |
|
|
OPENCODE_CMD="opencode" |
|
|
OPENCODE_DATA_DIR="${HOME}/.opencode" |
|
|
else |
|
|
echo " ❌ 未找到 OpenCode 安装" |
|
|
exit 1 |
|
|
fi |
|
|
|
|
|
echo " 📂 OpenCode 数据目录: ${OPENCODE_DATA_DIR}" |
|
|
} |
|
|
|
|
|
|
|
|
prepare_backup() { |
|
|
echo "📁 准备备份环境..." |
|
|
|
|
|
mkdir -p "$BACKUP_DIR" |
|
|
mkdir -p "$TEMP_BACKUP_DIR" |
|
|
|
|
|
|
|
|
cat > "${TEMP_BACKUP_DIR}/backup_metadata.json" << EOF |
|
|
{ |
|
|
"backup_id": "${BACKUP_ID}", |
|
|
"backup_date": "$(date -Iseconds)", |
|
|
"environment": "${ENVIRONMENT}", |
|
|
"hostname": "$(hostname)", |
|
|
"opencode_version": "$(opencode --version 2>/dev/null || echo 'unknown')", |
|
|
"system_info": { |
|
|
"os": "$(uname -s)", |
|
|
"arch": "$(uname -m)", |
|
|
"kernel": "$(uname -r)" |
|
|
}, |
|
|
"disk_usage": { |
|
|
"total": "$(df -h / | awk 'NR==2 {print $2}')", |
|
|
"used": "$(df -h / | awk 'NR==2 {print $3}')", |
|
|
"available": "$(df -h / | awk 'NR==2 {print $4}')" |
|
|
} |
|
|
} |
|
|
EOF |
|
|
} |
|
|
|
|
|
|
|
|
backup_configs() { |
|
|
echo "⚙️ 备份配置文件..." |
|
|
|
|
|
local config_dir="${TEMP_BACKUP_DIR}/configs" |
|
|
mkdir -p "$config_dir" |
|
|
|
|
|
|
|
|
[[ -f "${SCRIPT_DIR}/opencode.json" ]] && cp "${SCRIPT_DIR}/opencode.json" "$config_dir/" |
|
|
[[ -f "${SCRIPT_DIR}/AGENT.md" ]] && cp "${SCRIPT_DIR}/AGENT.md" "$config_dir/" |
|
|
[[ -f "${SCRIPT_DIR}/docker-start.sh" ]] && cp "${SCRIPT_DIR}/docker-start.sh" "$config_dir/" |
|
|
[[ -f "${SCRIPT_DIR}/Dockerfile" ]] && cp "${SCRIPT_DIR}/Dockerfile" "$config_dir/" |
|
|
|
|
|
|
|
|
if [[ -d "${SCRIPT_DIR}/nginx" ]]; then |
|
|
cp -r "${SCRIPT_DIR}/nginx" "$config_dir/" |
|
|
fi |
|
|
|
|
|
|
|
|
if [[ -d "${SCRIPT_DIR}/cron-jobs" ]]; then |
|
|
cp -r "${SCRIPT_DIR}/cron-jobs" "$config_dir/" |
|
|
fi |
|
|
|
|
|
|
|
|
env | grep -E '^(OPENCODE_|PUBLIC_|BASE_|GATEWAY_)' > "$config_dir/environment.txt" 2>/dev/null || true |
|
|
|
|
|
echo " ✅ 配置文件备份完成" |
|
|
} |
|
|
|
|
|
|
|
|
backup_opencode_data() { |
|
|
echo "🤖 备份 OpenCode 数据..." |
|
|
|
|
|
if [[ -d "$OPENCODE_DATA_DIR" ]]; then |
|
|
local data_dir="${TEMP_BACKUP_DIR}/opencode_data" |
|
|
mkdir -p "$data_dir" |
|
|
|
|
|
|
|
|
for subdir in "projects" "sessions" "config" "cache" "logs"; do |
|
|
if [[ -d "${OPENCODE_DATA_DIR}/${subdir}" ]]; then |
|
|
cp -r "${OPENCODE_DATA_DIR}/${subdir}" "$data_dir/" |
|
|
echo " 📦 已备份: $subdir" |
|
|
fi |
|
|
done |
|
|
|
|
|
|
|
|
[[ -f "${OPENCODE_DATA_DIR}/config.json" ]] && cp "${OPENCODE_DATA_DIR}/config.json" "$data_dir/" |
|
|
[[ -f "${OPENCODE_DATA_DIR}/preferences.json" ]] && cp "${OPENCODE_DATA_DIR}/preferences.json" "$data_dir/" |
|
|
|
|
|
echo " ✅ OpenCode 数据备份完成" |
|
|
else |
|
|
echo " ⚠️ OpenCode 数据目录不存在,跳过" |
|
|
fi |
|
|
} |
|
|
|
|
|
|
|
|
backup_runtime_state() { |
|
|
echo "🔄 备份运行时状态..." |
|
|
|
|
|
local runtime_dir="${TEMP_BACKUP_DIR}/runtime" |
|
|
mkdir -p "$runtime_dir" |
|
|
|
|
|
|
|
|
ps aux | grep -E "(opencode|nginx|openresty)" > "$runtime_dir/running_processes.txt" 2>/dev/null || true |
|
|
|
|
|
|
|
|
netstat -tlnp 2>/dev/null | grep -E ":(3000|7860)" > "$runtime_dir/network_state.txt" || true |
|
|
|
|
|
|
|
|
if [[ -d "/var/log" ]]; then |
|
|
find /var/log -name "*nginx*" -o -name "*opencode*" -mtime -7 -exec cp {} "$runtime_dir/" \; 2>/dev/null || true |
|
|
fi |
|
|
|
|
|
echo " ✅ 运行时状态备份完成" |
|
|
} |
|
|
|
|
|
|
|
|
create_archive() { |
|
|
echo "🗜️ 创建备份归档..." |
|
|
|
|
|
local archive_file="${BACKUP_DIR}/${BACKUP_ID}.tar.gz" |
|
|
|
|
|
|
|
|
tar -czf "$archive_file" -C "/tmp" "$BACKUP_ID" |
|
|
|
|
|
|
|
|
if [[ -f "$archive_file" ]]; then |
|
|
local archive_size=$(du -h "$archive_file" | cut -f1) |
|
|
echo " ✅ 备份归档创建成功: ${archive_file} (${archive_size})" |
|
|
|
|
|
|
|
|
sha256sum "$archive_file" > "${archive_file}.sha256" |
|
|
echo " 🔐 校验和已生成: ${archive_file}.sha256" |
|
|
else |
|
|
echo " ❌ 备份归档创建失败" |
|
|
exit 1 |
|
|
fi |
|
|
} |
|
|
|
|
|
|
|
|
cleanup_old_backups() { |
|
|
echo "🧹 清理旧备份..." |
|
|
|
|
|
cd "$BACKUP_DIR" |
|
|
|
|
|
|
|
|
local backup_count=$(ls -1 ocngx_backup_*.tar.gz 2>/dev/null | wc -l) |
|
|
if [[ $backup_count -gt 10 ]]; then |
|
|
echo " 🗑️ 删除超过 10 个的旧备份..." |
|
|
ls -t ocngx_backup_*.tar.gz 2>/dev/null | tail -n +11 | while read -r old_backup; do |
|
|
echo " 删除: $old_backup" |
|
|
rm -f "$old_backup" "${old_backup}.sha256" |
|
|
done |
|
|
fi |
|
|
|
|
|
|
|
|
local remaining_count=$(ls -1 ocngx_backup_*.tar.gz 2>/dev/null | wc -l) |
|
|
local total_size=$(du -sh "$BACKUP_DIR" 2>/dev/null | cut -f1) |
|
|
echo " 📊 当前备份状态: $remaining_count 个文件, 总大小: $total_size" |
|
|
} |
|
|
|
|
|
|
|
|
upload_to_external() { |
|
|
if [[ -n "${BACKUP_S3_BUCKET:-}" ]]; then |
|
|
echo "☁️ 上传备份到 S3..." |
|
|
|
|
|
local archive_file="${BACKUP_DIR}/${BACKUP_ID}.tar.gz" |
|
|
|
|
|
if command -v aws &> /dev/null; then |
|
|
aws s3 cp "$archive_file" "s3://${BACKUP_S3_BUCKET}/ocngx-backups/" --storage-class STANDARD_IA |
|
|
aws s3 cp "${archive_file}.sha256" "s3://${BACKUP_S3_BUCKET}/ocngx-backups/" |
|
|
echo " ✅ S3 上传完成" |
|
|
else |
|
|
echo " ⚠️ AWS CLI 未安装,跳过 S3 上传" |
|
|
fi |
|
|
fi |
|
|
} |
|
|
|
|
|
|
|
|
main() { |
|
|
echo "🚀 OCNGX 完整备份开始..." |
|
|
echo "📅 备份时间: $(date '+%Y-%m-%d %H:%M:%S')" |
|
|
echo "🆔 备份ID: ${BACKUP_ID}" |
|
|
echo "" |
|
|
|
|
|
detect_environment |
|
|
prepare_backup |
|
|
backup_configs |
|
|
backup_opencode_data |
|
|
backup_runtime_state |
|
|
create_archive |
|
|
cleanup_old_backups |
|
|
upload_to_external |
|
|
|
|
|
|
|
|
rm -rf "$TEMP_BACKUP_DIR" |
|
|
|
|
|
echo "" |
|
|
echo "✅ 备份完成!" |
|
|
echo "📁 备份位置: ${BACKUP_DIR}/${BACKUP_ID}.tar.gz" |
|
|
echo "🔍 验证命令: tar -tzf ${BACKUP_DIR}/${BACKUP_ID}.tar.gz | head -10" |
|
|
} |
|
|
|
|
|
|
|
|
main "$@" |