From 47c0ebff868a01adc14fe1c91f77a5b0016eff8d Mon Sep 17 00:00:00 2001 From: eddy Date: Sat, 18 Jul 2026 03:16:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=AC=AC=E4=BA=8C?= =?UTF-8?q?=E4=B8=AA=20Sub2API=20=E5=AE=9E=E4=BE=8B=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在同一台服务器上部署与现有安装相互独立的 Sub2API 实例 - 使用独立的容器、安装目录、持久化数据和访问端口 - 在更新或重装时保留账号凭证及应用数据 - 配置仅更新应用镜像的安全流程和每日自动更新任务 --- install/23-2-Sub2API.sh | 292 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 install/23-2-Sub2API.sh diff --git a/install/23-2-Sub2API.sh b/install/23-2-Sub2API.sh new file mode 100644 index 0000000..5fe2aad --- /dev/null +++ b/install/23-2-Sub2API.sh @@ -0,0 +1,292 @@ +#!/bin/bash +# chmod +x 23-2-Sub2API.sh && ./23-2-Sub2API.sh +# curl -sS -O https://gitea.tohub.top/Share/vps/raw/branch/main/install/23-2-Sub2API.sh && chmod +x 23-2-Sub2API.sh && ./23-2-Sub2API.sh + +set -euo pipefail + +port80=8231 + +# 安装目录与更新脚本路径 +install_dir=/root/data/docker_data/Sub2API-2 +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-2.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 < "$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 < docker-compose.yml +services: + db: + image: postgres:15-alpine + container_name: sub2api-2-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-2-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-2-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-2.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-2 + +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:10 自动更新任务 +cron_line="10 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:10 自动更新 Sub2API app 镜像(不更新 db / redis)。" +else + echo "未检测到 crontab,跳过定时更新任务配置。可手动执行 $update_script 更新。" +fi + +# 打印访问链接 +echo "------------------------" +echo "访问链接:" +echo "http://服务器IP:$port80" +echo "管理员账户(首次安装时自动创建,凭证保存在 $env_file):" +grep -E '^(ADMIN_EMAIL|ADMIN_PASSWORD)=' "$env_file" || true +echo "------------------------" +echo "已启用 AUTO_SETUP 自动初始化:更新/重建容器后无需重新走网页安装向导。" +echo "已开启定时自动更新:每天 04:10 仅拉取最新 app 镜像并重启(db / redis 不动)。" +echo "手动更新可执行:$update_script" +echo "------------------------"