Files
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

136 lines
5.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""把 WorkBuddy 网关口令**投递**给设备接入垫片(A 方案 · 2026-09-30 用户拍板)。
🔴 为什么需要它:垫片原设计「口令只在 env」⇒ 由**脱离会话**的上下文(计划任务/常驻)拉起时
读不到口令 ⇒ 请求一路 fail-closed 成 503,链路永远差最后一跳。
本脚本跑在**有口令的那一侧**(宿主进程树:钩子/`--tick`)⇒ 天然读得到 env ⇒ 负责「送」。
🔴 安全口径(与本项目红线一致):
· ⛔ 口令**不落盘**(本文件不写任何凭据;只在进程内从 env 读到内存,直接进请求体)
· ⛔ 口令**不进日志**(只写「投了/没投/被拒」+ HTTP 码)
· ⛔ 只投回环(127.0.0.1);垫片侧还有"**探活通过才采纳**"的第二道闸(错口令必被拒)
⚠️ 节流:≥ `MIN_GAP` 秒才尝试一次(钩子触发很频繁,⛔ 不能每次工具调用都去探)。
⛔ 失败一律**静默退出 0** —— 它是旁路,⛔ 不得影响钩子本身、更不得影响 WorkBuddy 运行。
"""
from __future__ import annotations
# [session-mechanism] roots.env 外置(由 install.py 生成;缺失则回落到按位置推导)
def _sm_load_roots():
import os as _os
_here = _os.path.dirname(_os.path.abspath(__file__))
for _up in range(4):
_p = _os.path.join(_here, *([".."] * _up), "roots.env")
_p = _os.path.normpath(_p)
if _os.path.isfile(_p):
try:
with open(_p, encoding="utf-8") as _f:
for _ln in _f:
_ln = _ln.strip()
if _ln and not _ln.startswith("#") and "=" in _ln:
_k, _v = _ln.split("=", 1)
_os.environ.setdefault(_k.strip(), _v.strip())
except Exception:
pass
return
_sm_load_roots()
# 🔴 2026-10-01 加 · **输出编码兜底**:脚本一旦被重定向(钩子/常驻/后台任务都会这么干),
# Windows 本地编码(GBK)编不出 ⛔/✅/🔴 这类字符 ⇒ `print` 抛 UnicodeEncodeError
# ⇒ 被顶层 handler 记成 `fatal`、**整轮失败**(实测:本包里连续 4 次 `fatal 'gbk' codec ...`)。
# ⇒ 出口一律 UTF-8 + errors="replace"(⛔ 不让"打不出字"升级成"程序死")。
try:
import sys as _sys
_sys.stdout.reconfigure(encoding="utf-8", errors="replace")
_sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
import json
import os
import sys
import time
import urllib.request
PORT = 20090
SELFCHECK = "/__device_access/selfcheck"
TOKEN_PATH = "/__device_access/token"
TOKEN_ENV = "CODEBUDDY_GATEWAY_PASSWORD"
MIN_GAP = 60.0 # 秒
WS = os.environ.get("DSH_COLLAB_WS") or os.environ.get("DSH_WS_ROOT") or os.getcwd()
STAMP = os.path.join(WS, "tmp", "supervise-inbox", "_token-deliver.stamp")
LOG = os.path.join(WS, "tmp", "supervise-inbox", "_token-deliver.log")
def _log(msg: str) -> None:
try:
os.makedirs(os.path.dirname(LOG), exist_ok=True)
with open(LOG, "a", encoding="utf-8") as f:
f.write("[%s] %s\n" % (time.strftime("%Y-%m-%d %H:%M:%S"), msg))
except Exception:
pass
def _http(url: str, data: bytes | None = None, timeout: float = 4.0):
req = urllib.request.Request(url, data=data, method=("POST" if data is not None else "GET"))
if data is not None:
req.add_header("Content-Type", "text/plain")
with urllib.request.urlopen(req, timeout=timeout) as r: # noqa: S310(只打回环)
return r.status, r.read().decode("utf-8", "replace")
def main() -> int:
# ① 节流:⛔ 每次工具调用都探一遍会平白增加负担
try:
if os.path.isfile(STAMP) and (time.time() - os.path.getmtime(STAMP)) < MIN_GAP:
return 0
os.makedirs(os.path.dirname(STAMP), exist_ok=True)
with open(STAMP, "w", encoding="utf-8") as f:
f.write(str(time.time()))
except Exception:
pass
token = os.environ.get(TOKEN_ENV) or ""
if token == "":
# 本进程没有口令 ⇒ 不可能是"送的一方"(钩子/--tick 才有);⛔ 不报错、不落 NEED-USER
return 0
base = "http://127.0.0.1:%d" % PORT
# ② 垫片不在 ⇒ 静默走人(⛔ 不起它;起它是另一件事)
try:
code, body = _http(base + SELFCHECK)
except Exception:
return 0
if code != 200:
return 0
try:
present = bool((json.loads(body).get("credential") or {}).get("present"))
except Exception:
present = False
if present:
return 0 # 已就位(env 或已投过)⇒ 不必重复投
# ③ 投(⛔ 口令只在请求体里,⛔ 绝不进日志/URL)
try:
code, body = _http(base + TOKEN_PATH, data=token.encode("utf-8"))
except Exception as e:
_log("投递异常(已静默): %s" % type(e).__name__)
return 0
try:
j = json.loads(body)
except Exception:
j = {}
if code == 200 and j.get("ok"):
_log("投递成功 → source=%s" % ((j.get("credential") or {}).get("source") or "?"))
else:
# 400 token-rejected-by-gateway = 口令过期/网关重启(正常会自愈,⛔ 不惊动用户)
_log("投递被拒 http=%s reason=%s" % (code, j.get("reason") or "?"))
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except Exception:
sys.exit(0) # ⛔ 旁路脚本绝不以非零码影响钩子