9438f4340a
- 在修改安装文件前先校验 Docker 与 Compose 是否可用 - 让“不安装”选项直接干净退出,而不是触发更新 - 复用已检测到的 Compose 命令执行安装和更新操作 - 在无 apt 的系统上跳过系统包更新 - 限制自动更新日志增长,并保留日志文件 inode
293 lines
11 KiB
Bash
293 lines
11 KiB
Bash
#!/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 "------------------------"
|