Skip to content

Commit e4e5047

Browse files
fix: Fix issues of startup failure and tool running failure in Windows environment
1 parent a141d0d commit e4e5047

4 files changed

Lines changed: 181 additions & 20 deletions

File tree

apps/common/utils/tool_code.py

Lines changed: 68 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@
55
import gzip
66
import json
77
import os
8-
import pwd
8+
try:
9+
import pwd
10+
except ImportError:
11+
pwd = None
912
import random
10-
import resource
13+
try:
14+
import resource
15+
except ImportError:
16+
resource = None
1117
import socket
1218
import subprocess
1319
import sys
@@ -24,6 +30,8 @@
2430
from maxkb.const import BASE_DIR, CONFIG
2531
from maxkb.const import PROJECT_DIR
2632

33+
IS_WINDOWS = sys.platform.startswith('win')
34+
2735
_enable_sandbox = bool(int(CONFIG.get('SANDBOX', 0)))
2836
_run_user = 'sandbox' if _enable_sandbox else getpass.getuser()
2937
_sandbox_path = CONFIG.get("SANDBOX_HOME", '/opt/maxkb-app/sandbox') if _enable_sandbox else os.path.join(PROJECT_DIR, 'data', 'sandbox')
@@ -89,7 +97,10 @@ def init_sandbox_dir():
8997
def exec_code(self, code_str, keywords, function_name=None):
9098
_id = str(uuid.uuid7())
9199
action_function = f'({function_name !a}, locals_v.get({function_name !a}))' if function_name else 'locals_v.popitem()'
92-
set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});' if _enable_sandbox else ''
100+
if pwd and _enable_sandbox:
101+
set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});'
102+
else:
103+
set_run_user = ''
93104
_exec_code = f"""
94105
try:
95106
import os, sys, json
@@ -118,11 +129,31 @@ def exec_code(self, code_str, keywords, function_name=None):
118129
sys.stdout.flush()
119130
"""
120131
maxkb_logger.debug(f"Tool execution({_id}) execute code: {_exec_code}")
121-
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=True) as f:
122-
f.write(_exec_code)
123-
f.flush()
124-
with execution_timer(_id):
125-
subprocess_result = self._exec(f.name, _id)
132+
133+
# Windows 需要特殊处理临时文件
134+
if IS_WINDOWS:
135+
# 在 Windows 上,创建临时文件后需要关闭才能被 subprocess 访问
136+
temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False)
137+
try:
138+
temp_file.write(_exec_code)
139+
temp_file.flush()
140+
temp_file.close() # 关闭文件以便 subprocess 可以访问
141+
with execution_timer(_id):
142+
subprocess_result = self._exec(temp_file.name, _id)
143+
finally:
144+
# 执行完成后删除临时文件
145+
try:
146+
os.unlink(temp_file.name)
147+
except:
148+
pass
149+
else:
150+
# 其他环境,直接执行
151+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=True) as f:
152+
f.write(_exec_code)
153+
f.flush()
154+
with execution_timer(_id):
155+
subprocess_result = self._exec(f.name, _id)
156+
126157
if subprocess_result.returncode != 0:
127158
raise Exception(subprocess_result.stderr or subprocess_result.stdout or "Unknown exception occurred")
128159
lines = subprocess_result.stdout.splitlines()
@@ -265,7 +296,10 @@ def visit_Return(self, node):
265296

