""" GitHub / 构建相关站点配置的统一读取层。 取值优先级: 1. 后台「GitHub 构建配置」页保存的值(SiteSetting 表) 2. 环境变量(docker-compose / 系统环境,历史部署方式,继续支持) 3. 内置默认值 进程内带 15 秒短缓存,避免每次调用都查库;后台保存后立即失效当前进程缓存, 其他 gunicorn worker 最迟 15 秒后生效。 """ import os import time # 每项:key=配置键 / env=对应环境变量名 / default=内置默认值 / label/help/组 供后台页渲染 SETTING_DEFS = [ { 'key': 'GHUSER', 'env': 'GHUSER', 'default': '', 'label': 'GitHub 用户名', 'group': 'basic', 'sensitive': False, 'help': 'Fork 本项目仓库所在的 GitHub 账号用户名(或组织名)。', }, { 'key': 'REPONAME', 'env': 'REPONAME', 'default': 'rdgen', 'label': '仓库名称', 'group': 'basic', 'sensitive': False, 'help': 'Fork 后的仓库名,默认为 rdgen;若改名请如实填写。', }, { 'key': 'GHBRANCH', 'env': 'GHBRANCH', 'default': 'master', 'label': '工作流分支', 'group': 'basic', 'sensitive': False, 'help': '触发 GitHub Actions 时使用的分支名,通常保持 master。', }, { 'key': 'GHBEARER', 'env': 'GHBEARER', 'default': '', 'label': 'GitHub 访问令牌(Fine-grained PAT)', 'group': 'basic', 'sensitive': True, 'help': '需对该仓库授予 Actions 与 Workflows 的读写权限。页面仅显示保存状态,不会回显明文。', }, { 'key': 'GENURL', 'env': 'GENURL', 'default': '', 'label': '本平台外部访问地址', 'group': 'callback', 'sensitive': False, 'help': '运行 Actions 的构建机可从网络访问到的本平台地址,例如 https://rdgen.example.com,用于回传构建产物。', }, { 'key': 'PROTOCOL', 'env': 'PROTOCOL', 'default': 'https', 'label': '平台访问协议', 'group': 'callback', 'sensitive': False, 'choices': [('https', 'https'), ('http', 'http')], 'help': '经 Nginx/Caddy 等 HTTPS 反向代理时保持 https;本地纯 HTTP 部署选 http。', }, { 'key': 'ZIP_PASSWORD', 'env': 'ZIP_PASSWORD', 'default': 'insecure', 'label': '配置压缩包密码', 'group': 'callback', 'sensitive': True, 'help': '本平台用它加密传给 Actions 的配置包,必须与构建仓库 Secret 中的 ZIP_PASSWORD 完全一致(GitHub 与 Gitea 仓库均需配置)。默认值不安全,建议更换。', }, { 'key': 'SH_SECRET', 'env': 'SH_SECRET', 'default': 'secret', 'label': '自托管 Runner 密钥', 'group': 'advanced', 'sensitive': True, 'help': '仅在使用自托管 GitHub Runner(rdgen-cli 的 sh_secret_field)时需要,默认值不安全。', }, { 'key': 'WEBHOOK_SECRET', 'env': 'WEBHOOK_SECRET', 'default': '', 'label': '回调校验密钥', 'group': 'advanced', 'sensitive': True, 'help': '可选。配置后构建平台(GitHub / Gitea)Actions 回调必须携带 X-Webhook-Secret 请求头;留空则不校验(兼容旧工作流)。', }, { 'key': 'GH_PROXY', 'env': 'GH_PROXY', 'default': '', 'label': 'GitHub 访问代理', 'group': 'network', 'sensitive': True, 'help': ( '本服务器直连 GitHub 失败时填写,例如 http://127.0.0.1:7890;' '支持 socks5://(需 PySocks)。可包含账密,如 http://user:pass@host:port。' '留空则直连(仍遵循容器的 HTTPS_PROXY/HTTP_PROXY 环境变量)。' ), }, # ===== 构建平台选择(GitHub / Gitea 二选一) ===== { 'key': 'BUILD_PLATFORM', 'env': 'BUILD_PLATFORM', 'default': 'github', 'label': '构建平台', 'group': 'platform', 'sensitive': False, 'choices': [('github', 'GitHub Actions'), ('gitea', 'Gitea Actions(自建)')], 'help': ( '选择实际执行构建任务的平台。切换后仅影响新提交的任务,' '历史任务仍在原平台查询状态与日志。Gitea 需自建实例并自备构建机(Linux/Windows/macOS)。' ), }, # ===== Gitea 连接(BUILD_PLATFORM=gitea 时生效) ===== { 'key': 'GITEA_SERVER_URL', 'env': 'GITEA_SERVER_URL', 'default': '', 'label': 'Gitea 服务器地址', 'group': 'gitea', 'sensitive': False, 'help': '自建 Gitea 的访问地址,例如 https://gitea.example.com(不带末尾斜杠);建议版本不低于 1.27。', }, { 'key': 'GITEA_OWNER', 'env': 'GITEA_OWNER', 'default': '', 'label': 'Gitea 仓库属主', 'group': 'gitea', 'sensitive': False, 'help': '存放构建仓库的 Gitea 用户名或组织名。', }, { 'key': 'GITEA_REPO', 'env': 'GITEA_REPO', 'default': 'rdgen', 'label': 'Gitea 仓库名称', 'group': 'gitea', 'sensitive': False, 'help': 'Gitea 上的构建仓库名,默认 rdgen;仓库需在设置中启用 Repository Actions。', }, { 'key': 'GITEA_BRANCH', 'env': 'GITEA_BRANCH', 'default': 'master', 'label': 'Gitea 工作流分支', 'group': 'gitea', 'sensitive': False, 'help': '触发 Gitea Actions 时使用的分支名,通常保持 master。', }, { 'key': 'GITEA_TOKEN', 'env': 'GITEA_TOKEN', 'default': '', 'label': 'Gitea 访问令牌', 'group': 'gitea', 'sensitive': True, 'help': '在 Gitea 个人设置中生成的 API 令牌,需对构建仓库授予 Actions 读写权限。页面仅显示保存状态,不会回显明文。', }, { 'key': 'GITEA_PROXY', 'env': 'GITEA_PROXY', 'default': '', 'label': 'Gitea 访问代理', 'group': 'gitea', 'sensitive': True, 'help': ( '服务器访问 Gitea 需要代理时填写;Gitea 通常部署在同内网,多数情况留空直连即可。' ), }, # ===== 任务维护(后台「维护设置」页使用,不渲染在 GitHub 配置页) ===== { 'key': 'BUILD_RETENTION_DAYS', 'env': 'BUILD_RETENTION_DAYS', 'default': '30', 'label': '构建任务保留天数', 'group': 'maintenance', 'sensitive': False, 'help': '超过该天数的已结束任务(成功/失败/取消)及其安装包产物会被自动清理;0 表示永不自动清理。', }, { 'key': 'DB_BACKUP_ENABLED', 'env': 'DB_BACKUP_ENABLED', 'default': '1', 'label': '启用数据库定时备份', 'group': 'maintenance', 'sensitive': False, 'help': '开启后维护进程每天自动备份一次数据库(含账号、配置方案、构建记录)。', }, { 'key': 'DB_BACKUP_KEEP', 'env': 'DB_BACKUP_KEEP', 'default': '7', 'label': '数据库备份保留份数', 'group': 'maintenance', 'sensitive': False, 'help': '超出份数的最旧备份会被自动删除,建议 7~30。', }, ] DEFS_BY_KEY = {d['key']: d for d in SETTING_DEFS} SOURCE_DB = 'db' # 后台配置 SOURCE_ENV = 'env' # 环境变量 SOURCE_DEFAULT = 'default' # 内置默认值 SOURCE_LABELS = { SOURCE_DB: '后台配置', SOURCE_ENV: '环境变量', SOURCE_DEFAULT: '默认值', } _CACHE_TTL = 15.0 _cache = {'ts': 0.0, 'rows': {}} def _load_rows(): """一次性读取全部后台配置,带短缓存。""" now = time.time() if now - _cache['ts'] > _CACHE_TTL: try: from .models import SiteSetting _cache['rows'] = dict(SiteSetting.objects.values_list('key', 'value')) except Exception: # 迁移尚未执行等异常情况下退回环境变量,避免阻断请求 _cache['rows'] = {} _cache['ts'] = now return _cache['rows'] def invalidate_cache(): _cache['ts'] = 0.0 def get_source(key): """返回某项当前生效值的来源:db / env / default。""" d = DEFS_BY_KEY[key] rows = _load_rows() if key in rows: return SOURCE_DB if os.environ.get(d['env']) is not None: return SOURCE_ENV return SOURCE_DEFAULT def get_value(key): """按 后台配置 → 环境变量 → 默认值 的优先级返回生效值(始终为字符串)。""" d = DEFS_BY_KEY[key] rows = _load_rows() if key in rows: return rows[key] return os.environ.get(d['env'], d['default']) def is_overridden(key): return get_source(key) == SOURCE_DB def set_value(key, value): """写入后台配置覆盖。""" from .models import SiteSetting SiteSetting.objects.update_or_create(key=key, defaults={'value': value}) invalidate_cache() def delete_value(key): """删除后台覆盖,回退到环境变量/默认值。""" from .models import SiteSetting SiteSetting.objects.filter(key=key).delete() invalidate_cache() def all_settings(): """供后台页面渲染:每项定义 + 当前生效值 + 来源。""" result = [] for d in SETTING_DEFS: result.append({ **d, 'value': get_value(d['key']), 'source': get_source(d['key']), }) return result def is_github_configured(): """当前所选构建平台是否具备发起构建的最低配置(保留函数名兼容既有调用)。""" from .build_backends import get_backend return get_backend().configured() def config_warnings(): """返回当前所选构建平台的配置隐患列表(中文),供页面提示。""" from .build_backends import get_backend return get_backend().warnings()