Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 67 additions & 22 deletions apps/common/utils/tool_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
import gzip
import json
import os
import pwd
try:
import pwd
except ImportError:
pwd = None
import random
import resource
try:
import resource
except ImportError:
resource = None
import socket
import subprocess
import sys
Expand All @@ -24,8 +30,10 @@
from maxkb.const import BASE_DIR, CONFIG
from maxkb.const import PROJECT_DIR

IS_WINDOWS = sys.platform.startswith('win')

_enable_sandbox = bool(int(CONFIG.get('SANDBOX', 0)))
_run_user = 'sandbox' if _enable_sandbox else getpass.getuser()
_run_user = 'sandbox' if _enable_sandbox and pwd else getpass.getuser()
_sandbox_path = CONFIG.get("SANDBOX_HOME", '/opt/maxkb-app/sandbox') if _enable_sandbox else os.path.join(PROJECT_DIR, 'data', 'sandbox')
_sandbox_python_sys_path = CONFIG.get_sandbox_python_package_paths().split(',')
_process_limit_timeout_seconds = int(CONFIG.get("SANDBOX_PYTHON_PROCESS_LIMIT_TIMEOUT_SECONDS", '3600'))
Expand Down Expand Up @@ -89,7 +97,10 @@ def init_sandbox_dir():
def exec_code(self, code_str, keywords, function_name=None):
_id = str(uuid.uuid7())
action_function = f'({function_name !a}, locals_v.get({function_name !a}))' if function_name else 'locals_v.popitem()'
set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});' if _enable_sandbox else ''
if _enable_sandbox and pwd:
set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});'
else:
set_run_user = ''
_exec_code = f"""
try:
import os, sys, json
Expand Down Expand Up @@ -118,11 +129,31 @@ def exec_code(self, code_str, keywords, function_name=None):
sys.stdout.flush()
"""
maxkb_logger.debug(f"Tool execution({_id}) execute code: {_exec_code}")
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=True) as f:
f.write(_exec_code)
f.flush()
with execution_timer(_id):
subprocess_result = self._exec(f.name, _id)

# Windows 需要特殊处理临时文件
if IS_WINDOWS:
# 在 Windows 上,创建临时文件后需要关闭才能被 subprocess 访问
temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False)
try:
temp_file.write(_exec_code)
temp_file.flush()
temp_file.close() # 关闭文件以便 subprocess 可以访问
with execution_timer(_id):
subprocess_result = self._exec(temp_file.name, _id)
finally:
# 执行完成后删除临时文件
try:
os.unlink(temp_file.name)
except:
pass
else:
# 其他环境,直接执行
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=True) as f:
f.write(_exec_code)
f.flush()
with execution_timer(_id):
subprocess_result = self._exec(f.name, _id)

if subprocess_result.returncode != 0:
raise Exception(subprocess_result.stderr or subprocess_result.stdout or "Unknown exception occurred")
lines = subprocess_result.stdout.splitlines()
Expand Down Expand Up @@ -265,7 +296,10 @@ def visit_Return(self, node):

