fix: raise terminal per-user session cap
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 2m44s

Allow more concurrent dashboard terminal tabs per user and use the current access token for websocket reconnects so refreshed sessions keep working.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gan, Jimmy
2026-03-07 10:37:08 +08:00
parent 6067857dad
commit 1b9ea140b6
2 changed files with 134 additions and 14 deletions
+43 -4
View File
@@ -1,6 +1,7 @@
import asyncio
import struct
import logging
import shlex
from collections import Counter
from fastapi import WebSocket, Query
import asyncssh
@@ -10,19 +11,28 @@ from auth import get_current_user_ws
logger = logging.getLogger(__name__)
MAX_SESSIONS = 5
MAX_SESSIONS_PER_USER = 2
MAX_SESSIONS_PER_USER = 5
_session_lock = asyncio.Lock()
_active_sessions: dict[int, str] = {}
PERSISTENCE_STATUS_PREFIX = "__DASHBOARD_PERSISTENCE__:"
HOSTS = {
"nas": {"host": config.SSH_HOST, "user": config.SSH_USER, "key": config.SSH_KEY_PATH},
"vps": {"host": config.VPS_SSH_HOST, "user": config.VPS_SSH_USER, "key": config.VPS_SSH_KEY_PATH},
"caddy-vps": {"host": config.CADDY_VPS_SSH_HOST, "user": config.CADDY_VPS_SSH_USER, "key": config.VPS_SSH_KEY_PATH},
"mac": {"host": config.MAC_SSH_HOST, "user": config.MAC_SSH_USER, "key": config.MAC_SSH_KEY_PATH},
"claude-dev": {"host": config.SSH_HOST, "user": config.SSH_USER, "key": config.SSH_KEY_PATH,
"command": "/volume1/@appstore/ContainerManager/usr/bin/docker exec -it claude-dev bash"},
"claude-dev": {
"host": config.SSH_HOST,
"user": config.SSH_USER,
"key": config.SSH_KEY_PATH,
"persistent_session": {
"container": "claude-dev",
"session_name": "dashboard-terminal",
},
},
}
# Load known_hosts once at import time — fail closed if missing
_known_hosts_available = False
try:
@@ -33,6 +43,35 @@ except Exception:
logger.error("SSH known_hosts file not found at %s — terminal connections will be refused", config.SSH_KNOWN_HOSTS)
def build_host_command(host_config: dict) -> str | None:
persistent = host_config.get("persistent_session")
if not persistent:
return host_config.get("command")
docker_bin = "/volume1/@appstore/ContainerManager/usr/bin/docker"
container = persistent["container"]
session_name = persistent["session_name"]
quoted_container = shlex.quote(container)
quoted_session = shlex.quote(session_name)
quoted_marker = shlex.quote(PERSISTENCE_STATUS_PREFIX)
attach_cmd = (
f"{docker_bin} exec -it {quoted_container} "
f"tmux new-session -A -s {quoted_session}"
)
script = " ".join([
"if",
f"{docker_bin} exec {quoted_container} tmux has-session -t {quoted_session} >/dev/null 2>&1;",
"then",
f"printf '%sattached\\n' {quoted_marker};",
"else",
f"printf '%screated\\n' {quoted_marker};",
"fi;",
f"exec {attach_cmd}",
])
return f"bash -lc {shlex.quote(script)}"
async def _reserve_session(session_id: int, username: str) -> tuple[bool, str | None, int | None]:
async with _session_lock:
if len(_active_sessions) >= MAX_SESSIONS:
@@ -98,7 +137,7 @@ async def ws_endpoint(websocket: WebSocket, host: str = Query("nas"), token: str
try:
process = await conn.create_process(
h.get("command"), term_type="xterm-256color", term_size=(80, 24),
build_host_command(h), term_type="xterm-256color", term_size=(80, 24),
encoding=None,
)