266297
def generate_mcp_server_code(self, code_str, params, name, description, tool_id):
267298
code = self._generate_mcp_server_code(code_str, params, name, description, tool_id)
268-
set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});' if _enable_sandbox else ''
299+
if pwd and _enable_sandbox:
300+
set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});'
301+
else:
302+
set_run_user = ''
269303
return f"""
270304
import os, sys, logging
271305
logging.basicConfig(level=logging.WARNING)
@@ -313,18 +347,32 @@ def _exec(self, execute_file, _id):
313347
'_ID': _id,
314348
}}
315349
def _set_resource_limit():
316-
if not _enable_sandbox or not sys.platform.startswith("linux"): return
317-
with suppress(Exception): resource.setrlimit(resource.RLIMIT_AS, (_process_limit_mem_mb * 1024 * 1024,) * 2)
318-
with suppress(Exception): os.sched_setaffinity(0, set(random.sample(list(os.sched_getaffinity(0)), _process_limit_cpu_cores)))
350+
if not _enable_sandbox or IS_WINDOWS or not resource:
351+
return
352+
with suppress(Exception):
353+
resource.setrlimit(resource.RLIMIT_AS, (_process_limit_mem_mb * 1024 * 1024,) * 2)
354+
with suppress(Exception):
355+
if hasattr(os, 'sched_setaffinity') and hasattr(os, 'sched_getaffinity'):
356+
os.sched_setaffinity(0, set(random.sample(list(os.sched_getaffinity(0)), _process_limit_cpu_cores)))
319357
try:
320-
subprocess_result = subprocess.run(
321-
[sys.executable, execute_file],
322-
timeout=_process_limit_timeout_seconds,
323-
text=True,
324-
capture_output=True,
325-
**kwargs,
326-
preexec_fn=_set_resource_limit
327-
)
358+
# Windows 不支持 preexec_fn 参数
359+
if IS_WINDOWS:
360+
subprocess_result = subprocess.run(
361+
[sys.executable, execute_file],
362+
timeout=_process_limit_timeout_seconds,
363+
text=True,
364+
capture_output=True,
365+
**kwargs
366+
)
367+
else:
368+
subprocess_result = subprocess.run(
369+
[sys.executable, execute_file],
370+
timeout=_process_limit_timeout_seconds,
371+
text=True,
372+
capture_output=True,
373+
**kwargs,
374+
preexec_fn=_set_resource_limit
375+
)
328376
return subprocess_result
329377
except subprocess.TimeoutExpired:
330378
raise Exception(_(f"Process execution timed out after {_process_limit_timeout_seconds} seconds."))

scripts/windows/1_init.md

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# 回到根目录
2+
```shell
3+
cd ../../
4+
```
5+
6+
# 安装 virtualenv
7+
```shell
8+
pip install virtualenv
9+
```
10+
11+
# 创建虚拟环境
12+
```shell
13+
python -m venv venv
14+
```
15+
16+
# 激活虚拟环境 (Windows)
17+
```shell
18+
venv\Scripts\activate
19+
```
20+
21+
22+
23+
# 安装 uv (推荐的 Python 包管理器)
24+
```shell
25+
pip install uv
26+
```
27+
28+
# 使用 uv 安装项目依赖
29+
```shell
30+
uv pip install -e .
31+
```
32+
33+
34+
35+
# 创建 .env 文件
36+
```shell
37+
@echo off
38+
chcp 65001 >nul
39+
setlocal
40+
cd ../../
41+
:: 检查 .env 是否已经存在
42+
if exist ".env" (
43+
echo ==============================================
44+
echo 提示:.env 文件已存在,无需重复创建
45+
echo ==============================================
46+
pause >nul
47+
exit /b
48+
)
49+
50+
:: 文件不存在,开始创建并写入内容
51+
echo ==============================================
52+
echo 正在生成 .env 配置文件...
53+
echo ==============================================
54+
55+
:: 先创建空文件
56+
type nul > .env
57+
58+
:: 写入自定义环境变量,自行修改这里的内容即可
59+
echo.> .env
60+
echo # 数据库配置> .env
61+
echo MAXKB_DB_NAME=maxkb>> .env
62+
echo MAXKB_DB_HOST=127.0.0.1>> .env
63+
echo MAXKB_DB_PORT=5432>> .env
64+
echo MAXKB_DB_USER=root>> .env
65+
echo MAXKB_DB_PASSWORD=Password123@postgres>> .env
66+
echo.>> .env
67+
echo # Redis 配置>> .env
68+
echo MAXKB_REDIS_HOST=127.0.0.1>> .env
69+
echo MAXKB_REDIS_PORT=6379>> .env
70+
echo MAXKB_REDIS_PASSWORD=Password123@redis>> .env
71+
echo MAXKB_REDIS_DB=0>> .env
72+
echo MAXKB_REDIS_MAX_CONNECTIONS=100>> .env
73+
echo.>> .env
74+
echo # 其他配置>> .env
75+
echo MAXKB_CONFIG_TYPE=ENV>> .env
76+
echo MAXKB_DEBUG=True>> .env
77+
echo MAXKB_LANGUAGE_CODE=zh-CN>> .env
78+
echo MAXKB_TIME_ZONE=Asia/Shanghai>> .env
79+
80+
echo.
81+
echo ✅ .env 文件创建并初始化完成,请按实际情况进行修改!
82+
echo.
83+
pause >nul
84+
```

scripts/windows/2_start_backend.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# 回到根目录
2+
```shell
3+
cd ../../
4+
```
5+
6+
# 激活虚拟环境 (Windows)
7+
```shell
8+
venv\Scripts\activate
9+
```
10+
11+
# 启动后端服务
12+
```shell
13+
python main.py dev web celery
14+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# 进入UI目录
2+
```shell
3+
cd ../../ui
4+
```
5+
6+
# 启动前端UI Web
7+
8+
```shell
9+
npm run dev
10+
```
11+
12+
13+
```shell
14+
yarn dev
15+
```

0 commit comments

Comments
 (0)