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)
|
if key.startswith(_env_prefix)
|
||||||
}
|
}
|
||||||
TERMINAL_SANDBOX_REQUIRE = os.environ.get("TERMINAL_SANDBOX_REQUIRE", "0") not in {"0", "false", "False"}
|
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"))
|
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"))
|
MAX_ACTIVE_USER_CONTAINERS = int(os.environ.get("MAX_ACTIVE_USER_CONTAINERS", "8"))
|
||||||
|
|
||||||
@ -65,6 +66,7 @@ __all__ = [
|
|||||||
"TERMINAL_SANDBOX_NAME_PREFIX",
|
"TERMINAL_SANDBOX_NAME_PREFIX",
|
||||||
"TERMINAL_SANDBOX_ENV",
|
"TERMINAL_SANDBOX_ENV",
|
||||||
"TERMINAL_SANDBOX_REQUIRE",
|
"TERMINAL_SANDBOX_REQUIRE",
|
||||||
|
"LINUX_SAFETY",
|
||||||
"TOOLBOX_TERMINAL_IDLE_SECONDS",
|
"TOOLBOX_TERMINAL_IDLE_SECONDS",
|
||||||
"MAX_ACTIVE_USER_CONTAINERS",
|
"MAX_ACTIVE_USER_CONTAINERS",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -210,7 +210,15 @@ class MainTerminal:
|
|||||||
else:
|
else:
|
||||||
self.container_mount_path = TERMINAL_SANDBOX_MOUNT_PATH or "/workspace"
|
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):
|
def record_model_call(self, is_thinking: bool):
|
||||||
|
if self._is_host_mode():
|
||||||
|
return True, {}
|
||||||
tracker = getattr(self, "usage_tracker", None)
|
tracker = getattr(self, "usage_tracker", None)
|
||||||
if not tracker:
|
if not tracker:
|
||||||
return True, {}
|
return True, {}
|
||||||
@ -224,6 +232,8 @@ class MainTerminal:
|
|||||||
return True, {}
|
return True, {}
|
||||||
|
|
||||||
def record_search_call(self):
|
def record_search_call(self):
|
||||||
|
if self._is_host_mode():
|
||||||
|
return True, {}
|
||||||
tracker = getattr(self, "usage_tracker", None)
|
tracker = getattr(self, "usage_tracker", None)
|
||||||
if not tracker:
|
if not tracker:
|
||||||
return True, {}
|
return True, {}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ from config import (
|
|||||||
TERMINAL_SANDBOX_NETWORK,
|
TERMINAL_SANDBOX_NETWORK,
|
||||||
TERMINAL_SANDBOX_REQUIRE,
|
TERMINAL_SANDBOX_REQUIRE,
|
||||||
LOGS_DIR,
|
LOGS_DIR,
|
||||||
|
LINUX_SAFETY,
|
||||||
)
|
)
|
||||||
from modules.container_monitor import collect_stats, inspect_state
|
from modules.container_monitor import collect_stats, inspect_state
|
||||||
|
|
||||||
@ -110,7 +111,13 @@ class UserContainerManager:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Public API
|
# 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:业务用户名(用于日志/权限)
|
- username:业务用户名(用于日志/权限)
|
||||||
@ -122,10 +129,21 @@ class UserContainerManager:
|
|||||||
workspace = str(Path(workspace_path).expanduser().resolve())
|
workspace = str(Path(workspace_path).expanduser().resolve())
|
||||||
Path(workspace).mkdir(parents=True, exist_ok=True)
|
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:
|
with self._lock:
|
||||||
handle = self._containers.get(key)
|
handle = self._containers.get(key)
|
||||||
if handle:
|
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._containers.pop(key, None)
|
||||||
self._kill_container(handle.container_name, handle.sandbox_bin)
|
self._kill_container(handle.container_name, handle.sandbox_bin)
|
||||||
handle = None
|
handle = None
|
||||||
@ -138,7 +156,7 @@ class UserContainerManager:
|
|||||||
raise RuntimeError("资源繁忙:容器配额已用尽,请稍候再试。")
|
raise RuntimeError("资源繁忙:容器配额已用尽,请稍候再试。")
|
||||||
|
|
||||||
# Important: create container using the cache key so each workspace gets its own container name.
|
# 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
|
self._containers[key] = handle
|
||||||
return handle
|
return handle
|
||||||
|
|
||||||
@ -262,8 +280,8 @@ class UserContainerManager:
|
|||||||
existing = 1 if username in self._containers else 0
|
existing = 1 if username in self._containers else 0
|
||||||
return (len(self._containers) - existing) < self.max_containers
|
return (len(self._containers) - existing) < self.max_containers
|
||||||
|
|
||||||
def _create_handle(self, username: str, workspace: str) -> ContainerHandle:
|
def _create_handle(self, username: str, workspace: str, mode: str) -> ContainerHandle:
|
||||||
if self.sandbox_mode != "docker":
|
if mode != "docker":
|
||||||
return self._host_handle(username, workspace)
|
return self._host_handle(username, workspace)
|
||||||
|
|
||||||
docker_path = shutil.which(self.sandbox_bin or "docker")
|
docker_path = shutil.which(self.sandbox_bin or "docker")
|
||||||
|
|||||||
@ -210,7 +210,7 @@ def admin_dashboard_snapshot_api():
|
|||||||
if uname:
|
if uname:
|
||||||
handles = state.container_manager.list_containers()
|
handles = state.container_manager.list_containers()
|
||||||
if uname not in handles:
|
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:
|
except Exception as ensure_exc:
|
||||||
logging.getLogger(__name__).warning("ensure_container for admin failed: %s", 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
|
return None, None
|
||||||
record = get_current_user_record()
|
record = get_current_user_record()
|
||||||
workspace = user_manager.ensure_user_workspace(username)
|
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)
|
usage_tracker = get_or_create_usage_tracker(username, workspace)
|
||||||
terminal = user_terminals.get(username)
|
terminal = user_terminals.get(username)
|
||||||
if not terminal:
|
if not terminal:
|
||||||
|
|||||||
@ -11,6 +11,7 @@ from config import (
|
|||||||
DATA_DIR,
|
DATA_DIR,
|
||||||
LOGS_DIR,
|
LOGS_DIR,
|
||||||
UPLOAD_QUARANTINE_SUBDIR,
|
UPLOAD_QUARANTINE_SUBDIR,
|
||||||
|
LINUX_SAFETY,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .auth_helpers import login_required, api_login_required, get_current_user_record, get_current_username
|
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
|
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'])
|
@auth_bp.route('/login', methods=['GET', 'POST'])
|
||||||
def login():
|
def login():
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
@ -92,7 +99,7 @@ def login():
|
|||||||
session.permanent = True
|
session.permanent = True
|
||||||
clear_failures("login", identifier=client_ip)
|
clear_failures("login", identifier=client_ip)
|
||||||
try:
|
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:
|
except RuntimeError as exc:
|
||||||
session.clear()
|
session.clear()
|
||||||
return jsonify({"success": False, "error": str(exc), "code": "resource_busy"}), 503
|
return jsonify({"success": False, "error": str(exc), "code": "resource_busy"}), 503
|
||||||
@ -136,7 +143,7 @@ def host_login():
|
|||||||
|
|
||||||
# 预先创建宿主机模式的终端/容器句柄(host 模式不会启动 Docker)
|
# 预先创建宿主机模式的终端/容器句柄(host 模式不会启动 Docker)
|
||||||
try:
|
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:
|
except RuntimeError as exc:
|
||||||
session.clear()
|
session.clear()
|
||||||
return jsonify({"success": False, "error": str(exc)}), 503
|
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"
|
workspace.workspace_id = "host"
|
||||||
|
|
||||||
term_key = "host"
|
term_key = "host"
|
||||||
container_handle = state.container_manager.ensure_container("host", str(project_path), container_key=term_key)
|
container_handle = state.container_manager.ensure_container("host", str(project_path), container_key=term_key, preferred_mode="host")
|
||||||
usage_tracker = get_or_create_usage_tracker("host", workspace)
|
usage_tracker = None # 宿主机模式不计配额
|
||||||
terminal = state.user_terminals.get(term_key)
|
terminal = state.user_terminals.get(term_key)
|
||||||
if not terminal:
|
if not terminal:
|
||||||
run_mode = session.get('run_mode') if has_request_context() else None
|
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
term_key = _make_terminal_key(username, getattr(workspace, "workspace_id", None) if is_api_user else None)
|
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)
|
usage_tracker = None if is_api_user else get_or_create_usage_tracker(username, workspace)
|
||||||
terminal = state.user_terminals.get(term_key)
|
terminal = state.user_terminals.get(term_key)
|
||||||
if not terminal:
|
if not terminal:
|
||||||
|
|||||||
@ -115,7 +115,7 @@
|
|||||||
<input type="password" id="password" autocomplete="current-password" />
|
<input type="password" id="password" autocomplete="current-password" />
|
||||||
</div>
|
</div>
|
||||||
<button id="login-btn">登录</button>
|
<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="error" id="error"></div>
|
||||||
<div class="link">
|
<div class="link">
|
||||||
还没有账号?<a href="/register">点击注册</a>
|
还没有账号?<a href="/register">点击注册</a>
|
||||||
@ -171,6 +171,16 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const hostBtn = document.getElementById('host-btn');
|
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.addEventListener('click', async () => {
|
||||||
hostBtn.disabled = true;
|
hostBtn.disabled = true;
|
||||||
errorEl.textContent = '';
|
errorEl.textContent = '';
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user