feat: 完善 NewApi 安装脚本,添加自动更新功能及前置检查
This commit is contained in:
+153
-91
@@ -6,6 +6,11 @@ set -euo pipefail
|
|||||||
|
|
||||||
port80=8200
|
port80=8200
|
||||||
|
|
||||||
|
# 安装目录与更新脚本路径
|
||||||
|
install_dir=/root/data/docker_data/NewApi
|
||||||
|
update_script="$install_dir/auto-update.sh"
|
||||||
|
update_log="$install_dir/auto-update.log"
|
||||||
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
echo "请使用 root 用户运行本脚本。" >&2
|
echo "请使用 root 用户运行本脚本。" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -18,9 +23,137 @@ for required_cmd in openssl curl grep sed apt; do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# 选择安装目录
|
# 检测 compose 命令,结果存入全局数组 compose_cmd;检测失败返回 1
|
||||||
|
detect_compose() {
|
||||||
|
if docker compose version >/dev/null 2>&1; then
|
||||||
|
compose_cmd=(docker compose)
|
||||||
|
elif command -v docker-compose >/dev/null 2>&1; then
|
||||||
|
compose_cmd=(docker-compose)
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 前置检查:docker 与 compose 必须先就绪,安装与更新分支都依赖它们
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
echo "未检测到 docker,请先安装 Docker 后再运行本脚本。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if ! detect_compose; then
|
||||||
|
echo "未检测到 docker compose 或 docker-compose,请先安装 Docker Compose。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 生成 auto-update.sh 并注册 crontab 定时任务(幂等,可无条件重复执行)。
|
||||||
|
# 安装分支和"仅执行 Docker 更新"分支都调用本函数:每次调用都重建更新脚本
|
||||||
|
# 并刷新 cron 条目,无论脚本被删、内容过时还是 cron 丢失,重跑本脚本即可自愈。
|
||||||
|
setup_auto_update() {
|
||||||
|
mkdir -p "$install_dir"
|
||||||
|
|
||||||
|
cat <<'EOF' > "$update_script"
|
||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
install_dir=/root/data/docker_data/NewApi
|
install_dir=/root/data/docker_data/NewApi
|
||||||
update_script="$install_dir/auto-update.sh"
|
archive_dir=/root/data/docker_data/NewApi.archive
|
||||||
|
image_name=calciumion/new-api:latest
|
||||||
|
|
||||||
|
# 统一带时间戳输出,便于从 auto-update.log 区分每次运行、定位问题
|
||||||
|
log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 备份数据:先写入临时目录,成功后再替换旧备份;任何一步失败都返回非零
|
||||||
|
backup_data() {
|
||||||
|
local tmp_archive_dir="${archive_dir}.tmp"
|
||||||
|
local rsync_rc=0
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$archive_dir")" || return 1
|
||||||
|
rm -rf "$tmp_archive_dir" || return 1
|
||||||
|
mkdir -p "$tmp_archive_dir" || return 1
|
||||||
|
|
||||||
|
if command -v rsync >/dev/null 2>&1; then
|
||||||
|
rsync -a --delete --exclude 'auto-update.log' --exclude 'logs/' "$install_dir"/ "$tmp_archive_dir"/ || rsync_rc=$?
|
||||||
|
# 退出码 24 表示复制期间部分源文件消失(正在运行的 sqlite 会产生转瞬即逝的
|
||||||
|
# WAL 临时文件),备份内容仍然可用,视为可接受
|
||||||
|
if [ "$rsync_rc" -ne 0 ] && [ "$rsync_rc" -ne 24 ]; then
|
||||||
|
return "$rsync_rc"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
cp -a "$install_dir"/. "$tmp_archive_dir"/ || return 1
|
||||||
|
rm -f "$tmp_archive_dir"/auto-update.log
|
||||||
|
rm -rf "$tmp_archive_dir"/logs
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$archive_dir" || return 1
|
||||||
|
mv "$tmp_archive_dir" "$archive_dir" || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
log "开始执行 NewApi 自动更新"
|
||||||
|
|
||||||
|
# 备份尽力而为:失败只告警不阻断,避免备份环节的问题导致镜像更新被跳过
|
||||||
|
backup_rc=0
|
||||||
|
backup_data || backup_rc=$?
|
||||||
|
if [ "$backup_rc" -eq 0 ]; then
|
||||||
|
log "数据备份完成:$archive_dir"
|
||||||
|
else
|
||||||
|
log "警告:数据备份失败(退出码 $backup_rc),本次跳过备份,继续执行镜像更新"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 进入 docker-compose 所在的文件夹
|
||||||
|
cd "$install_dir" || { log "无法进入安装目录 $install_dir"; exit 1; }
|
||||||
|
|
||||||
|
if docker compose version >/dev/null 2>&1; then
|
||||||
|
compose_cmd=(docker compose)
|
||||||
|
elif command -v docker-compose >/dev/null 2>&1; then
|
||||||
|
compose_cmd=(docker-compose)
|
||||||
|
else
|
||||||
|
log "未检测到 docker compose 或 docker-compose,无法自动更新。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 记录更新前后的镜像 ID,便于从日志一眼区分"没跑 / 拉取失败 / 本来就没有新版本"
|
||||||
|
old_image_id=$(docker image inspect --format '{{.Id}}' "$image_name" 2>/dev/null || echo "本地暂无该镜像")
|
||||||
|
log "更新前镜像 ID:$old_image_id"
|
||||||
|
|
||||||
|
"${compose_cmd[@]}" pull
|
||||||
|
"${compose_cmd[@]}" up -d
|
||||||
|
|
||||||
|
new_image_id=$(docker image inspect --format '{{.Id}}' "$image_name" 2>/dev/null || echo "本地暂无该镜像")
|
||||||
|
log "更新后镜像 ID:$new_image_id"
|
||||||
|
if [ "$new_image_id" != "$old_image_id" ]; then
|
||||||
|
log "镜像已更新并重启"
|
||||||
|
else
|
||||||
|
log "镜像无更新"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 清理旧镜像,释放磁盘空间
|
||||||
|
docker image prune -f
|
||||||
|
|
||||||
|
# 修剪日志避免无限增长(只保留最近 1000 行)
|
||||||
|
# 用 cat 覆盖写回而非 mv,保留原 inode,避免 cron 持有的 append 文件描述符失效
|
||||||
|
log_file="$install_dir/auto-update.log"
|
||||||
|
if [ -f "$log_file" ]; then
|
||||||
|
tail -n 1000 "$log_file" > "$log_file.tmp" && cat "$log_file.tmp" > "$log_file" && rm -f "$log_file.tmp"
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
|
||||||
|
chmod +x "$update_script"
|
||||||
|
|
||||||
|
# 写入每日 04:20 自动更新任务。
|
||||||
|
# 特意与 23-Sub2API.sh 的 04:00 任务错峰:两者都会执行全局的 docker image prune -f,
|
||||||
|
# 同时运行有竞态风险,且拉取镜像的带宽 / 磁盘 IO 会叠加。
|
||||||
|
cron_line="20 4 * * * $update_script >> $update_log 2>&1"
|
||||||
|
if command -v crontab >/dev/null 2>&1; then
|
||||||
|
# 用 NewApi 更新脚本的完整路径过滤旧条目(含老机器遗留的 04:00 条目),
|
||||||
|
# 幂等替换,且不会误删 crontab 中的其他任务(如 Sub2API 的 auto-update.sh)
|
||||||
|
( crontab -l 2>/dev/null | grep -v -F -- "$update_script" || true ; echo "$cron_line" ) | crontab -
|
||||||
|
echo "已配置定时自动更新任务:每天 04:20 自动更新 NewApi。"
|
||||||
|
else
|
||||||
|
echo "未检测到 crontab,跳过定时更新任务配置。可手动执行 $update_script 更新。"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# 交互:已有安装时默认只更新,避免覆盖数据库路径、账号数据和密钥
|
# 交互:已有安装时默认只更新,避免覆盖数据库路径、账号数据和密钥
|
||||||
existing_install=0
|
existing_install=0
|
||||||
@@ -39,27 +172,23 @@ fi
|
|||||||
read -r -p "请输入选项 [1/2](默认 1):" action_choice
|
read -r -p "请输入选项 [1/2](默认 1):" action_choice
|
||||||
action_choice="${action_choice:-1}"
|
action_choice="${action_choice:-1}"
|
||||||
|
|
||||||
|
# 严格校验输入,防止误输入落入安装/重装流程
|
||||||
|
if [ "$action_choice" != "1" ] && [ "$action_choice" != "2" ]; then
|
||||||
|
echo "无效选项:$action_choice,已退出。未做任何修改。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if { [ "$existing_install" -eq 1 ] && [ "$action_choice" = "1" ]; } || { [ "$existing_install" -eq 0 ] && [ "$action_choice" = "2" ]; }; then
|
if { [ "$existing_install" -eq 1 ] && [ "$action_choice" = "1" ]; } || { [ "$existing_install" -eq 0 ] && [ "$action_choice" = "2" ]; }; then
|
||||||
echo "已选择:立即执行 Docker 更新。"
|
echo "已选择:立即执行 Docker 更新。"
|
||||||
if [ -x "$update_script" ]; then
|
if [ ! -f "$install_dir/docker-compose.yml" ]; then
|
||||||
"$update_script"
|
|
||||||
elif [ -f "$install_dir/docker-compose.yml" ]; then
|
|
||||||
cd "$install_dir" || { echo "无法进入安装目录 $install_dir" >&2; exit 1; }
|
|
||||||
if docker compose version >/dev/null 2>&1; then
|
|
||||||
compose_cmd=(docker compose)
|
|
||||||
elif command -v docker-compose >/dev/null 2>&1; then
|
|
||||||
compose_cmd=(docker-compose)
|
|
||||||
else
|
|
||||||
echo "未检测到 docker compose 或 docker-compose,无法执行更新。" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
"${compose_cmd[@]}" pull
|
|
||||||
"${compose_cmd[@]}" up -d
|
|
||||||
docker image prune -f
|
|
||||||
else
|
|
||||||
echo "未检测到已安装的 NewApi(缺少 $install_dir/docker-compose.yml),请先安装。" >&2
|
echo "未检测到已安装的 NewApi(缺少 $install_dir/docker-compose.yml),请先安装。" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
# 先无条件重建 auto-update.sh 并刷新 cron 条目,修复自动更新机制:
|
||||||
|
# 脚本被删、内容过时、cron 丢失等任何损坏,重跑本分支即可自愈
|
||||||
|
setup_auto_update
|
||||||
|
# 再用刚重建的脚本完成本次更新
|
||||||
|
"$update_script"
|
||||||
echo "Docker 更新完成。"
|
echo "Docker 更新完成。"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
@@ -91,8 +220,8 @@ apt update -y || true
|
|||||||
apt upgrade -y || true
|
apt upgrade -y || true
|
||||||
|
|
||||||
# 2、创建安装目录
|
# 2、创建安装目录
|
||||||
mkdir -p /root/data/docker_data/NewApi
|
mkdir -p "$install_dir"
|
||||||
cd /root/data/docker_data/NewApi || { echo "无法进入安装目录 /root/data/docker_data/NewApi" >&2; exit 1; }
|
cd "$install_dir" || { echo "无法进入安装目录 $install_dir" >&2; exit 1; }
|
||||||
|
|
||||||
# 3、填写 docker-compose 配置
|
# 3、填写 docker-compose 配置
|
||||||
# 先写入占位符,再替换为实际配置
|
# 先写入占位符,再替换为实际配置
|
||||||
@@ -146,21 +275,7 @@ sed -i \
|
|||||||
-e "s|__SESSION_SECRET__|$session_secret|g" \
|
-e "s|__SESSION_SECRET__|$session_secret|g" \
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
|
|
||||||
# 4、安装
|
# 4、安装(docker 与 compose 已在脚本开头检查过)
|
||||||
if ! command -v docker >/dev/null 2>&1; then
|
|
||||||
echo "未检测到 docker,请先安装 Docker 后再运行本脚本。" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if docker compose version >/dev/null 2>&1; then
|
|
||||||
compose_cmd=(docker compose)
|
|
||||||
elif command -v docker-compose >/dev/null 2>&1; then
|
|
||||||
compose_cmd=(docker-compose)
|
|
||||||
else
|
|
||||||
echo "未检测到 docker compose 或 docker-compose,请先安装 Docker Compose。" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
"${compose_cmd[@]}" up -d
|
"${compose_cmd[@]}" up -d
|
||||||
|
|
||||||
# 5、打开防火墙的端口
|
# 5、打开防火墙的端口
|
||||||
@@ -171,61 +286,8 @@ else
|
|||||||
echo "未检测到 ufw,跳过防火墙端口放行。"
|
echo "未检测到 ufw,跳过防火墙端口放行。"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 6、配置每日自动更新
|
# 6、配置每日自动更新(生成 auto-update.sh 并写入 crontab)
|
||||||
cat <<'EOF' > "$update_script"
|
setup_auto_update
|
||||||
#!/bin/bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
install_dir=/root/data/docker_data/NewApi
|
|
||||||
archive_dir=/root/data/docker_data/NewApi.archive
|
|
||||||
|
|
||||||
# 备份数据,先写入临时目录,成功后再替换旧备份
|
|
||||||
mkdir -p "$(dirname "$archive_dir")"
|
|
||||||
tmp_archive_dir="${archive_dir}.tmp"
|
|
||||||
|
|
||||||
rm -rf "$tmp_archive_dir"
|
|
||||||
mkdir -p "$tmp_archive_dir"
|
|
||||||
|
|
||||||
if command -v rsync >/dev/null 2>&1; then
|
|
||||||
rsync -a --delete --exclude 'auto-update.log' --exclude 'logs/' "$install_dir"/ "$tmp_archive_dir"/
|
|
||||||
else
|
|
||||||
cp -a "$install_dir"/. "$tmp_archive_dir"/
|
|
||||||
rm -f "$tmp_archive_dir"/auto-update.log
|
|
||||||
rm -rf "$tmp_archive_dir"/logs
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf "$archive_dir"
|
|
||||||
mv "$tmp_archive_dir" "$archive_dir"
|
|
||||||
|
|
||||||
cd "$install_dir" # 进入 docker-compose 所在的文件夹
|
|
||||||
|
|
||||||
if docker compose version >/dev/null 2>&1; then
|
|
||||||
compose_cmd=(docker compose)
|
|
||||||
elif command -v docker-compose >/dev/null 2>&1; then
|
|
||||||
compose_cmd=(docker-compose)
|
|
||||||
else
|
|
||||||
echo "未检测到 docker compose 或 docker-compose,无法自动更新。" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
"${compose_cmd[@]}" pull
|
|
||||||
"${compose_cmd[@]}" up -d
|
|
||||||
|
|
||||||
# 清理旧镜像,释放磁盘空间
|
|
||||||
docker image prune -f
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod +x "$update_script"
|
|
||||||
|
|
||||||
# 写入每日 04:00 自动更新任务
|
|
||||||
cron_line="0 4 * * * $update_script >> /root/data/docker_data/NewApi/auto-update.log 2>&1"
|
|
||||||
if command -v crontab >/dev/null 2>&1; then
|
|
||||||
# 使用脚本路径去重,避免重复写入 NewApi 自动更新任务
|
|
||||||
( crontab -l 2>/dev/null | grep -v -F -- "$update_script" || true ; echo "$cron_line" ) | crontab -
|
|
||||||
echo "已配置定时自动更新任务:每天 04:00 自动更新 NewApi。"
|
|
||||||
else
|
|
||||||
echo "未检测到 crontab,跳过定时更新任务配置。可手动执行 $update_script 更新。"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 打印访问链接
|
# 打印访问链接
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
@@ -234,6 +296,6 @@ echo "http://$ipv4_address:$port80"
|
|||||||
echo "User: root"
|
echo "User: root"
|
||||||
echo "默认密码: 123456 —— 该端口已对公网开放,请【立即】登录并修改密码!"
|
echo "默认密码: 123456 —— 该端口已对公网开放,请【立即】登录并修改密码!"
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
echo "已开启定时自动更新:每天 04:00 自动备份并拉取最新镜像。"
|
echo "已开启定时自动更新:每天 04:20 自动备份并拉取最新镜像(与 Sub2API 的 04:00 任务错峰)。"
|
||||||
echo "手动更新可执行:$update_script"
|
echo "手动更新可执行:$update_script"
|
||||||
echo "------------------------"
|
echo "------------------------"
|
||||||
|
|||||||
Reference in New Issue
Block a user