fix: refine host mode controls and kimi-k2.5 support
This commit is contained in:
parent
8a7cc5d9c6
commit
60d27e9c1c
@ -40,6 +40,7 @@ TERMINAL_SANDBOX_ENV = {
|
||||
if key.startswith(_env_prefix)
|
||||
}
|
||||
TERMINAL_SANDBOX_REQUIRE = os.environ.get("TERMINAL_SANDBOX_REQUIRE", "0") not in {"0", "false", "False"}
|
||||
LINUX_SAFETY = os.environ.get("LINUX_SAFETY", "0") not in {"0", "false", "False"}
|
||||
TOOLBOX_TERMINAL_IDLE_SECONDS = int(os.environ.get("TOOLBOX_TERMINAL_IDLE_SECONDS", "900"))
|
||||
MAX_ACTIVE_USER_CONTAINERS = int(os.environ.get("MAX_ACTIVE_USER_CONTAINERS", "8"))
|
||||
|
||||
@ -65,6 +66,7 @@ __all__ = [
|
||||
"TERMINAL_SANDBOX_NAME_PREFIX",
|
||||
"TERMINAL_SANDBOX_ENV",
|
||||
"TERMINAL_SANDBOX_REQUIRE",
|
||||
"LINUX_SAFETY",
|
||||
"TOOLBOX_TERMINAL_IDLE_SECONDS",
|
||||
"MAX_ACTIVE_USER_CONTAINERS",
|
||||
]
|
||||
|
||||
@ -210,7 +210,15 @@ class MainTerminal:
|
||||
else:
|
||||
self.container_mount_path = TERMINAL_SANDBOX_MOUNT_PATH or "/workspace"
|
||||
|
||||
def _is_host_mode(self) -> bool:
|
||||
"""判定当前是否运行在宿主机模式,用于豁免配额等限制。"""
|
||||
if self.container_session and getattr(self.container_session, "mode", None) != "docker":
|
||||
return True
|
||||
return (TERMINAL_SANDBOX_MODE or "").lower() == "host"
|
||||
|
||||
def record_model_call(self, is_thinking: bool):
|
||||
if self._is_host_mode():
|
||||
return True, {}
|
||||
tracker = getattr(self, "usage_tracker", None)
|
||||
if not tracker:
|
||||
return True, {}
|
||||
@ -224,6 +232,8 @@ class MainTerminal:
|
||||
return True, {}
|
||||
|
||||
def record_search_call(self):
|
||||
if self._is_host_mode():
|
||||
return True, {}
|
||||
tracker = getattr(self, "usage_tracker", None)
|
||||
if not tracker:
|
||||
return True, {}
|
||||
|
||||
@ -29,6 +29,7 @@ from config import (
|
||||
TERMINAL_SANDBOX_NETWORK,
|
||||
TERMINAL_SANDBOX_REQUIRE,
|
||||
LOGS_DIR,
|
||||
LINUX_SAFETY,
|
||||
)
|
||||
from modules.container_monitor import collect_stats, inspect_state
|
||||
|
||||
@ -110,7 +111,13 @@ class UserContainerManager:
|
||||
# ------------------------------------------------------------------
|
||||
# Public API
|
||||
# ------------------------------------------------------------------
|
||||
def ensure_container(self, username: str, workspace_path: str, container_key: Optional[str] = None) -> ContainerHandle:
|
||||
def ensure_container(
|
||||
self,
|
||||
username: str,
|
||||
workspace_path: str,
|
||||
container_key: Optional[str] = None,
|
||||
preferred_mode: Optional[str] = None,
|
||||
) -> ContainerHandle:
|
||||
"""为指定“容器键”确保一个容器。
|
||||
|
||||
- username:业务用户名(用于日志/权限)
|
||||
@ -122,10 +129,21 @@ class UserContainerManager:
|
||||
workspace = str(Path(workspace_path).expanduser().resolve())
|
||||
Path(workspace).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
mode = (preferred_mode or self.sandbox_mode or "host").lower()
|
||||
# 安全模式:当 LINUX_SAFETY 开启且要求 host 时,强制回退 docker
|
||||
if mode == "host" and LINUX_SAFETY:
|
||||
mode = "docker"
|
||||
|
||||
with self._lock:
|
||||
handle = self._containers.get(key)
|
||||
if handle:
|
||||
if handle.mode == "docker" and not self._is_container_running(handle):
|
||||
# 模式不同或 docker 挂掉时重建
|
||||
if handle.mode != mode:
|
||||
self._containers.pop(key, None)
|
||||
if handle.mode == "docker":
|
||||
self._kill_container(handle.container_name, handle.sandbox_bin)
|
||||
handle = None
|
||||
elif handle.mode == "docker" and not self._is_container_running(handle):
|
||||
self._containers.pop(key, None)
|
||||
self._kill_container(handle.container_name, handle.sandbox_bin)
|
||||
handle = None
|
||||
@ -138,7 +156,7 @@ class UserContainerManager:
|
||||
raise RuntimeError("资源繁忙:容器配额已用尽,请稍候再试。")
|
||||
|
||||
# Important: create container using the cache key so each workspace gets its own container name.
|
||||
handle = self._create_handle(key, workspace)
|
||||
handle = self._create_handle(key, workspace, mode)
|
||||
self._containers[key] = handle
|
||||
return handle
|
||||
|
||||
@ -262,8 +280,8 @@ class UserContainerManager:
|
||||
existing = 1 if username in self._containers else 0
|
||||
return (len(self._containers) - existing) < self.max_containers
|
||||
|
||||
def _create_handle(self, username: str, workspace: str) -> ContainerHandle:
|
||||
if self.sandbox_mode != "docker":
|
||||
def _create_handle(self, username: str, workspace: str, mode: str) -> ContainerHandle:
|
||||
if mode != "docker":
|
||||
return self._host_handle(username, workspace)
|
||||
|
||||
docker_path = shutil.which(self.sandbox_bin or "docker")
|
||||
|
||||
@ -210,7 +210,7 @@ def admin_dashboard_snapshot_api():
|
||||
if uname:
|
||||
handles = state.container_manager.list_containers()
|
||||
if uname not in handles:
|
||||
state.container_manager.ensure_container(uname, str(state.user_manager.ensure_user_workspace(uname).project_path))
|
||||
state.container_manager.ensure_container(uname, str(state.user_manager.ensure_user_workspace(uname).project_path), preferred_mode="docker")
|
||||
except Exception as ensure_exc:
|
||||
logging.getLogger(__name__).warning("ensure_container for admin failed: %s", ensure_exc)
|
||||
|
||||
|
||||
@ -849,7 +849,7 @@ def get_user_resources(username: Optional[str] = None) -> Tuple[Optional[WebTerm
|
||||
return None, None
|
||||
record = get_current_user_record()
|
||||
workspace = user_manager.ensure_user_workspace(username)
|
||||
container_handle = container_manager.ensure_container(username, str(workspace.project_path))
|
||||
container_handle = container_manager.ensure_container(username, str(workspace.project_path), preferred_mode="docker")
|
||||
usage_tracker = get_or_create_usage_tracker(username, workspace)
|
||||
terminal = user_terminals.get(username)
|
||||
if not terminal:
|
||||
|
||||
@ -11,6 +11,7 @@ from config import (
|
||||
DATA_DIR,
|
||||
LOGS_DIR,
|
||||
UPLOAD_QUARANTINE_SUBDIR,
|
||||
LINUX_SAFETY,
|
||||
)
|
||||
|
||||
from .auth_helpers import login_required, api_login_required, get_current_user_record, get_current_username
|
||||
@ -36,6 +37,12 @@ def issue_csrf_token():
|
||||
return response
|
||||
|
||||
|
||||
@auth_bp.route('/api/host-mode-enabled', methods=['GET'])
|
||||
def host_mode_enabled():
|
||||
enabled = (TERMINAL_SANDBOX_MODE or "").lower() == "host" and not LINUX_SAFETY
|
||||
return jsonify({"success": True, "enabled": enabled})
|
||||
|
||||
|
||||
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'GET':
|
||||
@ -92,7 +99,7 @@ def login():
|
||||
session.permanent = True
|
||||
clear_failures("login", identifier=client_ip)
|
||||
try:
|
||||
state.container_manager.ensure_container(record.username, str(workspace.project_path))
|
||||
state.container_manager.ensure_container(record.username, str(workspace.project_path), preferred_mode="docker")
|
||||
except RuntimeError as exc:
|
||||
session.clear()
|
||||
return jsonify({"success": False, "error": str(exc), "code": "resource_busy"}), 503
|
||||
@ -136,7 +143,7 @@ def host_login():
|
||||
|
||||
# 预先创建宿主机模式的终端/容器句柄(host 模式不会启动 Docker)
|
||||
try:
|
||||
state.container_manager.ensure_container("host", str(host_path), container_key="host")
|
||||
state.container_manager.ensure_container("host", str(host_path), container_key="host", preferred_mode="host")
|
||||
except RuntimeError as exc:
|
||||
session.clear()
|
||||
return jsonify({"success": False, "error": str(exc)}), 503
|
||||
|
||||
@ -83,8 +83,8 @@ def get_user_resources(username: Optional[str] = None, workspace_id: Optional[st
|
||||
workspace.workspace_id = "host"
|
||||
|
||||
term_key = "host"
|
||||
container_handle = state.container_manager.ensure_container("host", str(project_path), container_key=term_key)
|
||||
usage_tracker = get_or_create_usage_tracker("host", workspace)
|
||||
container_handle = state.container_manager.ensure_container("host", str(project_path), container_key=term_key, preferred_mode="host")
|
||||
usage_tracker = None # 宿主机模式不计配额
|
||||
terminal = state.user_terminals.get(term_key)
|
||||
if not terminal:
|
||||
run_mode = session.get('run_mode') if has_request_context() else None
|
||||
@ -138,7 +138,7 @@ def get_user_resources(username: Optional[str] = None, workspace_id: Optional[st
|
||||
except Exception:
|
||||
pass
|
||||
term_key = _make_terminal_key(username, getattr(workspace, "workspace_id", None) if is_api_user else None)
|
||||
container_handle = state.container_manager.ensure_container(username, str(workspace.project_path), container_key=term_key)
|
||||
container_handle = state.container_manager.ensure_container(username, str(workspace.project_path), container_key=term_key, preferred_mode="docker")
|
||||
usage_tracker = None if is_api_user else get_or_create_usage_tracker(username, workspace)
|
||||
terminal = state.user_terminals.get(term_key)
|
||||
if not terminal:
|
||||
|
||||
@ -115,7 +115,7 @@
|
||||
<input type="password" id="password" autocomplete="current-password" />
|
||||
</div>
|
||||
<button id="login-btn">登录</button>
|
||||
<button id="host-btn" class="ghost-btn" title="仅当服务器配置为宿主机模式时可用">宿主机模式(免登录)</button>
|
||||
<button id="host-btn" class="ghost-btn" title="仅当服务器配置为宿主机模式且未启用安全保护时可用" style="display:none">宿主机模式(免登录)</button>
|
||||
<div class="error" id="error"></div>
|
||||
<div class="link">
|
||||
还没有账号?<a href="/register">点击注册</a>
|
||||
@ -171,6 +171,16 @@
|
||||
});
|
||||
|
||||
const hostBtn = document.getElementById('host-btn');
|
||||
// 按钮可见性由后端配置决定
|
||||
fetch('/api/host-mode-enabled')
|
||||
.then(resp => resp.json())
|
||||
.then(data => {
|
||||
if (data && data.success && data.enabled) {
|
||||
hostBtn.style.display = 'block';
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
hostBtn.addEventListener('click', async () => {
|
||||
hostBtn.disabled = true;
|
||||
errorEl.textContent = '';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user