From 5892e23f4293b87270e6c9f2d53b8518aa93ff02 Mon Sep 17 00:00:00 2001 From: jk <2081217232@qq.com> Date: Sun, 4 Oct 2026 09:41:50 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(openai-compat):=20thinking/reasoning?= =?UTF-8?q?=5Fcontent=20=E5=8F=8C=E5=90=91=E9=80=8F=E4=BC=A0=E4=B8=8E=20re?= =?UTF-8?q?asoning=5Feffort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 请求侧:历史轮 reasoning_content → Anthropic thinking 块(signature 空串可过); reasoning_effort low/high/max 透传上游,其余值不带(上游不支持关思考) - 响应侧:thinking 块聚合为 message.reasoning_content(DeepSeek/pi-ai 风格) - 流式:thinking_delta → reasoning_content 增量 chunk --- app/openai_compat.py | 24 +++++- tests/unit/test_openai_compat.py | 125 +++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/app/openai_compat.py b/app/openai_compat.py index 87aa012..5789ce2 100644 --- a/app/openai_compat.py +++ b/app/openai_compat.py @@ -106,6 +106,11 @@ def openai_to_anthropic(payload: dict) -> tuple[dict | None, str | None]: }) elif role == "assistant": blocks: list[dict] = [] + # 多轮重放:reasoning_content(历史轮思考文本)→ Anthropic thinking 块。 + # 上游对 thinking 块 signature 不校验(实测伪造值可通过),空串亦可。 + rc = msg.get("reasoning_content") or msg.get("reasoning") or msg.get("reasoning_text") + if isinstance(rc, str) and rc.strip(): + blocks.append({"type": "thinking", "thinking": rc, "signature": ""}) text = _text_from_content(content) if text: blocks.append({"type": "text", "text": text}) @@ -148,6 +153,12 @@ def openai_to_anthropic(payload: dict) -> tuple[dict | None, str | None]: body["top_p"] = float(payload["top_p"]) except (TypeError, ValueError): pass + # 推理强度:上游 GLM-5.3 系原生接受 reasoning_effort(low/high/max)。 + # 其余值(含 off/none)不带该字段——上游不支持关闭思考,交给默认档。 + effort = payload.get("reasoning_effort") + if isinstance(effort, str) and effort.strip().lower() in ("low", "high", "max"): + body["reasoning_effort"] = effort.strip().lower() + stop = payload.get("stop") if isinstance(stop, str) and stop: body["stop_sequences"] = [stop] @@ -186,11 +197,14 @@ def openai_to_anthropic(payload: dict) -> tuple[dict | None, str | None]: def anthropic_to_openai(data: dict, model: str) -> dict: """Anthropic message 响应 → OpenAI chat.completion。""" text_parts: list[str] = [] + thinking_parts: list[str] = [] tool_calls: list[dict] = [] for block in data.get("content") or []: if not isinstance(block, dict): continue - if block.get("type") == "text" and isinstance(block.get("text"), str): + if block.get("type") == "thinking" and isinstance(block.get("thinking"), str): + thinking_parts.append(block["thinking"]) + elif block.get("type") == "text" and isinstance(block.get("text"), str): text_parts.append(block["text"]) elif block.get("type") == "tool_use": tool_calls.append({ @@ -205,6 +219,10 @@ def anthropic_to_openai(data: dict, model: str) -> dict: in_tok = _as_int(usage.get("input_tokens")) or 0 out_tok = _as_int(usage.get("output_tokens")) or 0 message: dict = {"role": "assistant", "content": "".join(text_parts) or None} + # OpenAI 兼容侧通用约定:思考文本放 reasoning_content(pi-ai/DeepSeek 风格均可识别) + reasoning = "\n".join(p for p in thinking_parts if p) + if reasoning: + message["reasoning_content"] = reasoning if tool_calls: message["tool_calls"] = tool_calls return { @@ -261,6 +279,8 @@ def feed(self, evt: dict) -> list[str]: return [] if etype == "content_block_start": block = evt.get("content_block") or {} + if block.get("type") == "thinking": + return [self._chunk({"reasoning_content": ""})] if block.get("type") == "tool_use": idx = self._tool_seq self._tool_seq += 1 @@ -273,6 +293,8 @@ def feed(self, evt: dict) -> list[str]: return [] if etype == "content_block_delta": delta = evt.get("delta") or {} + if delta.get("type") == "thinking_delta" and isinstance(delta.get("thinking"), str): + return [self._chunk({"reasoning_content": delta["thinking"]})] if delta.get("type") == "text_delta" and isinstance(delta.get("text"), str): return [self._chunk({"content": delta["text"]})] if delta.get("type") == "input_json_delta" and isinstance(delta.get("partial_json"), str): diff --git a/tests/unit/test_openai_compat.py b/tests/unit/test_openai_compat.py index 26ba0cb..547142e 100644 --- a/tests/unit/test_openai_compat.py +++ b/tests/unit/test_openai_compat.py @@ -223,3 +223,128 @@ def test_unknown_events_ignored(self): conv.start() assert conv.feed({"type": "ping"}) == [] assert conv.feed({"type": "content_block_stop", "index": 0}) == [] + +class TestReasoningMapping: + """reasoning 双向映射:reasoning_effort 入站透传 + thinking 块 ↔ reasoning_content 出站。""" + + def test_reasoning_effort_passthrough(self): + body, err = openai_to_anthropic({ + "model": "glm-5.3", + "messages": [{"role": "user", "content": "hi"}], + "reasoning_effort": "high", + }) + assert err is None + assert body["reasoning_effort"] == "high" + + def test_reasoning_effort_normalizes_case(self): + body, _ = openai_to_anthropic({ + "model": "glm-5.3", + "messages": [{"role": "user", "content": "hi"}], + "reasoning_effort": "MAX", + }) + assert body["reasoning_effort"] == "max" + + def test_reasoning_effort_invalid_omitted(self): + # 上游不支持关闭思考,off/none/未知值一律不带该字段 + body, _ = openai_to_anthropic({ + "model": "glm-5.3", + "messages": [{"role": "user", "content": "hi"}], + "reasoning_effort": "off", + }) + assert "reasoning_effort" not in body + + def test_reasoning_effort_absent(self): + body, _ = openai_to_anthropic({ + "model": "glm-5.3", + "messages": [{"role": "user", "content": "hi"}], + }) + assert "reasoning_effort" not in body + + def test_assistant_reasoning_content_replays_as_thinking(self): + body, err = openai_to_anthropic({ + "model": "glm-5.3", + "messages": [ + {"role": "user", "content": "1+1"}, + {"role": "assistant", "content": "2", "reasoning_content": "用户问 1+1,答案是 2"}, + {"role": "user", "content": "再加 1"}, + ], + }) + assert err is None + assistant = body["messages"][1] + assert [b["type"] for b in assistant["content"]] == ["thinking", "text"] + assert assistant["content"][0]["thinking"] == "用户问 1+1,答案是 2" + + def test_assistant_reasoning_alias_fields(self): + for alias in ("reasoning", "reasoning_text"): + body, _ = openai_to_anthropic({ + "model": "glm-5.3", + "messages": [ + {"role": "assistant", "content": "ok", alias: "思考"}, + ], + }) + assert body["messages"][0]["content"][0]["type"] == "thinking" + + def test_assistant_blank_reasoning_skipped(self): + body, _ = openai_to_anthropic({ + "model": "glm-5.3", + "messages": [{"role": "assistant", "content": "ok", "reasoning_content": " "}], + }) + # 空白思考不产生 thinking 块,也不丢 assistant 消息 + assert body["messages"][0]["content"] == [{"type": "text", "text": "ok"}] + + def test_nonstream_thinking_to_reasoning_content(self): + data = { + "id": "msg_1", "type": "message", "role": "assistant", "model": "glm-5.3", + "content": [ + {"type": "thinking", "thinking": "先想一下", "signature": "sig"}, + {"type": "text", "text": "答案"}, + ], + "stop_reason": "end_turn", + "usage": {"input_tokens": 5, "output_tokens": 9}, + } + out = anthropic_to_openai(data, "glm-5.3") + msg = out["choices"][0]["message"] + assert msg["content"] == "答案" + assert msg["reasoning_content"] == "先想一下" + + def test_nonstream_no_thinking_no_field(self): + data = { + "id": "msg_1", "type": "message", "role": "assistant", "model": "glm-5.3", + "content": [{"type": "text", "text": "答案"}], + "stop_reason": "end_turn", "usage": {"input_tokens": 5, "output_tokens": 1}, + } + out = anthropic_to_openai(data, "glm-5.3") + assert "reasoning_content" not in out["choices"][0]["message"] + + def test_stream_thinking_to_reasoning_content(self): + conv = StreamConverter("GLM-5.3") + outs = [conv.start()] + events = [ + {"type": "message_start", "message": {"id": "msg_r", "usage": {"input_tokens": 3}}}, + {"type": "content_block_start", "index": 0, "content_block": {"type": "thinking"}}, + {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "想"}}, + {"type": "content_block_delta", "index": 0, "delta": {"type": "thinking_delta", "thinking": "一下"}}, + {"type": "content_block_stop", "index": 0}, + {"type": "content_block_start", "index": 1, "content_block": {"type": "text"}}, + {"type": "content_block_delta", "index": 1, "delta": {"type": "text_delta", "text": "答案"}}, + {"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"output_tokens": 4}}, + ] + for evt in events: + outs.extend(conv.feed(evt)) + outs.append(conv.done()) + + chunks = _parse_sse("".join(outs)) + reasoning = "".join(c["choices"][0]["delta"].get("reasoning_content") or "" for c in chunks) + text = "".join(c["choices"][0]["delta"].get("content") or "" for c in chunks) + assert reasoning == "想一下" + assert text == "答案" + + def test_stream_no_thinking_keeps_shape(self): + # 原有纯文本流不应引入 reasoning_content 字段(与既有客户端行为兼容) + conv = StreamConverter("GLM-5.3") + conv.start() + outs = conv.feed({"type": "content_block_delta", "index": 0, + "delta": {"type": "text_delta", "text": "你好"}}) + chunks = _parse_sse("".join(outs)) + assert all("reasoning_content" not in c["choices"][0]["delta"] for c in chunks) + From 82e9115c5faa68031564b8b3a8e99c9870697ffc Mon Sep 17 00:00:00 2001 From: jk <2081217232@qq.com> Date: Sun, 4 Oct 2026 09:41:50 +0800 Subject: [PATCH 2/5] =?UTF-8?q?docs:=20README=20=E5=A2=9E=E5=8A=A0=20Docke?= =?UTF-8?q?r=20=E9=83=A8=E7=BD=B2=E8=AF=B4=E6=98=8E=EF=BC=88compose=20?= =?UTF-8?q?=E4=B8=80=E9=94=AE=E8=B5=B7=EF=BC=8C=E6=95=B0=E6=8D=AE=E6=8C=81?= =?UTF-8?q?=E4=B9=85=E5=8C=96=20./data=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 0d9effa..70f74d0 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,21 @@ cp .env.example .env # 按需修改密钥、端口等 > 使用 Z.AI **JWT 模式**需要 Node.js 求解验证码,首次先执行:`cd captcha_node && npm install`。 +### Docker 部署(推荐) + +镜像自带 Python 3.11 + Node 22 + Chromium,无需宿主机装任何运行时: + +```bash +cp .env.example .env # 按需修改 ZCODE_ADMIN_KEY、端口等 +docker compose up -d --build +``` + +- 访问 `http://127.0.0.1:3000/admin/login`;宿主机端口取 `.env` 的 `ZCODE_PORT` +- 账号库与设备指纹持久化在宿主机 `./data/`(首次启动自动生成,已有数据直接沿用) +- `.env` 里的 `ZCODE_CHROMIUM_PATH` 等宿主机路径会被 compose 覆盖为容器内路径,无需改动 +- 容器内 CLI:`docker compose exec zcode-hub python cli.py accounts`(其余子命令同理) +- 停止:`docker compose down`(数据保留在 `./data/`) + ## 快速上手一个账号 ```bash From df4f070399f0244a082c803a52625141c488405b Mon Sep 17 00:00:00 2001 From: jk <2081217232@qq.com> Date: Sun, 4 Oct 2026 09:41:50 +0800 Subject: [PATCH 3/5] =?UTF-8?q?chore:=20=E5=89=8D=E7=AB=AF=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E5=AF=B9=E9=BD=90=20+=20captcha=5Fnode=20loc?= =?UTF-8?q?kfile=20=E5=90=8C=E6=AD=A5=EF=BC=88puppeteer-core=20=E7=9C=9F?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E6=B1=82=E8=A7=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- captcha_node/package-lock.json | 953 ++++++++++++++++++++++++++++++++- frontend/version | 2 +- 2 files changed, 951 insertions(+), 4 deletions(-) diff --git a/captcha_node/package-lock.json b/captcha_node/package-lock.json index 5c71a08..4516427 100644 --- a/captcha_node/package-lock.json +++ b/captcha_node/package-lock.json @@ -1,16 +1,45 @@ { "name": "zcode2api-captcha-solver", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zcode2api-captcha-solver", - "version": "2.0.0", + "version": "2.1.0", "dependencies": { - "happy-dom": "^20.14.0" + "happy-dom": "^20.14.0", + "puppeteer-core": "^23.0.0" } }, + "node_modules/@puppeteer/browsers": { + "version": "2.6.1", + "resolved": "https://registry.npmmirror.com/@puppeteer/browsers/-/browsers-2.6.1.tgz", + "integrity": "sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmmirror.com/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "26.4.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", @@ -35,6 +64,217 @@ "@types/node": "*" } }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmmirror.com/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.9.0", + "resolved": "https://registry.npmmirror.com/b4a/-/b4a-1.9.0.tgz", + "integrity": "sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.9.2", + "resolved": "https://registry.npmmirror.com/bare-events/-/bare-events-2.9.2.tgz", + "integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.8.2", + "resolved": "https://registry.npmmirror.com/bare-fs/-/bare-fs-4.8.2.tgz", + "integrity": "sha512-+ZI68KHMUvosXfKbg/UOHK0tbCdRnegbvPEdEcZ3Nd6TetieQsJPRXBRXPdLyy8+3VSEbPXtsumTpEtt78xv9w==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.28.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-path": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/bare-path/-/bare-path-3.1.2.tgz", + "integrity": "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==", + "license": "Apache-2.0" + }, + "node_modules/bare-stream": { + "version": "2.13.4", + "resolved": "https://registry.npmmirror.com/bare-stream/-/bare-stream-2.13.4.tgz", + "integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.5.4", + "resolved": "https://registry.npmmirror.com/bare-url/-/bare-url-2.5.4.tgz", + "integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.3.1", + "resolved": "https://registry.npmmirror.com/basic-ftp/-/basic-ftp-5.3.1.tgz", + "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-image-size": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", @@ -47,6 +287,255 @@ "node": ">=4.0" } }, + "node_modules/chromium-bidi": { + "version": "0.11.0", + "resolved": "https://registry.npmmirror.com/chromium-bidi/-/chromium-bidi-0.11.0.tgz", + "integrity": "sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "3.0.1", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1367902", + "resolved": "https://registry.npmmirror.com/devtools-protocol/-/devtools-protocol-0.0.1367902.tgz", + "integrity": "sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==", + "license": "BSD-3-Clause" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.5", + "resolved": "https://registry.npmmirror.com/get-uri/-/get-uri-6.0.5.tgz", + "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/happy-dom": { "version": "20.14.0", "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.14.0.tgz", @@ -86,12 +575,415 @@ "node": ">=12" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ip-address": { + "version": "10.7.2", + "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/netmask": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/netmask/-/netmask-2.1.1.tgz", + "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmmirror.com/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer-core": { + "version": "23.11.1", + "resolved": "https://registry.npmmirror.com/puppeteer-core/-/puppeteer-core-23.11.1.tgz", + "integrity": "sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.6.1", + "chromium-bidi": "0.11.0", + "debug": "^4.4.0", + "devtools-protocol": "0.0.1367902", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.10", + "resolved": "https://registry.npmmirror.com/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmmirror.com/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamx": { + "version": "2.28.1", + "resolved": "https://registry.npmmirror.com/streamx/-/streamx-2.28.1.tgz", + "integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/tar-fs/-/tar-fs-3.1.3.tgz", + "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/tar-stream/-/tar-stream-3.2.1.tgz", + "integrity": "sha512-nqsEO8zLZJvrOMdEwkA0QdCLFbetHMn95Zqu4fKwX+hkaTWJPZZOrxx/PwtxoK0MMGQmBQNRW3CPs8IFYQz4cQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmmirror.com/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmmirror.com/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typed-query-selector": { + "version": "2.12.3", + "resolved": "https://registry.npmmirror.com/typed-query-selector/-/typed-query-selector-2.12.3.tgz", + "integrity": "sha512-/dtuMZyutHLlrxWhhC1uib00ELUr2dZ8C9B8YzZYsJmeE3V56OxRblrSM2UaF142pcMtak7ehZ6+poWGeehNIg==", + "license": "MIT" + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmmirror.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/undici-types": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "license": "MIT" }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/ws": { "version": "8.21.0", "resolved": "https://registry.npmmirror.com/ws/-/ws-8.21.0.tgz", @@ -112,6 +1004,61 @@ "optional": true } } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmmirror.com/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/frontend/version b/frontend/version index bd4053b..2714f53 100644 --- a/frontend/version +++ b/frontend/version @@ -1 +1 @@ -2.6.3 \ No newline at end of file +2.6.4 From b87963fc7ff213318e98a1c1887ebf7c7180f319 Mon Sep 17 00:00:00 2001 From: jk <2081217232@qq.com> Date: Sun, 4 Oct 2026 10:33:01 +0800 Subject: [PATCH 4/5] =?UTF-8?q?chore:=20Linux/Windows=20=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=EF=BC=88=E5=8A=9F=E8=83=BD=E5=AF=B9=E9=BD=90?= =?UTF-8?q?=EF=BC=89+=20Docker=20=E9=83=A8=E7=BD=B2=E6=96=87=E4=BB=B6=20+?= =?UTF-8?q?=20=E6=9C=AC=E5=9C=B0=E6=9D=82=E7=89=A9=E5=BF=BD=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - start.sh / start.bat:启动/停止/重启语义对齐——停旧实例(serve.pid, 优雅→强杀)、 端口占用检测、依赖自动安装、后台运行+日志、/meta 健康检查 - .gitattributes:*.bat/*.cmd 强制 CRLF(cmd 对 LF 的 goto/label 有解析怪癖) - Dockerfile + compose + .dockerignore:多阶段镜像(python3.11-slim + node22 + Debian chromium + tini + CJK 字体),数据卷 ./data,.env 不入镜像 - .gitignore:排除 serve.log/pid、.trae_*、.sg_* 等本地运行产物 --- .dockerignore | 22 +++++++ .gitattributes | 3 + .gitignore | 8 +++ Dockerfile | 70 +++++++++++++++++++++ docker-compose.yml | 23 +++++++ start.bat | 147 +++++++++++++++++++++++++++++++++++++++++++++ start.sh | 93 ++++++++++++++++++++++++++++ 7 files changed, 366 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 start.bat create mode 100755 start.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3f56a82 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,22 @@ +# 构建上下文瘦身 + 秘密不入镜像(roadmap 约定:.env / data/ 永不入镜像) +.git +.github +.venv +.env +data +tests +docs +scripts +__pycache__ +**/__pycache__ +.pytest_cache +.ruff_cache +.mypy_cache +captcha_node/node_modules +*.md +Dockerfile +docker-compose.yml +.dockerignore +start.bat +start.sh +uv.lock diff --git a/.gitattributes b/.gitattributes index 6313b56..a2237bc 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,4 @@ * text=auto eol=lf + +*.bat text eol=crlf +*.cmd text eol=crlf diff --git a/.gitignore b/.gitignore index 0d237a6..c735577 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,11 @@ node_modules/ # 秘钥 / 凭证导出 *accounts*.json zcode-accounts*.json + +# 本地运行 / 工具杂物 +serve.log +serve.err.log +serve.pid +.trae_* +.sg-js/ +.sg_login.html diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..63d47cf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,70 @@ +# zcode-hub 容器镜像:Python 3.11 网关 + Node 22 验证码求解子进程 + Debian Chromium +# +# 运行时结构: +# /app 代码(app/ cli.py frontend/ captcha_node/) +# /app/data 持久化(accounts.db + device_mid),挂载卷 +# /usr/bin/node 从 node:22 官方镜像复制(bookworm 与运行时基础镜像同系,glibc 兼容) +# /usr/bin/chromium Debian chromium,solver_pw.js findChromium() 默认探测路径之一 +# +# captcha 求解依赖真浏览器(solver 已带 --no-sandbox/--disable-dev-shm-usage), +# 且 oom 看门狗需要写 /proc/*/oom_score_adj —— 容器以 root 运行是前提,勿改 USER。 + +# ── 阶段 1:求解器 Node 依赖(纯 JS,无原生编译) ───────────────────────────── +FROM node:22-bookworm-slim AS captcha-deps +WORKDIR /build +COPY captcha_node/package.json captcha_node/package-lock.json ./ +RUN npm ci --omit=dev + +# ── 阶段 2:运行时 ──────────────────────────────────────────────────────────── +FROM python:3.11-slim-bookworm + +ENV PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 \ + PIP_NO_CACHE_DIR=1 \ + ZCODE_HOST=0.0.0.0 \ + ZCODE_PORT=3000 \ + ZCODE_DATA_DIR=/app/data \ + ZCODE_NODE_PATH=/usr/local/bin/node \ + ZCODE_CHROMIUM_PATH=/usr/bin/chromium + +# tini 作 PID 1:回收 uvicorn→node→chrome 进程树遗留的僵尸 +# fonts-noto-cjk:验证码挑战页含中文文案,缺字形可能影响求解 +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + chromium \ + fonts-noto-cjk \ + fonts-liberation \ + ca-certificates \ + tini \ + && rm -rf /var/lib/apt/lists/* + +# Node 运行时(构建期不需要 npm,只带 node 本体) +COPY --from=captcha-deps /usr/local/bin/node /usr/local/bin/node + +WORKDIR /app + +# 运行时依赖(与 requirements.txt 上半段保持一致;dev/test 工具不进镜像) +RUN pip install --no-cache-dir \ + "fastapi>=0.115.0" \ + "uvicorn[standard]>=0.30.0" \ + "httpx>=0.27.0" \ + "python-dotenv>=1.0.1" \ + "cryptography>=42.0.0" \ + "pyyaml>=6.0.0" + +COPY app/ app/ +COPY captcha_node/ captcha_node/ +COPY --from=captcha-deps /build/node_modules/ captcha_node/node_modules/ +COPY frontend/ frontend/ +COPY cli.py ./ + +# data/ 不打进镜像(.dockerignore 双保险),挂卷持久化 +RUN mkdir -p /app/data + +EXPOSE 3000 + +ENTRYPOINT ["/usr/bin/tini", "--"] +CMD ["python", "cli.py", "serve"] + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD ["python", "-c", "import urllib.request;urllib.request.urlopen('http://127.0.0.1:3000/meta', timeout=4)"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..fb06b38 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +services: + zcode-hub: + build: . + image: zcode-hub:latest + container_name: zcode-hub + restart: unless-stopped + init: true + ports: + # 宿主机端口取 .env 的 ZCODE_PORT(默认 3000);容器内固定 3000 + - "${ZCODE_PORT:-3000}:3000" + env_file: + - path: .env + required: false + environment: + # 容器侧固定值,覆盖 .env 中的宿主机配置(如 Windows Chrome 路径) + ZCODE_HOST: 0.0.0.0 + ZCODE_PORT: "3000" + ZCODE_DATA_DIR: /app/data + ZCODE_NODE_PATH: /usr/local/bin/node + ZCODE_CHROMIUM_PATH: /usr/bin/chromium + volumes: + # 账号库(accounts.db)与设备指纹(device_mid)持久化;沿用宿主机已有 data/ + - ./data:/app/data diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..ee8bc63 --- /dev/null +++ b/start.bat @@ -0,0 +1,147 @@ +@echo off +setlocal EnableExtensions +chcp 65001 >nul +cd /d "%~dp0" + +rem ===================================================================== +rem zcode-hub Windows launcher -- feature parity with start.sh (Linux) +rem +rem Usage: +rem start.bat Start (auto-stops a running instance first) +rem start.bat stop Stop +rem +rem Notes: +rem - Runs the server hidden in background; logs: serve.log + serve.err.log +rem - Health check via GET /meta after start +rem - Console messages are ASCII on purpose (avoids codepage mojibake) +rem ===================================================================== + +rem -- Edit as needed ---------------------------------------------------- +rem Upstream proxy (Clash mixed port); leave empty "" for direct connection +set "PROXY=http://127.0.0.1:7890" +rem ---------------------------------------------------------------------- + +set "PORT=3000" +if exist .env for /f "tokens=1,* delims==" %%a in ('findstr /b /c:"ZCODE_PORT=" .env') do set "PORT=%%b" +if defined PORT set "PORT=%PORT: =%" +if not defined PORT set "PORT=3000" + +rem Pause on exit only when launched by double-click (cmd /c), never in scripts +set "DBLCLK=0" +echo %cmdcmdline% | find /i "/c" >nul 2>&1 && set "DBLCLK=1" + +if /i "%~1"=="stop" ( + call :stop_instance + echo [OK] Stopped. + if "%DBLCLK%"=="1" pause + exit /b 0 +) + +rem -- 1. venv + Python dependencies ------------------------------------- +if not exist ".venv\Scripts\python.exe" ( + where python >nul 2>&1 + if errorlevel 1 ( + echo [!] python not found on PATH. Install Python 3.11+ first. + goto :fail + ) + echo [*] Creating venv (.venv) ... + python -m venv .venv || goto :fail +) +".venv\Scripts\python.exe" -c "import fastapi, uvicorn, httpx" >nul 2>&1 +if errorlevel 1 ( + echo [*] Installing Python dependencies ... + if defined PROXY ( + ".venv\Scripts\python.exe" -m pip install -q --proxy "%PROXY%" -r requirements.txt || goto :fail + ) else ( + ".venv\Scripts\python.exe" -m pip install -q -r requirements.txt || goto :fail + ) +) + +rem -- 2. captcha_node (npm) dependencies -------------------------------- +if not exist "captcha_node\node_modules" ( + echo [*] Installing captcha_node dependencies (npm install) ... + pushd captcha_node + call npm install --no-audit --no-fund + if errorlevel 1 ( + popd + goto :fail + ) + popd +) + +rem -- 3. Stop old instance (restart semantics, clears admin lock too) --- +call :stop_instance + +rem -- 4. Port must be free ---------------------------------------------- +set "OCCUPY=" +for /f "tokens=5" %%p in ('netstat -ano ^| findstr /r /c:":%PORT% .*LISTENING"') do set "OCCUPY=%%p" +if defined OCCUPY ( + echo [!] Port %PORT% still occupied by another process ^(PID %OCCUPY%^): + netstat -ano | findstr /r /c:":%PORT% .*LISTENING" + echo Stop it manually first, e.g.: taskkill /F /PID %OCCUPY% + goto :fail +) + +rem -- 5. Start hidden in background, capture real python PID ------------ +echo [*] Starting zcode-hub (port=%PORT%) ... +if defined PROXY ( + set "HTTP_PROXY=%PROXY%/" + set "HTTPS_PROXY=%PROXY%/" +) +powershell -NoProfile -Command "$d=(Get-Location).Path; $p = Start-Process -FilePath (Join-Path $d '.venv\Scripts\python.exe') -ArgumentList 'cli.py','serve' -WorkingDirectory $d -WindowStyle Hidden -RedirectStandardOutput (Join-Path $d 'serve.log') -RedirectStandardError (Join-Path $d 'serve.err.log') -PassThru; Write-Output $p.Id" > "%TEMP%\zcode_hub_pid.txt" 2>nul +set "NEWPID=" +set /p NEWPID=<"%TEMP%\zcode_hub_pid.txt" +del "%TEMP%\zcode_hub_pid.txt" >nul 2>&1 +if not defined NEWPID ( + echo [!] Failed to launch python process. + goto :fail +) +(echo %NEWPID%)>"serve.pid" + +rem -- 6. Health check (up to ~15s) -------------------------------------- +set /a TRIES=0 +:health_wait +ping -n 2 127.0.0.1 >nul +curl -sf -m 2 "http://127.0.0.1:%PORT%/meta" >nul 2>&1 && goto :started +set /a TRIES+=1 +if %TRIES% lss 15 goto :health_wait + +echo [!] Start FAILED. Last log lines: +echo --- serve.log --- +powershell -NoProfile -Command "if (Test-Path serve.log) { Get-Content serve.log -Tail 15 }" +echo --- serve.err.log --- +powershell -NoProfile -Command "if (Test-Path serve.err.log) { Get-Content serve.err.log -Tail 15 }" +goto :fail + +:started +echo [OK] Started: http://127.0.0.1:%PORT% +echo Admin UI : http://127.0.0.1:%PORT%/admin/login +echo Logs : serve.log + serve.err.log +echo Stop : start.bat stop Restart: just run start.bat again +if "%DBLCLK%"=="1" pause +exit /b 0 + +:fail +echo [!] start.bat aborted. +if "%DBLCLK%"=="1" pause +exit /b 1 + +:stop_instance +set "OLDPID=" +if exist serve.pid for /f %%p in (serve.pid) do set "OLDPID=%%p" +if not defined OLDPID goto :stop_cleanup +tasklist /FI "PID eq %OLDPID%" /NH 2>nul | find /i "python.exe" >nul 2>&1 || goto :stop_cleanup +echo [*] Stopping old instance (pid=%OLDPID%) ... +taskkill /PID %OLDPID% >nul 2>&1 +set /a N=0 +:stop_wait +tasklist /FI "PID eq %OLDPID%" /NH 2>nul | find /i "python.exe" >nul 2>&1 || goto :stop_cleanup +set /a N+=1 +if %N% geq 10 goto :stop_force +ping -n 2 127.0.0.1 >nul +goto :stop_wait +:stop_force +taskkill /F /PID %OLDPID% >nul 2>&1 +:stop_cleanup +if exist serve.pid del serve.pid >nul 2>&1 +goto :eof diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..9e1beaa --- /dev/null +++ b/start.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +# zcode-hub Linux 启动脚本(对应 Windows 的 start.bat) +# +# 用法: +# ./start.sh 启动(若已在运行则先停旧实例再启动,同时清除后台登录失败锁定) +# ./start.sh stop 停止 +set -euo pipefail + +cd "$(dirname "$(readlink -f "$0")")" + +# ── 可按需修改 ────────────────────────────────────────────── +# 上游代理(Clash 混合端口);直连环境把它留空即可 +PROXY="http://127.0.0.1:7890" +# ──────────────────────────────────────────────────────────── + +PORT=$(grep -E '^ZCODE_PORT=' .env 2>/dev/null | cut -d= -f2 | tr -d '[:space:]') +PORT=${PORT:-3000} + +stop_instance() { + local old_pid + old_pid=$(cat serve.pid 2>/dev/null || true) + if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then + echo "[*] 停止旧实例 (pid=$old_pid)..." + kill "$old_pid" 2>/dev/null || true + for _ in $(seq 1 10); do + kill -0 "$old_pid" 2>/dev/null || break + sleep 1 + done + kill -9 "$old_pid" 2>/dev/null || true + fi + rm -f serve.pid +} + +if [ "${1:-}" = "stop" ]; then + stop_instance + echo "[✔] 已停止" + exit 0 +fi + +# 1. Python 虚拟环境:缺失则创建,依赖缺失则安装 +if [ ! -x .venv/bin/python ]; then + echo "[*] 创建虚拟环境 (.venv)..." + python3 -m venv .venv +fi +if ! .venv/bin/python -c "import fastapi, uvicorn, httpx" 2>/dev/null; then + echo "[*] 安装 Python 依赖..." + if [ -n "$PROXY" ]; then + .venv/bin/pip install -q --proxy "$PROXY" -r requirements.txt + else + .venv/bin/pip install -q -r requirements.txt + fi +fi + +# 2. 验证码求解 Node 依赖(JWT 账号需要) +if [ ! -d captcha_node/node_modules ]; then + echo "[*] 安装 captcha_node 依赖 (npm install)..." + (cd captcha_node && npm install --no-audit --no-fund) +fi + +# 3. 已在运行则先停(重启同时清掉内存里的后台登录失败锁定) +stop_instance + +# 4. 端口仍被其他进程占用则报错 +if ss -tln 2>/dev/null | grep -q ":${PORT} "; then + echo "[!] 端口 ${PORT} 仍被其他进程占用,请手动处理:" >&2 + ss -tlnp 2>/dev/null | grep ":${PORT} " >&2 || true + exit 1 +fi + +# 5. 启动(不设 ALL_PROXY/socks —— httpx 缺 socks 支持会报 Unknown scheme) +echo "[*] 启动 zcode-hub (port=${PORT})..." +if [ -n "$PROXY" ]; then + env "HTTP_PROXY=${PROXY}/" "HTTPS_PROXY=${PROXY}/" \ + nohup .venv/bin/python cli.py serve > serve.log 2>&1 & +else + nohup .venv/bin/python cli.py serve > serve.log 2>&1 & +fi +echo $! > serve.pid + +# 6. 健康检查 +for _ in $(seq 1 15); do + sleep 1 + if curl -sf -m 2 "http://127.0.0.1:${PORT}/meta" >/dev/null 2>&1; then + echo "[✔] 已启动: http://127.0.0.1:${PORT}" + echo " 后台: http://127.0.0.1:${PORT}/admin/login" + echo " 日志: serve.log 停止: ./start.sh stop" + exit 0 + fi +done + +echo "[!] 启动失败,最近日志:" >&2 +tail -20 serve.log >&2 +exit 1 From 50f3ca72f64d02134e95cf06283deda7bf984253 Mon Sep 17 00:00:00 2001 From: jk <2081217232@qq.com> Date: Sun, 4 Oct 2026 11:07:22 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix(scripts):=20start.sh=20=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E5=89=8D=E5=89=94=E9=99=A4=20ALL=5FPROXY=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20Clash=20=E7=B3=BB=E7=BB=9F=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E6=B3=A8=E5=85=A5=E7=9A=84=20socks://=20=E4=BC=9A=E8=AE=A9=20h?= =?UTF-8?q?ttpx=20=E5=85=A8=E9=87=8F=20500?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- start.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/start.sh b/start.sh index 9e1beaa..15df39e 100755 --- a/start.sh +++ b/start.sh @@ -67,7 +67,9 @@ if ss -tln 2>/dev/null | grep -q ":${PORT} "; then exit 1 fi -# 5. 启动(不设 ALL_PROXY/socks —— httpx 缺 socks 支持会报 Unknown scheme) +# 5. 启动。剔除环境里已有的 ALL_PROXY/socks(Clash 系统代理常注入 socks://, +# httpx 缺 socks 支持会报 Unknown scheme → 全部请求 500);仅留 HTTP(S)_PROXY +unset ALL_PROXY all_proxy echo "[*] 启动 zcode-hub (port=${PORT})..." if [ -n "$PROXY" ]; then env "HTTP_PROXY=${PROXY}/" "HTTPS_PROXY=${PROXY}/" \