Publish GitHub 5fccb3e release tree

This commit is contained in:
lizhongyi1209
2026-09-24 10:59:26 +00:00
parent 3a05bb6277
commit 3e337722ab
5 changed files with 44 additions and 122 deletions
+34 -21
View File
@@ -14,28 +14,36 @@
## 📦 安装 ## 📦 安装
### 通过公开 Gitea 仓库安装(无需登录 ### 方法一:通过 ComfyUI Manager(推荐
1. 在 ComfyUI 中打开 Manager
2. 搜索 `Comfyui_o1key`
3. 点击安装
4. 重启 ComfyUI
### 方法二:手动安装
```bash ```bash
cd ComfyUI/custom_nodes cd ComfyUI/custom_nodes
git clone https://git.o1key.com/publisher/comfyui_o1key.git git clone https://github.com/lizhongyi1209/comfyui_o1key.git
cd comfyui_o1key cd comfyui_o1key
python -m pip install -r requirements.txt pip install -r requirements.txt
``` ```
请在 ComfyUI 使用的 Python 环境中运行安装命令,然后重启 ComfyUI。之后可直接使用左侧功能栏的「更新」按钮 然后重启 ComfyUI
### 已通过 GitHub 安装的用户 ### 国内用户安装(GitHub 拉取慢或失败时)
在节点包目录中把更新地址切换到 Gitea,即可继续保留现有安装和本地配置 使用 Gitee 镜像安装与更新,避免网络问题
```bash ```bash
cd ComfyUI/custom_nodes/comfyui_o1key cd ComfyUI/custom_nodes
git remote set-url origin https://git.o1key.com/publisher/comfyui_o1key.git git clone https://gitee.com/resonLzy/comfyui_o1key.git
git pull --ff-only origin main cd comfyui_o1key
pip install -r requirements.txt
``` ```
仓库公开读取,无需为用户配置 Gitea 账号或令牌 自动更新脚本(见下方「更新插件」)已改为从 Gitee 拉取,国内用户可直接使用
--- ---
@@ -126,10 +134,16 @@ O1KEY_API_KEY=你的API密钥
### 界面更新 ### 界面更新
本机 ComfyUI 左侧功能栏点击「更新」(位于「重启」下方)。按钮会从当前 Git 仓库的 `origin/main` 拉取最新版本;依赖列表有变化时,会在 ComfyUI 当前 Python 环境中自动安装。完成后点击「重启」使新版本生效。 在 ComfyUI 左侧功能栏点击「更新」(位于「重启」下方)。按钮会从当前 Git 仓库的 `origin/main` 拉取最新版本。完成后点击「重启」使新版本生效。
界面更新需要通过 Git 安装、处于 `main` 分支,且节点包文件没有本地修改。更新仅允许快进,不会覆盖本地修改或删除配置。ZIP 安装、分支分叉或网络连接失败时,界面会显示原因,需要手动处理。 界面更新需要通过 Git 安装、处于 `main` 分支,且节点包文件没有本地修改。更新仅允许快进,不会覆盖本地修改或删除配置。ZIP 安装、分支分叉或网络连接失败时,界面会显示原因,需要手动处理。
为了防止其他网站或局域网设备触发本机的软件管理操作,「更新」和「重启」按钮只能在本机打开的 ComfyUI 页面使用。
如果提示依赖列表已变化,请在 ComfyUI 使用的 Python 环境中执行:
```bash
cd ComfyUI/custom_nodes/comfyui_o1key
python -m pip install -r requirements.txt
```
### 手动更新 ### 手动更新
@@ -188,27 +202,26 @@ Google Gemini 模型节点,支持更多模型选择。
## ⚠️ 开发者注意事项 ## ⚠️ 开发者注意事项
### 维护者:发布流程 ### 维护者:发布流程与镜像同步
`git.o1key.com` 是插件的公开发布仓库,用户的「更新」按钮从安装目录的 `origin/main` 获取版本。开发电脑首次切换发布地址并同步服务器已有更新: 代码**先提交并推送到 GitHub**,再**同步到 Gitee 镜像**,国内用户通过 Gitee 拉取以解决网络问题。
**首次配置**(仅需一次):
```bash ```bash
git remote set-url origin https://git.o1key.com/publisher/comfyui_o1key.git git remote add gitee https://gitee.com/resonLzy/comfyui_o1key.git
git pull --ff-only origin main
``` ```
**每次发布** **每次发布**
```bash ```bash
git push origin main git push origin main # 先更新 GitHub
git push gitee main # 再同步到 Gitee 镜像
``` ```
推送需要发布者的 Gitea 凭据;普通用户匿名拉取和界面更新无需登录。
### 文件编码要求 ### 文件编码要求
**所有文本文件必须使用 UTF-8 编码(无 BOM)!** **所有文本文件必须使用 UTF-8 编码(无 BOM)!**
如果你在仓库网页上看到中文乱码,说明文件编码有问题。请使用以下方法修复: 如果你在 GitHub 上看到中文乱码,说明文件编码有问题。请使用以下方法修复:
**Windows 用户:** **Windows 用户:**
@@ -230,7 +243,7 @@ chmod +x fix_encoding.sh
## 📮 联系方式 ## 📮 联系方式
- GitHub: [@lizhongyi1209](https://github.com/lizhongyi1209) - GitHub: [@lizhongyi1209](https://github.com/lizhongyi1209)
- 项目地址: https://git.o1key.com/publisher/comfyui_o1key - 项目地址: https://github.com/lizhongyi1209/comfyui_o1key
--- ---
+2 -4
View File
@@ -208,7 +208,7 @@ try:
from server import PromptServer from server import PromptServer
import folder_paths import folder_paths
from .utils.config import CONFIG_FILE, load_config, NETWORK_ROUTES from .utils.config import CONFIG_FILE, load_config, NETWORK_ROUTES
from .utils.updater import UpdateError, is_local_management_request, update_package from .utils.updater import UpdateError, update_package
import threading as _update_threading import threading as _update_threading
_update_lock = _update_threading.Lock() _update_lock = _update_threading.Lock()
@@ -618,7 +618,7 @@ try:
@PromptServer.instance.routes.post("/o1key/update") @PromptServer.instance.routes.post("/o1key/update")
async def update_node_package(request): async def update_node_package(request):
if not is_local_management_request(request) or request.headers.get("X-O1Key-Update") != "1": if request.headers.get("X-O1Key-Update") != "1":
return web.json_response({"error": "无效的更新请求。"}, status=403) return web.json_response({"error": "无效的更新请求。"}, status=403)
if not _update_lock.acquire(blocking=False): if not _update_lock.acquire(blocking=False):
return web.json_response({"error": "更新正在进行,请稍候。"}, status=409) return web.json_response({"error": "更新正在进行,请稍候。"}, status=409)
@@ -636,8 +636,6 @@ try:
# === AI 聊天代理(流式 SSE 透传) === # === AI 聊天代理(流式 SSE 透传) ===
@PromptServer.instance.routes.post("/o1key/restart") @PromptServer.instance.routes.post("/o1key/restart")
async def restart_server(request): async def restart_server(request):
if not is_local_management_request(request):
return web.json_response({"error": "仅允许在本机界面重启 ComfyUI。"}, status=403)
import sys, os as _ros, subprocess, threading import sys, os as _ros, subprocess, threading
def _do_restart(): def _do_restart():
import time import time
-32
View File
@@ -5,7 +5,6 @@ import subprocess
import tempfile import tempfile
import unittest import unittest
from pathlib import Path from pathlib import Path
from unittest.mock import Mock, patch
UPDATER_PATH = Path(__file__).resolve().parents[1] / "utils" / "updater.py" UPDATER_PATH = Path(__file__).resolve().parents[1] / "utils" / "updater.py"
@@ -46,24 +45,11 @@ class UpdaterTests(unittest.TestCase):
(self.author / "version.txt").write_text("2\n", encoding="utf-8") (self.author / "version.txt").write_text("2\n", encoding="utf-8")
(self.author / "requirements.txt").write_text("requests>=3\n", encoding="utf-8") (self.author / "requirements.txt").write_text("requests>=3\n", encoding="utf-8")
self.commit_and_push() self.commit_and_push()
with patch.object(updater, "_install_requirements") as install:
result = updater.update_package() result = updater.update_package()
install.assert_called_once_with("requests>=3\n")
self.assertTrue(result["updated"]) self.assertTrue(result["updated"])
self.assertTrue(result["requirements_changed"]) self.assertTrue(result["requirements_changed"])
self.assertEqual((self.install / "version.txt").read_text(encoding="utf-8"), "2\n") self.assertEqual((self.install / "version.txt").read_text(encoding="utf-8"), "2\n")
def test_dependency_failure_keeps_previous_code(self):
old_commit = self.git(self.install, "rev-parse", "HEAD")
(self.author / "requirements.txt").write_text("requests>=3\n", encoding="utf-8")
(self.author / "version.txt").write_text("2\n", encoding="utf-8")
self.commit_and_push()
with patch.object(updater, "_install_requirements", side_effect=updater.UpdateError("依赖安装失败")):
with self.assertRaisesRegex(updater.UpdateError, "依赖安装失败"):
updater.update_package()
self.assertEqual(self.git(self.install, "rev-parse", "HEAD"), old_commit)
self.assertEqual((self.install / "version.txt").read_text(encoding="utf-8"), "1\n")
def test_local_changes_are_preserved(self): def test_local_changes_are_preserved(self):
(self.install / "version.txt").write_text("local\n", encoding="utf-8") (self.install / "version.txt").write_text("local\n", encoding="utf-8")
with self.assertRaisesRegex(updater.UpdateError, "本地修改"): with self.assertRaisesRegex(updater.UpdateError, "本地修改"):
@@ -82,24 +68,6 @@ class UpdaterTests(unittest.TestCase):
updater.update_package() updater.update_package()
self.assertEqual((self.install / "version.txt").read_text(encoding="utf-8"), "local commit\n") self.assertEqual((self.install / "version.txt").read_text(encoding="utf-8"), "local commit\n")
def test_management_request_requires_local_peer_and_same_origin(self):
cases = [
("local browser", "127.0.0.1", "127.0.0.1:8188", "http://127.0.0.1:8188", True),
("localhost", "::1", "localhost:8188", "http://localhost:8188", True),
("remote peer", "192.168.1.2", "127.0.0.1:8188", "http://127.0.0.1:8188", False),
("cross origin", "127.0.0.1", "127.0.0.1:8188", "http://evil.example", False),
("missing origin", "127.0.0.1", "127.0.0.1:8188", "", False),
("malformed origin", "127.0.0.1", "127.0.0.1:8188", "http://[", False),
("rebinding host", "127.0.0.1", "evil.example:8188", "http://evil.example:8188", False),
]
for name, peer, host, origin, expected in cases:
with self.subTest(name=name):
request = Mock()
request.transport.get_extra_info.return_value = (peer, 12345)
request.headers = {"Host": host, "Origin": origin}
request.scheme = "http"
self.assertEqual(updater.is_local_management_request(request), expected)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+1 -60
View File
@@ -1,12 +1,8 @@
"""Safely fast-forward a Git installation of this node package.""" """Safely fast-forward a Git installation of this node package."""
import ipaddress
import os import os
import subprocess import subprocess
import sys
import tempfile
from pathlib import Path from pathlib import Path
from urllib.parse import urlsplit
PLUGIN_DIR = Path(__file__).resolve().parent.parent PLUGIN_DIR = Path(__file__).resolve().parent.parent
@@ -16,35 +12,6 @@ class UpdateError(Exception):
pass pass
def is_local_management_request(request):
"""Allow software management only from the local ComfyUI browser."""
peer = request.transport.get_extra_info("peername") if request.transport else None
if not peer:
return False
try:
if not ipaddress.ip_address(peer[0]).is_loopback:
return False
host = request.headers.get("Host", "")
host_url = urlsplit(f"http://{host}")
hostname = host_url.hostname
if host_url.username or host_url.password or host_url.path or host_url.query or host_url.fragment:
return False
if hostname != "localhost" and not ipaddress.ip_address(hostname).is_loopback:
return False
origin = request.headers.get("Origin", "")
parsed_origin = urlsplit(origin)
except (ValueError, TypeError):
return False
return (
parsed_origin.scheme == request.scheme
and parsed_origin.netloc.lower() == host.lower()
and not parsed_origin.path
and not parsed_origin.query
and not parsed_origin.fragment
)
def _git(*args, timeout=60, check=True): def _git(*args, timeout=60, check=True):
env = os.environ.copy() env = os.environ.copy()
env["GIT_TERMINAL_PROMPT"] = "0" env["GIT_TERMINAL_PROMPT"] = "0"
@@ -70,29 +37,6 @@ def _git(*args, timeout=60, check=True):
return result return result
def _install_requirements(requirements):
fd, filename = tempfile.mkstemp(prefix=".o1key-requirements-", suffix=".txt", dir=PLUGIN_DIR)
try:
with os.fdopen(fd, "w", encoding="utf-8") as req_file:
req_file.write(requirements)
try:
result = subprocess.run(
[sys.executable, "-m", "pip", "install", "-r", filename],
cwd=PLUGIN_DIR,
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=600,
)
except subprocess.TimeoutExpired as exc:
raise UpdateError("依赖安装超时,节点代码尚未更新,请检查网络后重试。") from exc
if result.returncode:
raise UpdateError("依赖安装失败,节点代码尚未更新,请检查 ComfyUI 的 Python 环境后重试。")
finally:
Path(filename).unlink(missing_ok=True)
def update_package(): def update_package():
"""Update origin/main without discarding local changes or switching branches.""" """Update origin/main without discarding local changes or switching branches."""
if not (PLUGIN_DIR / ".git").exists(): if not (PLUGIN_DIR / ".git").exists():
@@ -115,11 +59,8 @@ def update_package():
if _git("merge-base", "--is-ancestor", "HEAD", "FETCH_HEAD", check=False).returncode: if _git("merge-base", "--is-ancestor", "HEAD", "FETCH_HEAD", check=False).returncode:
raise UpdateError("本地与 origin/main 已分叉,无法安全快进。请手动处理。") raise UpdateError("本地与 origin/main 已分叉,无法安全快进。请手动处理。")
new_requirements = _git("show", "FETCH_HEAD:requirements.txt", check=False).stdout
requirements_changed = old_requirements != new_requirements
if requirements_changed and new_requirements.strip():
_install_requirements(new_requirements)
_git("merge", "--ff-only", "FETCH_HEAD") _git("merge", "--ff-only", "FETCH_HEAD")
requirements_changed = old_requirements != (PLUGIN_DIR / "requirements.txt").read_text(encoding="utf-8")
return { return {
"updated": True, "updated": True,
"version": new_commit[:7], "version": new_commit[:7],
+6 -4
View File
@@ -57,11 +57,11 @@ app.registerExtension({
let updating = false; let updating = false;
updateBtn.addEventListener("click", async () => { updateBtn.addEventListener("click", async () => {
if (updating) return; if (updating) return;
if (!confirm("更新 comfyui_o1key 到发布仓库的最新版本?若依赖有变化,也会自动安装,可能需要几分钟。")) return; if (!confirm("从 origin/main 拉取 comfyui_o1key 最新版本?")) return;
updating = true; updating = true;
updateBtn.disabled = true; updateBtn.disabled = true;
updateBtn.style.opacity = "0.5"; updateBtn.style.opacity = "0.5";
updateBtn.title = "正在拉取代码并安装所需依赖..."; updateBtn.title = "正在更新...";
try { try {
const response = await fetch("/o1key/update", { const response = await fetch("/o1key/update", {
method: "POST", method: "POST",
@@ -72,8 +72,10 @@ app.registerExtension({
if (!result.updated) { if (!result.updated) {
alert(`已是最新版本(${result.version})。`); alert(`已是最新版本(${result.version})。`);
} else { } else {
const dependencies = result.requirements_changed ? "依赖已同步。\n" : ""; const dependencies = result.requirements_changed
alert(`更新完成(${result.version})。\n${dependencies}请点击“重启”使新版本生效。`); ? "\n依赖列表已变化,请先在 ComfyUI 的 Python 环境中执行 pip install -r requirements.txt。"
: "";
alert(`更新完成(${result.version})。${dependencies}\n请点击“重启”使新版本生效。`);
} }
} catch (error) { } catch (error) {
alert(`更新失败:${error.message}`); alert(`更新失败:${error.message}`);