diff --git a/apps/common/utils/tool_code.py b/apps/common/utils/tool_code.py index 1f44a9741be..912065e3707 100644 --- a/apps/common/utils/tool_code.py +++ b/apps/common/utils/tool_code.py @@ -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 @@ -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')) @@ -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 @@ -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() @@ -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) @@ -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.")) diff --git a/scripts/windows/1_init.md b/scripts/windows/1_init.md new file mode 100644 index 00000000000..0458e868f2f --- /dev/null +++ b/scripts/windows/1_init.md @@ -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 +``` diff --git a/scripts/windows/2_start_backend.md b/scripts/windows/2_start_backend.md new file mode 100644 index 00000000000..5419679aa30 --- /dev/null +++ b/scripts/windows/2_start_backend.md @@ -0,0 +1,14 @@ +# 回到根目录 +```shell +cd ../../ +``` + +# 激活虚拟环境 (Windows) +```shell +venv\Scripts\activate +``` + +# 启动后端服务 +```shell +python main.py dev web celery +``` diff --git a/scripts/windows/3_start_frontend_ui.md b/scripts/windows/3_start_frontend_ui.md new file mode 100644 index 00000000000..d644f045297 --- /dev/null +++ b/scripts/windows/3_start_frontend_ui.md @@ -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