From cfc3af89844fa7ba948c172050e5fea1519ecb62 Mon Sep 17 00:00:00 2001 From: lix-2026 Date: Fri, 17 Apr 2026 23:33:24 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=81=A2=E5=A4=8D=E6=9C=AC=E5=9C=B0=20C?= =?UTF-8?q?onvex=20Auth=20=E9=93=BE=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复 desktop 脚本在 Linux 下优先使用后端虚拟环境可执行文件 - 移除 Convex Auth 的本地密钥兜底,回到真实 deployment env - 首页未登录时先跳转 /auth,避免根路由直接触发 workspace 初始化失败 --- scripts/desktop-hot.js | 81 ++++++++++++++++++++++++++++++--- scripts/desktop-local.js | 28 +++++++++++- scripts/desktop-prod.js | 30 +++++++++++- wolai-frontend/convex/auth.ts | 4 -- wolai-frontend/src/app/page.tsx | 6 +++ 5 files changed, 135 insertions(+), 14 deletions(-) diff --git a/scripts/desktop-hot.js b/scripts/desktop-hot.js index 7b34fe64..c7f8ca22 100644 --- a/scripts/desktop-hot.js +++ b/scripts/desktop-hot.js @@ -19,12 +19,38 @@ const net = require("net"); const { URL } = require("url"); const fs = require("fs"); -const rootDir = path.resolve(__dirname, ".."); -const frontendDir = path.join(rootDir, "wolai-frontend"); -const backendDir = path.join(rootDir, "wolai-backend"); - -const pythonBin = process.env.PYTHON_BIN || "python"; -const celeryBin = process.env.CELERY_BIN || "celery"; +const rootDir = path.resolve(__dirname, ".."); +const frontendDir = path.join(rootDir, "wolai-frontend"); +const backendDir = path.join(rootDir, "wolai-backend"); + +function resolveBackendExecutable(envName, fallbackName) { + const fromEnv = (process.env[envName] || "").trim(); + if (fromEnv) return fromEnv; + + const isWin = process.platform === "win32"; + const candidates = isWin + ? [ + path.join(backendDir, ".venv", "Scripts", `${fallbackName}.exe`), + path.join(backendDir, ".venv312", "Scripts", `${fallbackName}.exe`), + path.join(backendDir, "venv", "Scripts", `${fallbackName}.exe`), + ] + : [ + path.join(backendDir, ".venv-linux", "bin", fallbackName), + path.join(backendDir, ".venv", "bin", fallbackName), + path.join(backendDir, "venv", "bin", fallbackName), + ]; + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + + return fallbackName; +} + +const pythonBin = resolveBackendExecutable("PYTHON_BIN", "python"); +const celeryBin = resolveBackendExecutable("CELERY_BIN", "celery"); const celeryPoolFromEnv = (process.env.CELERY_POOL || "").trim(); const skipCelery = (process.env.SKIP_CELERY || "").toLowerCase() === "1" || @@ -89,11 +115,44 @@ async function findFreePort(startPort) { } function getListeningPidsByPort(port) { + const pids = new Set(); + + if (process.platform !== "win32") { + try { + const out = execSync(`ss -ltnp 'sport = :${port}'`, { encoding: "utf8" }); + for (const match of out.matchAll(/pid=(\d+)/g)) { + const pid = Number(match[1]); + if (Number.isFinite(pid) && pid > 0) { + pids.add(pid); + } + } + } catch { + // ignore + } + + if (pids.size > 0) { + return [...pids]; + } + + try { + const out = execSync(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t`, { encoding: "utf8" }); + for (const line of out.split(/\r?\n/)) { + const pid = Number(line.trim()); + if (Number.isFinite(pid) && pid > 0) { + pids.add(pid); + } + } + } catch { + // ignore + } + + return [...pids]; + } + try { // 说明:netstat 输出示例: // TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 12345 const out = execSync("netstat -ano -p tcp", { encoding: "utf8" }); - const pids = new Set(); for (const line of out.split(/\r?\n/)) { if (!line.includes(`:${port}`)) continue; if (!/LISTENING/i.test(line)) continue; @@ -110,6 +169,14 @@ function getListeningPidsByPort(port) { } function getProcessNameByPid(pid) { + if (process.platform !== "win32") { + try { + return execSync(`ps -p ${pid} -o comm=`, { encoding: "utf8" }).trim(); + } catch { + return ""; + } + } + try { // 输出为 CSV,示例: // "Image Name","PID","Session Name","Session#","Mem Usage" diff --git a/scripts/desktop-local.js b/scripts/desktop-local.js index 167d5582..d4acc8f8 100644 --- a/scripts/desktop-local.js +++ b/scripts/desktop-local.js @@ -22,7 +22,33 @@ const path = require("path"); const rootDir = path.resolve(__dirname, ".."); const backendDir = path.join(rootDir, "wolai-backend"); -const pythonBin = process.env.PYTHON_BIN || "python"; +function resolveBackendPython() { + const fromEnv = (process.env.PYTHON_BIN || "").trim(); + if (fromEnv) return fromEnv; + + const isWin = process.platform === "win32"; + const candidates = isWin + ? [ + path.join(backendDir, ".venv", "Scripts", "python.exe"), + path.join(backendDir, ".venv312", "Scripts", "python.exe"), + path.join(backendDir, "venv", "Scripts", "python.exe"), + ] + : [ + path.join(backendDir, ".venv-linux", "bin", "python"), + path.join(backendDir, ".venv", "bin", "python"), + path.join(backendDir, "venv", "bin", "python"), + ]; + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + + return "python"; +} + +const pythonBin = resolveBackendPython(); const dataDir = process.env.MNOTE_DATA_DIR || path.join(rootDir, "data-desktop-test"); const backendCmd = diff --git a/scripts/desktop-prod.js b/scripts/desktop-prod.js index 3b79687b..2fa94067 100644 --- a/scripts/desktop-prod.js +++ b/scripts/desktop-prod.js @@ -29,8 +29,34 @@ const backendDir = path.join(rootDir, "wolai-backend"); const ingestDir = path.join(rootDir, "services", "ingest_service"); const ragGatewayDir = path.join(rootDir, "services", "rag_gateway"); -const pythonBin = process.env.PYTHON_BIN || "python"; -const celeryBin = process.env.CELERY_BIN || "celery"; +function resolveBackendExecutable(envName, fallbackName) { + const fromEnv = (process.env[envName] || "").trim(); + if (fromEnv) return fromEnv; + + const isWin = process.platform === "win32"; + const candidates = isWin + ? [ + path.join(backendDir, ".venv", "Scripts", `${fallbackName}.exe`), + path.join(backendDir, ".venv312", "Scripts", `${fallbackName}.exe`), + path.join(backendDir, "venv", "Scripts", `${fallbackName}.exe`), + ] + : [ + path.join(backendDir, ".venv-linux", "bin", fallbackName), + path.join(backendDir, ".venv", "bin", fallbackName), + path.join(backendDir, "venv", "bin", fallbackName), + ]; + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + return candidate; + } + } + + return fallbackName; +} + +const pythonBin = resolveBackendExecutable("PYTHON_BIN", "python"); +const celeryBin = resolveBackendExecutable("CELERY_BIN", "celery"); const skipCelery = (process.env.SKIP_CELERY || "").toLowerCase() === "1" || (process.env.SKIP_CELERY || "").toLowerCase() === "true"; diff --git a/wolai-frontend/convex/auth.ts b/wolai-frontend/convex/auth.ts index 0e82a5fd..c17af75e 100644 --- a/wolai-frontend/convex/auth.ts +++ b/wolai-frontend/convex/auth.ts @@ -1,10 +1,6 @@ import { Password } from "@convex-dev/auth/providers/Password"; import { convexAuth } from "@convex-dev/auth/server"; -if (!process.env.JWT_PRIVATE_KEY) { - process.env.JWT_PRIVATE_KEY = "-----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCPcrWDwjh74nu1 gfYo9ywVIRMedfPwbNiDjHnovzHAIeToc1+Lw3ju/io9OME3KLWzIibUHX39LilK sSQKigJ3+i7J13IquJD++ozY9C8Dp/P34txVY+ECSQRdIBbETLJxYBWXOx6Ysw/d /CUuDOdEw2IhNvN97sTh5jUZe6f/H6mfCpJ3X5SZrqrIUTTL6r4Lj9ZS3PUX5ivU 6sd2rXzpDsUQgrNgmvGN3w9nAHudxAUv/zOGa4802Z+z14LCBIX6v6PIK79YQ4s9 /5hH73MesA6vgzWShJcyVy3guyde+duhpECo1q3d6dIZeqrazQwqAW3vcpmEFJdX jkpociDfAgMBAAECggEAAYtRE+mH1SGThlkvTrKWeWXBQG8xoJFzZTsiZtSEExbq UWxIh4cjqqL2znDpd5ALILIJ6/ejTxHrpN+yTSC+NQ9u6IJWusoA2ZXV5VH/nZD1 yeHZ0FuCZRVnJB9/zz4qH5lSsi2TPz6SOagIuG2wIafeyw+94EmtOedSBAO2Q8NN 3jHoINBCyRu6hU3ml0h7daoIhUw9ONI7MZUlYvuV7Ti3yf+czzpqwQx3gZA39sa8 qUbJPFk6ts+CVjqSAdYVSfU3TC1Us1usOM3+04mACp3vc6ZUKxnB4Dsk/sSHj71n EcZ3fOR7EgjmXf6wBiq24T2+0UzoHW2yDaiAowr6iQKBgQDDs2eJTopFejt47EMm zY//e8fyUDKx07CWwUZKP78IMTZcp8BwHmKTSY+VbQLvVYnNxADkckYapokjU3a7 GMOXxvKVLqfgU/oUBIkRFDRo+nasFCd5cPpnYjQ6lGalBkDghVlyvq7kKF/MPtIK dLqsV3tZDXoHX2mjTga94ygTZwKBgQC7pa+eZdWiz275mpYql9Gs6N3G3HhcpRxb oWYWekCtZQ9gtecpvA9e0tW3ShoF19ksWYGpM4vxAOXul5Ei9IPVVNHc7RvGF5CO 0Zryx3qaVk7E6XQc3BQVVAenT2fNiv3+fkCMeAvcZBZOGiPPziCCT3wU3bd3tsn8 EsMiAPvTyQKBgEs0iWhBr29NrsckfBXQTzMN/WOIIEMoJ6d3dKyZ3K6oQszOhmxP sPALB8uTjdotk/xoAzPHGlupfe/+ZhU2Sgvsn1JnEIprmyHQMGBI1G83OR2dzSGl IgVSvuF4IA3w3kOp2xr2Xj09qrrRtWPhQc9y+urY+/kTWIQyOvMD9WWnAoGADdUN 2BBLqj++P3oMvcEJPMTBrGoOGU42g+6m1ttWLzH26zsdei8ZtvS1ulglCO87XBCR BUb+dtqJGIhls3zwxuYEvlNgK78K8ewzjtfzirL4BX3sCECU3mmeUtAAp98qD/uA iJpEzY83MbStlSDtto1jaSpa3uFDjGhZqAUIizkCgYABf/HSMwWsnvRgo0JMmDru 3L/xScJiFVdFV9vNiSckjUYhx7lgFqEjsDEN1mZiRIa2UhLOqpo5S/cIXfMkeEPh 9Rusf9STwNwOOyhFRrHCxqhTH6Ivnj6oZSM/UrS8GvCVMPWs1z8k45FWKI/IRXQq AT5PRKihrWbg63/rXaSCxw== -----END PRIVATE KEY-----"; -} - /** * Convex Auth 配置 * diff --git a/wolai-frontend/src/app/page.tsx b/wolai-frontend/src/app/page.tsx index d54e2ffc..ab2453cc 100644 --- a/wolai-frontend/src/app/page.tsx +++ b/wolai-frontend/src/app/page.tsx @@ -2,6 +2,7 @@ import { redirect } from "next/navigation"; import { isConvexEnabled } from "@/lib/convex/enabled"; import { getAuthedConvexClient } from "@/lib/convex/route"; import { api } from "@/lib/convex/api"; +import { convexAuthNextjsToken } from "@convex-dev/auth/nextjs/server"; function makeId(): string { return typeof crypto.randomUUID === "function" @@ -14,6 +15,11 @@ export default async function Home() { redirect("/auth"); } + const token = await convexAuthNextjsToken(); + if (!token) { + redirect("/auth"); + } + const { auth, client } = await getAuthedConvexClient(); const { activeWorkspaceId } = await client.mutation(api.workspaces.ensureDefaultWorkspace, {