云崽持久化备份和迁移恢复
帮写我两个脚本
第一个脚本我需要实现:
提取位于/opt/docker里的Yunzai目录的config/config/里的bot.yaml、group.yaml、other.yaml,提取Yunzai里的data、resources目录,提取Yunzai/plugins里的目录体积小于100mb的文件夹,要保留提取文件的yunzai里目录结构位置生成一个压缩包,对于Yunzai/plugins里的大于100mb以上的目录,分别提取里面的git clone的git仓库链接汇总待用,并且提取Yunzai/plugins里大于100mb的各个目录里面的config文件夹,其中waves-plugin/resources/rolepic 也保留,以上仍然保留目录结构,另外建立备份压缩包。以上两个压缩包和git链接的txt放在/opt/docker目录。
AI:
#!/bin/bash
set -euo pipefail
# 路径配置
yunzai_dir="/opt/docker/Yunzai"
backup_dir="/opt/docker"
tmp1="${backup_dir}/tmp_part1" # 小文件临时目录
tmp2="${backup_dir}/tmp_part2" # 大文件临时目录
git_links="${backup_dir}/git_clone_links.txt"
# 清理旧文件
rm -rf "$tmp1" "$tmp2" "${backup_dir}/backup_part1.tar.gz" "${backup_dir}/backup_part2.tar.gz" "$git_links"
# 创建临时目录
mkdir -p "$tmp1" "$tmp2"
# 智能复制函数(自动创建目录结构)
preserve_copy() {
local src="$1"
local dest_root="$2"
local relative_path="${src#$yunzai_dir/}"
if [[ -e "$src" ]]; then
mkdir -p "${dest_root}/$(dirname "$relative_path")"
cp -rf "$src" "${dest_root}/$relative_path"
else
echo "警告: 跳过不存在的文件 $src"
fi
}
# Part1:处理小文件备份
echo "正在收集第一部分数据..."
# 复制配置文件
for config in bot.yaml group.yaml other.yaml; do
preserve_copy "${yunzai_dir}/config/config/$config" "$tmp1"
done
# 复制核心目录
for dir in data resources; do
preserve_copy "${yunzai_dir}/$dir" "$tmp1"
done
# 处理小插件(≤100MB)
find "${yunzai_dir}/plugins" -maxdepth 1 -type d | while read -r plugin; do
if [[ "$plugin" != "${yunzai_dir}/plugins" ]]; then
size_kb=$(du -sk "$plugin" | cut -f1)
if (( size_kb <= 100000 )); then # 100MB=100000KB
preserve_copy "$plugin" "$tmp1"
fi
fi
done
# Part2:处理大插件相关数据
echo "正在收集第二部分数据..."
echo "# 需要手动克隆的插件列表" > "$git_links"
find "${yunzai_dir}/plugins" -maxdepth 1 -type d | while read -r plugin; do
if [[ "$plugin" != "${yunzai_dir}/plugins" ]]; then
size_kb=$(du -sk "$plugin" | cut -f1)
if (( size_kb > 100000 )); then # 精确100MB判断
# 记录git信息
if [[ -d "${plugin}/.git" ]]; then
git_url=$(git -C "$plugin" config --get remote.origin.url 2>/dev/null || true)
if [[ -n "$git_url" ]]; then
echo "插件: $(basename "$plugin")" >> "$git_links"
echo "git clone $git_url" >> "$git_links"
echo "" >> "$git_links"
fi
fi
# 复制config目录
if [[ -d "${plugin}/config" ]]; then
preserve_copy "${plugin}/config" "$tmp2"
fi
# 特殊处理waves-plugin的rolepic
if [[ "$(basename "$plugin")" == "waves-plugin" ]]; then
rolepic_src="${plugin}/resources/rolePic"
if [[ -d "$rolepic_src" ]]; then
preserve_copy "$rolepic_src" "$tmp2"
fi
fi
fi
fi
done
# 创建压缩包(使用tar.gz格式)
echo "正在生成压缩文件..."
tar -czf "${backup_dir}/yunzai_backup_part1.tar.gz" -C "$tmp1" .
tar -czf "${backup_dir}/yunzai_backup_part2.tar.gz" -C "$tmp2" .
# 清理临时文件
rm -rf "$tmp1" "$tmp2"
echo "操作完成!生成文件:"
ls -lh "${backup_dir}/yunzai_backup_part1.tar.gz" "${backup_dir}/yunzai_backup_part2.tar.gz" "$git_links"
第二个脚本
第二个脚本我需要实现
执行下面这个命令,
DIR=/opt/docker/Yunzai CMD=yz DKNAME=yunzai NETWORK=1panel-network bash <(curl -L https://gitee.com/TimeRainStarSky/Yunzai/raw/main/lib/tools/docker.sh)
直到名为yunzai的容器运行后,进入容器内部终端(容器内终端初始目录已经是Yunzai),执行多个 git clone前面让你提取仓库git链接的的多个插件项目仓库到Yunzai下的plugins文件夹内,完成后退出容器终端。解压位于/opt/docker的两个压缩包到Yunzai目录覆盖。解压完后再次进入容器终端,遍历Yunazi/plugins里的所有插件目录,在里面分别执行pnpm i 安装依赖,无需安装依赖的插件目录跳过。
注意容器里已经有git和pnpm等我要让你用到的环境,无需再安装。
安装完依赖后重启yunzai容器。
AI:
#!/bin/bash
set -euo pipefail
# 配置参数
export DIR="/opt/docker/Yunzai"
export CMD="yz"
export DKNAME="yunzai"
export NETWORK="1panel-network"
backup_dir="/opt/docker"
git_links="${backup_dir}/git_clone_links.txt"
# 彩色输出定义
RED='\033[31m'
GREEN='\033[32m'
YELLOW='\033[33m'
BLUE='\033[34m'
RESET='\033[0m'
CYAN='\033[36m' # 添加此行
# GitHub镜像代理配置(可自定义顺序)
GITHUB_PROXY_FORMATS=(
"https://gitclone.com/github.com/{author}/{repo}"
"https://ghproxy.357361.xyz/github.com/{author}/{repo}"
# "https://hub.fastgit.org/{author}/{repo}" # 可添加更多镜像源
)
# 阶段1:部署新容器
echo -e "${BLUE}▌阶段1/5 正在部署新容器...${RESET}"
if ! bash <(curl -fsSL https://gitee.com/TimeRainStarSky/Yunzai/raw/main/lib/tools/docker.sh); then
echo -e "${RED}错误:容器部署失败!${RESET}"
exit 1
fi
# 等待容器启动
echo -ne "${YELLOW}等待容器启动...${RESET}"
until docker inspect --format '{{.State.Running}}' "$DKNAME" | grep -q "true"; do
sleep 2
echo -n "."
done
echo -e "${GREEN} 已运行!${RESET}"
# 阶段2:安装Git插件(增强代理重试版+存在性检查)
echo -e "${BLUE}▌阶段2/5 正在安装Git插件...${RESET}"
# 新增文件存在性检查(带彩色路径显示)
if [[ ! -f "$git_links" ]]; then
echo -e "${YELLOW}▌未检测到Git链接文件[${CYAN}${git_links}${YELLOW}],跳过插件克隆阶段${RESET}"
else
clone_cmds=$(grep -E '^git clone ' "$git_links" | sed 's/^ *//;s/ *$//')
if [[ -n "$clone_cmds" ]]; then
docker exec -i "$DKNAME" bash <<EOF
set -eo pipefail
cd plugins || { echo -e "${RED}▌错误:无法进入plugins目录${RESET}" >&2; exit 1; }
# 代理重试克隆函数(带耗时统计)
clone_with_proxy() {
original_url="\$1"
target_dir="\$2"
echo -e "原始地址: \${YELLOW}\${original_url}${RESET}"
# 解析GitHub项目路径
if [[ \$original_url =~ ^https://github.com/([^/]+)/([^/]+)(/|\.git)?$ ]]; then
author="\${BASH_REMATCH[1]}"
repo="\${BASH_REMATCH[2]%.git}"
retry_sources=("\$original_url")
# 生成代理地址
for proxy_format in ${GITHUB_PROXY_FORMATS[@]}; do
proxy_url="\${proxy_format//\{author\}/\$author}"
proxy_url="\${proxy_url//\{repo\}/\$repo}"
retry_sources+=("\$proxy_url")
done
else
echo -e "${YELLOW}▌非GitHub地址,直接尝试克隆${RESET}"
retry_sources=("\$original_url")
fi
# 依次尝试所有克隆源
for url in "\${retry_sources[@]}"; do
echo -n "尝试克隆: \${url} ... "
if git clone --progress "\${url}" "\$target_dir" 2>&1 ; then
echo -e "${GREEN}成功${RESET}"
return 0
fi
status_code=\$?
echo -e "${RED}失败 (代码:\$status_code)${RESET}"
rm -rf "\$target_dir" 2>/dev/null || true
done
echo -e "${RED}▌所有镜像源尝试均失败!${RESET}"
return 1
}
# 执行克隆命令(带存在性检查和错误处理)
exit_status=0
while read -r cmd; do
args=(\$cmd)
url="\${args[2]}"
dir="\${args[3]:-}"
# 计算目标目录路径
if [[ -n "\$dir" ]]; then
clone_dir="\${dir}"
else
clone_dir="\$(basename "\$url" .git)"
fi
# 目录存在性检查
if [ -d "\$clone_dir" ]; then
echo -e "\n${YELLOW}▌检测到现有插件: \${clone_dir}${RESET}"
echo -e "${YELLOW} ▌目录已存在,跳过克隆操作${RESET}"
continue
fi
# 开始克隆流程
echo -e "\n▌克隆项目: \${YELLOW}\${clone_dir}${RESET}"
SECONDS=0
if ! clone_with_proxy "\$url" "\$clone_dir"; then
echo -e "${RED}▌严重: 插件克隆失败,终止流程!${RESET}"
exit_status=3
break
fi
echo -e "耗时: ${MAGENTA}\${SECONDS}s${RESET}"
done <<< "$clone_cmds"
exit \$exit_status
EOF
# Docker执行结果检查
if [ \$? -ne 0 ]; then
echo -e "${RED}▌错误:插件安装过程中发生致命错误${RESET}"
exit 4
fi
else
echo -e "${YELLOW}▌文件中未找到有效的插件仓库链接${RESET}"
fi
fi
# 宿主机验证提示
echo -e "\n${GREEN}插件同步完成,请检查宿主机目录:"
echo -e " ${YELLOW}/opt/docker/Yunzai/plugins${RESET}"
# 阶段3:恢复备份数据
echo -e "${BLUE}▌阶段3/5 正在恢复备份...${RESET}"
# 设置nullglob避免未匹配时保留通配符
shopt -s nullglob
backup_files=("${backup_dir}"/yunzai_backup_part*.tar.gz)
shopt -u nullglob # 恢复原设置
temp_dir=$(mktemp -d -p "$backup_dir")
if [ ${#backup_files[@]} -eq 0 ]; then
echo -e "${YELLOW}未找到备份压缩包,已跳过解压步骤${RESET}"
rm -rf "$temp_dir" # 没有文件时也删除临时目录
else
for backup in "${backup_files[@]}"; do
echo -e "处理备份文件: ${YELLOW}$(basename "$backup")${RESET}"
# 直接解压到宿主机持久化目录(保留所有属性)
if ! tar -xzpf "$backup" -C "$DIR" --warning=no-unknown-keyword 2>/tmp/tar_error.log; then
echo -e "${RED}错误: 解压失败,详情:${RESET}"
cat /tmp/tar_error.log
rm -rf "$temp_dir"
exit 4
fi
# 验证解压文件数量
expected_files=$(tar -tzf "$backup" | wc -l)
actual_files=$(find "$DIR" -type f | wc -l)
if [[ "$actual_files" -lt "$expected_files" ]]; then
echo -e "${YELLOW}警告: 解压文件数量不足 (应有:$expected_files 现有:$actual_files)${RESET}"
fi
done
rm -rf "$temp_dir" # 所有解压完成后删除临时目录
fi
# 阶段4:安装插件依赖(增强版)
echo -e "${BLUE}▌阶段4/5 正在安装插件依赖...${RESET}"
docker exec -i "$DKNAME" bash <<'EOF'
set -eo pipefail
log_dir="/root/Yunzai/plugins/_install_logs"
mkdir -p "$log_dir"
cd /root/Yunzai/plugins || exit 1
# 设置npm镜像源
npm config set registry https://registry.npmmirror.com
find . -maxdepth 1 -type d -print0 | while IFS= read -r -d '' plugin; do
if [[ "$plugin" != "." ]]; then
plugin_name="${plugin#./}"
log_file="${log_dir}/${plugin_name}_$(date +%Y%m%d-%H%M%S).log"
echo "▌处理插件: ${plugin_name}"
{
echo "工作目录: $(pwd)/${plugin_name}"
cd "$plugin_name" || { echo "无法进入目录"; exit 1; }
# 检查package.json是否存在
if [ ! -f package.json ]; then
echo "未找到package.json,跳过依赖安装"
exit 0
fi
echo "开始安装依赖..."
pnpm install --frozen-lockfile \
--reporter=append-only \
--prefer-offline 2>&1
echo "依赖安装完成"
} | tee "$log_file" || {
echo "此插件安装失败,已记录日志:$log_file"
continue
}
fi
done
EOF
# 阶段5:重启容器
echo -e "${BLUE}▌阶段5/5 正在重启容器...${RESET}"
docker stop "$DKNAME" && docker rm "$DKNAME"
docker run -d --name "$DKNAME" --network 1panel-network -p 7536-7540:2536-2540 --restart unless-stopped trss:yunzai
# 最终状态检查
echo -e "\n${GREEN}恢复流程已完成!容器状态:${RESET}"
docker ps -f name="$DKNAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
NAPCATqq持久化路径清理脚本
#!/bin/bash
# Napcat的基础目录(可修改)
BASE_DIR="/opt/docker/napcat"
# 要清理的用户目录(可修改,以逗号分隔的列表)
USER_DIRS="napcat357,napcat202,napcat304"
# 清理指定路径的函数
cleanup() {
local base_dir=$1
local user_dir=$2
echo "正在清理用户目录: $user_dir"
# 清理 QQ/NapCat/temp
local temp_dir="$base_dir/$user_dir/QQ/NapCat/temp"
if [ -d "$temp_dir" ]; then
echo "正在删除 $temp_dir 中的内容"
rm -rf "$temp_dir"/*
mkdir -p "$temp_dir"
fi
# 查找所有 nt_qq_* 目录
local qq_dirs=$(find "$base_dir/$user_dir/QQ" -type d -name "nt_qq_*" 2>/dev/null)
for qq_dir in $qq_dirs; do
echo "正在处理QQ目录: $qq_dir"
# 清理数据库文件
local db_dir="$qq_dir/nt_db"
if [ -d "$db_dir" ]; then
echo "正在从 $db_dir 删除数据库文件"
rm -f "$db_dir/nt_msg.db" "$db_dir/group_msg_fts_db"
fi
# 清理数据目录
local data_dir="$qq_dir/nt_data"
if [ -d "$data_dir" ]; then
echo "正在清理 $data_dir 中的目录"
for subdir in Video log Pic File Ptt avatar; do
if [ -d "$data_dir/$subdir" ]; then
echo "正在删除 $data_dir/$subdir 中的内容"
rm -rf "$data_dir/$subdir"/*
mkdir -p "$data_dir/$subdir"
fi
done
fi
done
}
# 主脚本
IFS=',' read -ra USERS <<< "$USER_DIRS"
for user in "${USERS[@]}"; do
cleanup "$BASE_DIR" "$user"
done
echo "清理完成!"