From 1b2ddded38e7842ea23278b1e0c208d235952965 Mon Sep 17 00:00:00 2001 From: cbb Date: Thu, 15 Oct 2026 01:31:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20v1.5.6=20=E2=80=94=20=E3=80=8C=E6=89=93?= =?UTF-8?q?=E5=BC=80=20Gitea=E3=80=8D=E6=8C=89=E9=92=AE=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E5=8F=AF=E6=98=BE=E5=BC=8F=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 远程访问时 CGI/代理环境常常无法可靠转发 Host 头(fnOS CGI 里 $HTTP_HOST 可能为空、Referer 被剥离),导致自动推断 出的'真实主机'就是面板本机的 127.0.0.1,按钮指向 localhost 没法用。 新增设置「打开 Gitea 链接」(cfg.open_url),按钮地址优先级: 1. 环境变量 GITEA_URL 2. cfg.open_url(本次新增,留空走推断) 3. cfg.gitea_host + 推断(XFH/Host/Referer) - UI:Gitea 与同步设置卡片里新增「打开 Gitea 链接」输入框 (含「保存」按钮,placeholder 提示一般不用填) - 校验:必须带协议头(http/https)、不能含路径;非法值拒绝 - /api/settings 暴露 open_url - 留空时维持原有自动推断行为,向后兼容 - manifest 1.5.5 → 1.5.6 --- GiteaHubSync/app/backend/panel.py | 49 ++++++++++++++++++++++++++----- GiteaHubSync/manifest | 2 +- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/GiteaHubSync/app/backend/panel.py b/GiteaHubSync/app/backend/panel.py index b4502ea..5d50410 100644 --- a/GiteaHubSync/app/backend/panel.py +++ b/GiteaHubSync/app/backend/panel.py @@ -71,7 +71,8 @@ ENV["GIT_TERMINAL_PROMPT"] = "0" # ---------------- 应用设置(settings.json) ---------------- cfg = {"gitea_user": LEGACY_GITEA_USER, "gitea_pass": "", "auto_create": True, "interval": DEFAULT_INTERVAL, "gitea_host": DEFAULT_GITEA_HOST, - "max_workers": DEFAULT_WORKERS, "max_retries": DEFAULT_MAX_RETRIES} + "max_workers": DEFAULT_WORKERS, "max_retries": DEFAULT_MAX_RETRIES, + "open_url": ""} # 「打开 Gitea」按钮专用链接(留空则用 gitea_host + 自动推断) def norm_gitea_host(h): @@ -98,11 +99,15 @@ def gitea_host(): def gitea_ui(host_header="", referer="", xfwd_host=""): - """「打开 Gitea」按钮地址:把 127.0.0.1/localhost 替换成真实远程主机名。 - 推断顺序:X-Forwarded-Host(CGI 转发) > Host > Referer(浏览器自带)。 - 注意:仅当推断出的真实主机名不是 loopback 时才替换(本地直连保持原样)。""" - if GITEA_URL_ENV: - return GITEA_URL_ENV + """「打开 Gitea」按钮地址。 + 优先级:环境变量 GITEA_URL > 配置 open_url > gitea_host(自动推断替换)。 + 自动推断顺序:X-Forwarded-Host > Host > Referer,仅当真实主机非 loopback 时替换 gitea_host。""" + env_url = GITEA_URL_ENV + if env_url: + return env_url + ou = (cfg.get("open_url") or "").strip() + if ou: + return ou base = gitea_host() real = (xfwd_host or host_header or "").split(":")[0].strip() if not real and referer: @@ -110,7 +115,7 @@ def gitea_ui(host_header="", referer="", xfwd_host=""): if m: real = m.group(1) if not real or real in ("127.0.0.1", "localhost", "0.0.0.0", "::1"): - return base # 本地/无法推断,保留 gitea_host 原值 + return base try: p = urlsplit(base) if p.hostname in ("127.0.0.1", "localhost", "0.0.0.0", "::1", ""): @@ -184,6 +189,12 @@ def load_settings(): h = norm_gitea_host(d.get("gitea_host")) if h: cfg["gitea_host"] = h + # open_url 校验:必须是 http(s) URL 且无路径 + ou = d.get("open_url") + if isinstance(ou, str): + cfg["open_url"] = norm_gitea_host(ou.strip()) or "" + else: + cfg["open_url"] = str(ou or "").strip() or cfg.get("open_url", "") except Exception: pass write_gitcreds() @@ -1309,6 +1320,11 @@ PAGE_SETTINGS = """
+ +
+ + +
@@ -1386,6 +1402,7 @@ async function refresh(){ document.getElementById('giteaPass').placeholder=s.gitea_pass_set ?'Gitea 密码(已设置;留空保存可清除)':'Gitea 密码(私有仓库必填)'; document.getElementById('giteaUrlTip').textContent='「打开 Gitea」按钮地址:'+(s.gitea_url||'—')+'(随访问地址自动适配);密码只保存在 NAS 本地,仅用于读取私有仓库。'; + document.getElementById('openUrl').value=s.open_url||''; document.getElementById('swAuto').checked=!!s.auto_create; const iv=document.getElementById('syncInterval'); iv.value=s.interval||10;iv.min=s.min_interval||5;iv.max=s.max_interval||86400; @@ -1526,6 +1543,12 @@ async function saveGitea(withPass){ if(withPass)document.getElementById('giteaPass').value=''; refresh(); } +async function saveOpenUrl(){ + const v=document.getElementById('openUrl').value.trim(); + const r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({open_url:v})})).json(); + showMsg('msgCfg',r.msg||JSON.stringify(r),!!r.ok); + refresh(); +} async function saveAuto(){ const auto_create=document.getElementById('swAuto').checked; const r=await (await fetch('api/settings/save',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({auto_create})})).json(); @@ -1713,6 +1736,7 @@ class Handler(BaseHTTPRequestHandler): "max_retries": max_retries(), "retry_interval": RETRY_INTERVAL, "gitea_host": gitea_host(), + "open_url": cfg.get("open_url", ""), "gitea_url": gitea_ui( self.headers.get("Host"), self.headers.get("Referer"), @@ -1751,6 +1775,15 @@ class Handler(BaseHTTPRequestHandler): self._json({"ok": False, "msg": "Gitea 地址不合法,应类似 http://127.0.0.1:3000"}) return new_host = h + if "open_url" in data: + v = (data.get("open_url") or "").strip() + if v: + if "://" not in v: + self._json({"ok": False, "msg": "「打开 Gitea 链接」必须带协议头,如 http://nas.local:3000 或 https://..."}) + return + if not norm_gitea_host(v): + self._json({"ok": False, "msg": "「打开 Gitea 链接」格式不合法,应类似 http://nas.local:3000(留空则用默认推断)"}) + return if "interval" in data: if not valid_interval(data.get("interval")): self._json({"ok": False, "msg": f"同步间隔须为 {MIN_INTERVAL} ~ {MAX_INTERVAL} 之间的整数(秒)"}) @@ -1802,6 +1835,8 @@ class Handler(BaseHTTPRequestHandler): log("Gitea 密码已" + ("更新" if new_pass else "清除")) if new_host is not None: cfg["gitea_host"] = new_host + if "open_url" in data: + cfg["open_url"] = (data.get("open_url") or "").strip() if "auto_create" in data: cfg["auto_create"] = bool(data.get("auto_create")) if "interval" in data: diff --git a/GiteaHubSync/manifest b/GiteaHubSync/manifest index 6d5d2f9..2a2775b 100644 --- a/GiteaHubSync/manifest +++ b/GiteaHubSync/manifest @@ -1,5 +1,5 @@ appname = GiteaHubSync -version = 1.5.5 +version = 1.5.6 display_name = Gitea→GitHub 同步助手 desc = 每次推送到飞牛 Gitea 的代码,10 秒内自动镜像到 GitHub 的 7 个私有仓库。内置控制面板:自动同步开关、立即同步、仓库状态、同步日志,一键打开 Gitea。数据全部保存在 NAS 本地。 platform = all