Files
vps/install/23-Sub2API.sh
T
eddy 9438f4340a fix: 强化 Sub2API 安装与更新流程
- 在修改安装文件前先校验 Docker 与 Compose 是否可用
- 让“不安装”选项直接干净退出,而不是触发更新
- 复用已检测到的 Compose 命令执行安装和更新操作
- 在无 apt 的系统上跳过系统包更新
- 限制自动更新日志增长,并保留日志文件 inode
2026-07-05 05:13:14 +08:00

293 lines
11 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# chmod +x 23-Sub2API.sh && ./23-Sub2API.sh
# curl -sS -O https://gitea.tohub.top/Share/vps/raw/branch/main/install/23-Sub2API.sh && chmod +x 23-Sub2API.sh && ./23-Sub2API.sh
set -euo pipefail
port80=8230
# 安装目录与更新脚本路径
install_dir=/root/data/docker_data/Sub2API
update_script="$install_dir/auto-update.sh"
env_file="$install_dir/.env"
update_log="$install_dir/auto-update.log"
# 检测 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
# 交互:已有安装时默认只更新,避免覆盖数据库账号、密码和连接配置
existing_install=0
if [ -f "$install_dir/docker-compose.yml" ]; then
existing_install=1
fi
echo "请选择操作:"
if [ "$existing_install" -eq 1 ]; then
echo " 1) 已安装,仅更新 Sub2API app 镜像(默认,安全,不覆盖配置/数据库)"
echo " 2) 强制重装 Sub2API(重写 docker-compose.yml,保留 .env 与数据目录)"
else
echo " 1) 安装 Sub2API(默认)"
echo " 2) 不安装,退出"
fi
read -r -p "请输入选项 [1/2](默认 1):" action_choice
action_choice="${action_choice:-1}"
# 严格校验输入,防止误输入落入重装流程
if [ "$action_choice" != "1" ] && [ "$action_choice" != "2" ]; then
echo "无效选项:$action_choice,已退出。未做任何修改。" >&2
exit 1
fi
if [ "$existing_install" -eq 0 ] && [ "$action_choice" = "2" ]; then
echo "已选择不安装,退出。"
exit 0
fi
if [ "$existing_install" -eq 1 ] && [ "$action_choice" = "1" ]; then
echo "已选择:立即执行 Docker 更新。"
if [ -x "$update_script" ]; then
# 优先用已生成的 auto-update.sh(同样只更新 app,不动 db / redis
"$update_script"
else
cd "$install_dir" || { echo "无法进入安装目录 $install_dir" >&2; exit 1; }
# 回退方案:直接用 compose 只更新 app 服务(Sub2API 镜像),不动 db / redis
"${compose_cmd[@]}" pull app
"${compose_cmd[@]}" up -d app
docker image prune -f
fi
echo "Docker 更新完成。"
exit 0
fi
if [ "$existing_install" -eq 1 ] && [ "$action_choice" = "2" ]; then
echo "检测到已有 Sub2API 安装:$install_dir/docker-compose.yml"
echo "强制重装会重写 docker-compose.yml.env 与 data/ 数据目录会保留,原管理员账号密码继续有效)。"
read -r -p "如确需重装,请输入 REINSTALL 确认:" reinstall_confirm
if [ "$reinstall_confirm" != "REINSTALL" ]; then
echo "未确认强制重装,已退出。原有配置未修改。"
exit 0
fi
fi
# 1、更新包(更新失败不阻断后续安装;非 Debian 系无 apt 则跳过)
if command -v apt >/dev/null 2>&1; then
export DEBIAN_FRONTEND=noninteractive
apt update -y || true
apt upgrade -y || true #更新一下包
else
echo "未检测到 apt,跳过系统包更新。"
fi
# 2、创建安装目录
mkdir -p "$install_dir"
cd "$install_dir" || { echo "无法进入安装目录 $install_dir" >&2; exit 1; }
# 3、生成 .env(仅首次生成,重装/更新不覆盖,避免密钥和数据库密码变化)
gen_secret() {
openssl rand -hex 32 2>/dev/null || head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n'
}
if [ ! -f "$env_file" ]; then
# 兼容旧版安装:尝试从旧 docker-compose.yml 中提取已在用的数据库密码,
# 避免生成新密码后与已有 postgres 数据目录不一致导致连不上库
old_pg_password=""
old_pg_user=""
if [ -f docker-compose.yml ]; then
# grep -v '\$' 排除新版 compose 中的 ${VAR} 占位符,只取旧版明文值
old_pg_password=$(sed -n 's/.*POSTGRES_PASSWORD=\([^ #]*\).*/\1/p' docker-compose.yml | grep -v '\$' | head -n1 || true)
old_pg_user=$(sed -n 's/.*POSTGRES_USER=\([^ #]*\).*/\1/p' docker-compose.yml | grep -v '\$' | head -n1 || true)
fi
pg_password="${old_pg_password:-$(gen_secret)}"
pg_user="${old_pg_user:-postgres}"
# 交互:设置管理员账号密码(仅数据库首次初始化时生效;
# 如果数据库已有数据,登录仍用数据库里已存在的账号密码)
default_admin_email="admin@sub2api.local"
default_admin_password=$(gen_secret | cut -c1-16)
echo "配置管理员账户(仅数据库首次初始化时创建):"
read -r -p "管理员邮箱(默认 $default_admin_email):" admin_email
admin_email="${admin_email:-$default_admin_email}"
read -r -p "管理员密码(回车使用随机密码 $default_admin_password):" admin_password
admin_password="${admin_password:-$default_admin_password}"
cat <<EOF > "$env_file"
# Sub2API 环境配置(由安装脚本生成,请勿删除,否则需重新配置)
# 数据库
POSTGRES_USER=$pg_user
POSTGRES_PASSWORD=$pg_password
POSTGRES_DB=sub2api
# 固定密钥:保证容器重建/更新后登录态和 2FA 不失效
JWT_SECRET=$(gen_secret)
TOTP_ENCRYPTION_KEY=$(gen_secret)
# 管理员账户(仅首次启动时创建,之后修改无效)
ADMIN_EMAIL=$admin_email
ADMIN_PASSWORD=$admin_password
TZ=Asia/Shanghai
EOF
chmod 600 "$env_file"
echo "已生成 $env_file(包含数据库密码、密钥和管理员初始密码,请妥善保管)。"
else
echo "检测到已有 $env_file,沿用原有管理员账号密码与数据库配置,不再询问。"
fi
# 4、填写docker-compose配置
# AUTO_SETUP=true + 环境变量传入数据库/Redis 配置,容器重建后无需再走网页安装向导
cat <<EOF > docker-compose.yml
services:
db:
image: postgres:15-alpine
container_name: sub2api-db
restart: unless-stopped
volumes:
- ./data/postgres:/var/lib/postgresql/data
environment:
- POSTGRES_USER=\${POSTGRES_USER:-postgres}
- POSTGRES_PASSWORD=\${POSTGRES_PASSWORD:?POSTGRES_PASSWORD 未配置,请检查 .env}
- POSTGRES_DB=\${POSTGRES_DB:-sub2api}
- TZ=\${TZ:-Asia/Shanghai}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER:-postgres} -d \${POSTGRES_DB:-sub2api}"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: sub2api-redis
restart: unless-stopped
volumes:
- ./data/redis:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
app:
image: weishaw/sub2api:latest
container_name: sub2api-app
restart: unless-stopped
volumes:
- ./data/app:/app/data # 持久化 app 配置/数据
ports:
- "$port80:8080" # 左边的端口可以更换,右边不要动!
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
# 自动初始化:跳过网页安装向导,配置全部来自环境变量(.env)
- AUTO_SETUP=true
- SERVER_HOST=0.0.0.0
- SERVER_PORT=8080 # 必须与 ports 右边的容器端口一致
- SERVER_MODE=release
# 主机用容器服务名 db / redis,不要写宿主机 IP
- DATABASE_HOST=db
- DATABASE_PORT=5432
- DATABASE_USER=\${POSTGRES_USER:-postgres}
- DATABASE_PASSWORD=\${POSTGRES_PASSWORD}
- DATABASE_DBNAME=\${POSTGRES_DB:-sub2api}
- DATABASE_SSLMODE=disable
- REDIS_HOST=redis
- REDIS_PORT=6379
# 固定密钥(来自 .env):更新/重启后登录态和 2FA 不失效
- JWT_SECRET=\${JWT_SECRET}
- TOTP_ENCRYPTION_KEY=\${TOTP_ENCRYPTION_KEY}
# 管理员账户仅首次启动时创建
- ADMIN_EMAIL=\${ADMIN_EMAIL:-admin@sub2api.local}
- ADMIN_PASSWORD=\${ADMIN_PASSWORD:-}
- TZ=\${TZ:-Asia/Shanghai}
EOF
# 5、安装(docker 与 compose 已在脚本开头检查过)
"${compose_cmd[@]}" up -d
# 6、打开防火墙的端口
if command -v ufw >/dev/null 2>&1; then
ufw allow "$port80"
ufw status
else
echo "未检测到 ufw,跳过防火墙端口放行。"
fi
# 7、配置每日自动更新
cat <<'EOF' > "$update_script"
#!/bin/bash
set -euo pipefail
install_dir=/root/data/docker_data/Sub2API
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
# 只更新 app 服务(Sub2API 镜像),不动 db / redis
"${compose_cmd[@]}" pull app
"${compose_cmd[@]}" up -d app
# 清理旧镜像,释放磁盘空间
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:00 自动更新任务
cron_line="0 4 * * * $update_script >> $update_log 2>&1"
if command -v crontab >/dev/null 2>&1; then
# 使用脚本路径去重,避免重复写入 Sub2API 自动更新任务
( crontab -l 2>/dev/null | grep -v -F -- "$update_script" || true ; echo "$cron_line" ) | crontab -
echo "已配置定时自动更新任务:每天 04:00 自动更新 Sub2API app 镜像(不更新 db / redis)。"
else
echo "未检测到 crontab,跳过定时更新任务配置。可手动执行 $update_script 更新。"
fi
# 打印访问链接
echo "------------------------"
echo "访问链接:"
echo "https://sub2api.ghuang.top"
echo "管理员账户(首次安装时自动创建,凭证保存在 $env_file):"
grep -E '^(ADMIN_EMAIL|ADMIN_PASSWORD)=' "$env_file" || true
echo "------------------------"
echo "已启用 AUTO_SETUP 自动初始化:更新/重建容器后无需重新走网页安装向导。"
echo "已开启定时自动更新:每天 04:00 仅拉取最新 app 镜像并重启(db / redis 不动)。"
echo "手动更新可执行:$update_script"
echo "------------------------"