def generate_mcp_server_code(self, code_str, params, name, description, tool_id):
code = self._generate_mcp_server_code(code_str, params, name, description, tool_id)
set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});' if _enable_sandbox else ''
if _enable_sandbox and pwd:
set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});'
else:
set_run_user = ''
return f"""
import os, sys, logging
logging.basicConfig(level=logging.WARNING)
Expand Down Expand Up @@ -312,19 +346,30 @@ def _exec(self, execute_file, _id):
'LD_PRELOAD': f'{_sandbox_path}/lib/sandbox.so',
'_ID': _id,
}}
def _set_resource_limit():
if not _enable_sandbox or not sys.platform.startswith("linux"): return
with suppress(Exception): resource.setrlimit(resource.RLIMIT_AS, (_process_limit_mem_mb * 1024 * 1024,) * 2)
with suppress(Exception): os.sched_setaffinity(0, set(random.sample(list(os.sched_getaffinity(0)), _process_limit_cpu_cores)))
try:
subprocess_result = subprocess.run(
[sys.executable, execute_file],
timeout=_process_limit_timeout_seconds,
text=True,
capture_output=True,
**kwargs,
preexec_fn=_set_resource_limit
)
# Windows 不支持 preexec_fn 参数
if IS_WINDOWS:
subprocess_result = subprocess.run(
[sys.executable, execute_file],
timeout=_process_limit_timeout_seconds,
text=True,
capture_output=True,
**kwargs
)
else:
def _set_resource_limit():
if not _enable_sandbox or not sys.platform.startswith("linux"): return
if resource:
with suppress(Exception): resource.setrlimit(resource.RLIMIT_AS, (_process_limit_mem_mb * 1024 * 1024,) * 2)
with suppress(Exception): os.sched_setaffinity(0, set(random.sample(list(os.sched_getaffinity(0)), _process_limit_cpu_cores)))
subprocess_result = subprocess.run(
[sys.executable, execute_file],
timeout=_process_limit_timeout_seconds,
text=True,
capture_output=True,
**kwargs,
preexec_fn=_set_resource_limit
)
return subprocess_result
except subprocess.TimeoutExpired:
raise Exception(_(f"Process execution timed out after {_process_limit_timeout_seconds} seconds."))
Expand Down
84 changes: 84 additions & 0 deletions scripts/windows/1_init.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# 回到根目录
```shell
cd ../../
```

# 安装 virtualenv
```shell
pip install virtualenv
```

# 创建虚拟环境
```shell
python -m venv venv
```

# 激活虚拟环境 (Windows)
```shell
venv\Scripts\activate
```



# 安装 uv (推荐的 Python 包管理器)
```shell
pip install uv
```

# 使用 uv 安装项目依赖
```shell
uv pip install -e .
```



# 创建 .env 文件
```shell
@echo off
chcp 65001 >nul
setlocal
cd ../../
:: 检查 .env 是否已经存在
if exist ".env" (
echo ==============================================
echo 提示:.env 文件已存在,无需重复创建
echo ==============================================
pause >nul
exit /b
)

:: 文件不存在,开始创建并写入内容
echo ==============================================
echo 正在生成 .env 配置文件...
echo ==============================================

:: 先创建空文件
type nul > .env

:: 写入自定义环境变量,自行修改这里的内容即可
echo.> .env
echo # 数据库配置> .env
echo MAXKB_DB_NAME=maxkb>> .env
echo MAXKB_DB_HOST=127.0.0.1>> .env
echo MAXKB_DB_PORT=5432>> .env
echo MAXKB_DB_USER=root>> .env
echo MAXKB_DB_PASSWORD=Password123@postgres>> .env
echo.>> .env
echo # Redis 配置>> .env
echo MAXKB_REDIS_HOST=127.0.0.1>> .env
echo MAXKB_REDIS_PORT=6379>> .env
echo MAXKB_REDIS_PASSWORD=Password123@redis>> .env
echo MAXKB_REDIS_DB=0>> .env
echo MAXKB_REDIS_MAX_CONNECTIONS=100>> .env
echo.>> .env
echo # 其他配置>> .env
echo MAXKB_CONFIG_TYPE=ENV>> .env
echo MAXKB_DEBUG=True>> .env
echo MAXKB_LANGUAGE_CODE=zh-CN>> .env
echo MAXKB_TIME_ZONE=Asia/Shanghai>> .env

echo.
echo ✅ .env 文件创建并初始化完成,请按实际情况进行修改!
echo.
pause >nul
```
14 changes: 14 additions & 0 deletions scripts/windows/2_start_backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 回到根目录
```shell
cd ../../
```

# 激活虚拟环境 (Windows)
```shell
venv\Scripts\activate
```

# 启动后端服务
```shell
python main.py dev web celery
```
19 changes: 19 additions & 0 deletions scripts/windows/3_start_frontend_ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# 进入UI目录
```shell
cd ../../ui
```

# 启动前端UI Web

```shell
npm run dev
```

```shell
yarn dev
```


# 准备就绪,在浏览器端打开MaxKB
http://127.0.0.1:3000/admin/application
Loading