sing-box-subscribe地址
sing-box-subscribe是根据配置模板生成 sing-box 使用的 config.json,主要用于将机场订阅节点添加到 config.json之中。
现在将它改成在docker容器中定时自动运行main.py来生成sing-box的config.json配置文件。
先将项目目录图片和如下内容丢给ai自动生成代码,然后再根据ai生成的代码再自行修改。
我现在的目录结构如图,帮我写一个在 docker 中运行 pyhon 命令的镜像,主要是运行 main.py,把 parsers、config_template 文件夹和 main.py 文件复制到容器里,在对应的映射卷中只有运行 main.py 时自动生成的 config.json 文件。main.py 在运行过程中会要求从终端输入一个数字,这个数字写进容器的环境变量中,main.py 从环境变量中读取。要能在容器的环境变量 CRON_TIME 中指定自动执行 main.py 的时间,如:CRON_TIME=25 20 * * *。所有依赖文件如 parsers 文件夹,config_template 文件夹、main.py、providers.json 全部打包到镜像。帮我做一个镜像。
|
项目文件清单(本地目录结构)
生成singbox-subscribe镜像的文件目录如下:
./项目根目录 ├── Dockerfile ├── entrypoint.sh # 容器启动入口脚本 ├── requirements.txt # Python依赖 ├── main.py # 运行的主程序 ├── providers.json # 内置默认配置打包进镜像 ├── docker-compose.yml # 安装镜像 ├── parsers/ # 完整解析器文件夹 │ ├── anytls.py │ ├── app.py │ ├── tool.py │ ├── clash2base64.py │ ├── http.py │ ├── https.py │ ├── hysteria.py │ ├── hysteria2.py │ ├── socks.py │ ├── ss.py │ ├── ssr.py │ ├── tool.py │ ├── trojan.py │ ├── tuic.py │ ├── vless.py │ ├── vmess.py │ ├── wg.py │ └── gh_proxy_helper.py └── config_template/ # 模板文件夹,内置到镜像 ├── sb-config-1.12.json ├── sb-config-1.14.json └── ...所有json模板
|
requirements.txt(Python 依赖)
根据 main.py 里 import 提取所有依赖:
requests paramiko scp chardet PyYAML ruamel.yaml Flask==2.3.2 cryptography flask flask-login flask-wtf
|
Dockerfile(完整镜像构建文件)
使用 python:slim 轻量基础镜像,内置 cron,打包全部代码、模板、providers.json,挂载/config为持久化卷(仅生成 config.json)
FROM python:3.11-slim
LABEL maintainer="singbox-subscribePro"
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1 ENV TZ=Asia/Shanghai
RUN apt-get update && apt-get install -y --no-install-recommends \ cron tzdata \ && rm -rf /var/lib/apt/lists/* && apt-get clean
WORKDIR /app
COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
COPY main.py . COPY providers.json . COPY parsers ./parsers COPY config_template ./config_template
COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh
VOLUME ["/config"]
ENV TEMPLATE_INDEX=3 ENV CRON_TIME="59 10 * * *"
ENTRYPOINT ["/entrypoint.sh"]
|
docker-compose.yml
cd到singbox-subscribe目录下,然后执行docker compose up -d
services: singbox-subscribe: image: singbox-subscribepro:latest container_name: singbox-subscribepro restart: on-failure environment: - CRON_TIME=59 10 * * * - RUN_ON_START=false - TEMPLATE_INDEX=3 - SUB_URL_1=http://192.168.50.1:8299/download/sub?target=sing-box - SUB_URL_2=https://tv.*****.top:2053/sub volumes: - /etc/localtime:/etc/localtime:ro - ./:/config logging: driver: "json-file" options: max-size: "10M" max-file: "3"
|
logging: 完整代码逐行解释
- driver: “json-file”
Docker 默认日志驱动,容器所有标准输出 / 错误(print、脚本打印、报错)会以 JSON 格式持久化存储在宿主机磁盘。
- 不配置时默认也是
json-file;
- 日志文件会保存在 Docker 数据目录(通常
/var/lib/docker/containers/容器ID/);
- 执行
docker logs 容器名 底层就是读取该 JSON 日志文件。
options.max-size: “10M”
单日志文件最大上限 10 兆
容器持续运行、定时每日打印日志,日志文件会不断膨胀;
当日志文件达到 10MB 时,Docker 自动滚动分割日志,不再往这个文件追加内容。
options.max-file: “3”
最多保留 3 份滚动日志文件
举个例子配合上面 max-size:10M:
初始日志:xxx-json.log(当前正在写入)
填满 10M → 重命名为 xxx-json.log.1,新建空日志继续输出
再次填满 10M → 生成 xxx-json.log.2
第三次填满 10M → 生成 xxx-json.log.3
第四次达到 10M 时:最旧的 .log.1 文件直接自动删除,只维持最多 3 个历史日志文件
整体作用总结
logging: driver: "json-file" options: max-size: "10M" max-file: "3"
|
限制容器日志磁盘占用:最多占用 3 × 10M = 30MB 存储空间,避免长期定时运行(每天 20:25 执行)日志无限堆积撑满服务器硬盘,是生产环境标准优化配置,推荐保留。
Docker 镜像标签(仓库名)强制要求全小写
- 镜像名称只能:小写字母、数字、横线
-、下划线 _、点 .;
- 不能包含任意大写英文字母;
- 镜像名分隔建议用短横线
-,可读性更好。
entrypoint.sh
启动程序入口
#!/bin/bash set -e
ln -sf /usr/share/zoneinfo/Asia/Shanghai echo "Asia/Shanghai" > /etc/timezone
RUN_ON_START_RAW="${RUN_ON_START:-true}" RUN_ON_START=$(echo "$RUN_ON_START_RAW" | tr '[:upper:]' '[:lower:]')
cat > /app/run_task.sh <<'EOF'
cd /app /usr/local/bin/python3 main.py >> /proc/1/fd/1 2>&1 EOF chmod +x /app/run_task.sh
if [ "$RUN_ON_START" = "true" ]; then echo "RUN_ON_START=true,容器启动立即执行转换脚本" /app/run_task.sh fi
rm -f /etc/cron.d/app-task touch /etc/cron.d/app-task if [ -n "${CRON_TIME}" ]; then echo "配置定时任务:${CRON_TIME}" CRON_FULL_CMD="${CRON_TIME} root TEMPLATE_INDEX=${TEMPLATE_INDEX} SUB_URL_1='${SUB_URL_1}' SUB_URL_2='${SUB_URL_2}' /app/run_task.sh" echo "$CRON_FULL_CMD" >> /etc/cron.d/app-task chmod 0644 /etc/cron.d/app-task crontab /etc/cron.d/app-task exec cron -f else echo "未设置CRON_TIME,执行完成后容器退出" fi
|
以上代码的核心问题:cron 进程隔离环境变量
现象拆解
- 容器前台
exec /app/run_task.sh 正常读取 SUB_URL_1/SUB_URL_2/TEMPLATE_INDEX=3;
- cron 定时执行
/app/run_task.sh 时丢失所有自定义环境变量,程序读空,回退 default-url;
- 根本原理:Linux cron 运行在独立极简环境,不会继承容器启动时
-e 注入的环境变量,/app/run_task.sh 内部 export $VAR 拿不到父进程容器变量。
两种彻底解决方案(推荐方案 1)
方案 1:把所有环境变量写入 cron 任务行(一劳永逸,无需改 run_task.sh)
替换完整 entrypoint.sh,cron 命令行前置注入全部变量,cron 执行时自带,不受环境隔离限制:
#!/bin/bash set -e
ln -sf /usr/share/zoneinfo/Asia/Shanghai echo "Asia/Shanghai" > /etc/timezone
RUN_ON_START_RAW="${RUN_ON_START:-true}" RUN_ON_START=$(echo "$RUN_ON_START_RAW" | tr '[:upper:]' '[:lower:]')
cat > /app/run_task.sh <<'EOF'
cd /app /usr/local/bin/python3 main.py >> /proc/1/fd/1 2>&1 EOF chmod +x /app/run_task.sh
if [ "$RUN_ON_START" = "true" ]; then echo "RUN_ON_START=true,容器启动立即执行转换脚本" /app/run_task.sh fi
rm -f /etc/cron.d/app-task touch /etc/cron.d/app-task if [ -n "${CRON_TIME}" ]; then echo "配置定时任务:${CRON_TIME}" CRON_FULL_CMD="${CRON_TIME} root TEMPLATE_INDEX=${TEMPLATE_INDEX} SUB_URLS='${SUB_URLS}' SUB_URL_1='${SUB_URL_1}' SUB_URL_2='${SUB_URL_2}' SUB_URL_3='${SUB_URL_3}' /app/run_task.sh" echo "$CRON_FULL_CMD" >> /etc/cron.d/app-task chmod 0644 /etc/cron.d/app-task crontab /etc/cron.d/app-task exec cron -f else echo "未设置CRON_TIME,执行完成后容器退出" fi
|
关键改动说明
cron 任务不再只写 /app/run_task.sh,而是:
13 10 * * * root TEMPLATE_INDEX=3 SUB_URL_1="xxx" SUB_URL_2="xxx" /app/run_task.sh
|
cron 执行这条命令时,临时定义环境变量传递给脚本,不受 cron 隔离机制影响,和手动执行环境完全一致。
方案 2:全局持久环境文件(备选)
如果订阅链接含特殊引号导致方案 1 解析异常,可在 /etc/environment 写入变量,cron 默认读取该文件:
entrypoint 内增加:
cat > /etc/environment <<ENV TEMPLATE_INDEX="${TEMPLATE_INDEX}" SUB_URLS="${SUB_URLS}" SUB_URL_1="${SUB_URL_1}" SUB_URL_2="${SUB_URL_2}" SUB_URL_3="${SUB_URL_3}" ENV
|
run_task.sh 顶部加载全局环境:
#!/bin/bash
while read line; do export "$line"; done < /etc/environment cd /app /usr/local/bin/python3 main.py >> /proc/1/fd/1 2>&1
|
部署生效步骤
- 替换本地
entrypoint.sh 为方案 1 完整代码
- 重新构建镜像
docker compose down docker rmi singbox-subscribepro:latest docker build -t singbox-subscribepro:latest . docker compose up -d
|
- 校验 cron 任务内容
docker exec -it singbox-subscribepro cat /etc/cron.d/app-task
|
输出应看到变量拼接在命令最前方,示例:
13 10 * * root TEMPLATE_INDEX=3 SUB_URLS='' SUB_URL_1='http://192.168.50.1:8299/download/sub?target=sing-box' SUB_URL_2='https://tv.****.top:2053/sub' SUB_URL_3='' /app/run_task.sh
|
验证方式
手动模拟 cron 完整命令(复制 cat 输出的整行,去掉时间 + root 前缀执行):
docker exec -it singbox-subscribepro bash -c "TEMPLATE_INDEX=3 SUB_URL_1='http://192.168.50.1:8299/download/sub?target=sing-box' SUB_URL_2='https://tv.*****.top:2053/sub' /app/run_task.sh"
|
输出和你手动运行完全一致,不再出现未设置SUB_URL、TEMPLATE_INDEX无效报错。
补充原因总结
容器直接 exec:进程是容器主进程子进程,完整继承 -e 注入环境;
cron 定时任务:属于系统独立后台进程,不继承容器启动时的自定义环境变量,必须在 cron 命令行显式传入变量。
删除 main.py 中的 gh_proxy 代理
因为我是本地执行,不需要代理
一、确认要删除的 4 处 gh_proxy 代码,删除后程序完全正常运行
下面 4 段全部删掉,本地订阅逻辑不受任何影响,不会报错:
1. 顶部导入删除
原代码:
from parsers.gh_proxy_helper import set_gh_proxy
|
直接整行删掉。
2. argparse 参数删除
parser.add_argument('--gh_proxy_index', type=str, help='github加速链接')
|
整行删除。
3. 接收 gh_proxy_index 变量删除
gh_proxy_index = args.gh_proxy_index
|
整行删除。
4. github 加速逻辑整块删除
原完整块:
if hasattr(args, 'gh_proxy_index') and str(args.gh_proxy_index).isdigit(): gh_proxy_index = int(args.gh_proxy_index) print(gh_proxy_index) urls = [item["url"] for item in config["route"]["rule_set"]] new_urls = set_gh_proxy(urls, gh_proxy_index) for item, new_url in zip(config["route"]["rule_set"], new_urls): item["url"] = new_url
|
全部删掉。
二、删除后对应修改片段展示(对照修改,避免改错)
修改后头部导入区
import json, os, time, requests, sys, importlib, argparse, yaml, ruamel import re from datetime import datetime from urllib.parse import urlparse from collections import OrderedDict
from parsers.app import TEMP_DIR from parsers import tool from parsers.clash2base64 import clash2v2ray
|
修改后 argparse 区域
parser = argparse.ArgumentParser() parser.add_argument('--temp_json_data', type=parse_json, help='临时内容') parser.add_argument('--template_index', type=int, help='模板序号')
args = parser.parse_args() temp_json_data = args.temp_json_data
|
修改后主逻辑(删除加速块)
nodes = process_subscribes(providers["subscribes"])
if providers.get('Only-nodes'): combined_contents = [] for sub_tag, contents in nodes.items(): for content in contents: combined_contents.append(content) final_config = combined_contents else: final_config = combin_to_config(config, nodes)
|
三、为什么删除后程序 100% 正常?
- gh_proxy 仅作用于github 规则集链接加速,你本地内网订阅(192.168、tv.weito)完全不依赖这段代码;
- 删除后不会调用
set_gh_proxy函数,不会出现导入缺失报错;
--gh_proxy_index启动参数彻底移除,无多余参数干扰;
- 订阅拉取、节点解析、分流分组、定时、TEMPLATE_INDEX 全部原有逻辑保留;
- 不会影响
providers.json、config 模板、/config 配置保存逻辑。
四、修改后验证步骤
- 保存修改后的 main.py
- 重建镜像
docker compose down docker rmi singbox-subscribepro:latest docker build -t singbox-subscribepro:latest . docker compose up -d
|
- 手动执行测试
docker exec -it singbox-subscribepro /app/run_task.sh
|
输出正常加载 SUB_URL、zy_base 模板、正常生成 config.json,无任何报错即为修改成功。
五、额外清理(可选)
parsers 文件夹里的gh_proxy_helper.py文件可以直接删除,不再使用,不删也不影响运行。