#!/usr/bin/env python3 # 档案 25:not_running 的浏览器导航兜底 —— 绝不吐 JSON;并发进场等待在飞的实例 # 用法: python3 patch-25.py /opt/dshs import io import os import sys root = sys.argv[1] if len(sys.argv) > 1 else '.' p = os.path.join(root, 'src/supervisor/proxy.ts') s = io.open(p, encoding='utf-8').read() old = """ // 实例未运行(后台回收/崩溃/停止后刷新会话页)→ 自动重启并 302 到新实例 URL, // 透明恢复,不必让用户重新登录。 if (access.code === 404 && access.error === 'not_running' && access.userId !== undefined) { try { const folderAbs = app.userFs.resolvePath(access.userId, '') const instance = await app.supervisor.launch(access.userId, folderAbs, undefined) const token = instance.launchToken ?? '' if (token !== '') { const scheme = app.config.secureCookies ? 'https' : 'http' reply.redirect(`${scheme}://${clientHost(request.headers)}/?token=${encodeURIComponent(token)}`) return } } catch { // 启动失败(目录异常/已在启动中等)→ 落到下方 not_running 响应 } } reply.code(access.code).send({ error: access.error }) return""" new = """ // 实例未运行(后台回收/崩溃/停止/熔断后刷新会话页)→ 自动重启并 302 到新实例 URL, // 透明恢复,不必让用户重新登录。 if (access.code === 404 && access.error === 'not_running' && access.userId !== undefined) { const navScheme = app.config.secureCookies ? 'https' : 'http' const navHost = clientHost(request.headers) ?? app.config.baseDomain const isNavigation = request.raw.method === 'GET' && (request.headers.accept ?? '').includes('text/html') let token = '' try { const folderAbs = app.userFs.resolvePath(access.userId, '') const instance = await app.supervisor.launch(access.userId, folderAbs, undefined) token = instance.launchToken ?? '' } catch { // 并发进场(另一个请求正在拉起 → AlreadyRunningError)或启动异常: // 等一会儿取在飞实例的 token,而不是直接把 404 JSON 甩给浏览器(档案 25)。 try { await app.supervisor.waitForLaunchTokenForUser(access.userId, 20000) token = (await app.supervisor.status(access.userId)).main?.launchToken ?? '' } catch { token = '' } } if (token !== '') { reply.redirect(`${navScheme}://${navHost}/?token=${encodeURIComponent(token)}`) return } // 仍拿不到 token:浏览器导航一律回门户(可点「进入」,避免看到裸 JSON);API 保持 JSON。 if (isNavigation) { reply.redirect(`${navScheme}://${app.config.baseDomain}/`) return } } reply.code(access.code).send({ error: access.error }) return""" assert old in s, 'anchor not found' s = s.replace(old, new, 1) io.open(p, 'w', encoding='utf-8', newline='').write(s) print('patched: not_running 浏览器导航兜底 + 并发等待')