diff --git a/apps/client/src/desktop.ts b/apps/client/src/desktop.ts index 88f3e0731..e762aa65b 100644 --- a/apps/client/src/desktop.ts +++ b/apps/client/src/desktop.ts @@ -8,7 +8,7 @@ import electronContextMenu from "./menus/electron_context_menu.js"; import glob from "./services/glob.js"; import { t } from "./services/i18n.js"; import options from "./services/options.js"; -import { isRunningUnderRosetta2 } from "./services/rosetta_detection.js"; +import server from "./services/server.js"; import type ElectronRemote from "@electron/remote"; import type Electron from "electron"; import "./stylesheets/bootstrap.scss"; @@ -119,12 +119,13 @@ function initDarkOrLightMode(style: CSSStyleDeclaration) { nativeTheme.themeSource = themeSource; } -function checkRosetta2Warning() { +async function checkRosetta2Warning() { if (!utils.isElectron()) return; try { - // Check if running under Rosetta 2 directly on client - if (isRunningUnderRosetta2()) { + // Check if running under Rosetta 2 by calling the server + const response = await server.get("api/system-info/rosetta-check") as { isRunningUnderRosetta2: boolean }; + if (response.isRunningUnderRosetta2) { // Trigger the Rosetta 2 warning dialog appContext.triggerCommand("showRosettaWarning", {}); } diff --git a/apps/client/src/services/rosetta_detection.ts b/apps/client/src/services/rosetta_detection.ts deleted file mode 100644 index b3d0fccd2..000000000 --- a/apps/client/src/services/rosetta_detection.ts +++ /dev/null @@ -1,34 +0,0 @@ -import utils from "./utils.js"; - -/** - * Detects if the application is running under Rosetta 2 translation on Apple Silicon. - * This happens when an x64 version of the app is run on an M1/M2/M3 Mac. - * Uses the macOS sysctl.proc_translated to properly detect translation. - * @returns true if running under Rosetta 2, false otherwise - */ -export function isRunningUnderRosetta2(): boolean { - if (!utils.isElectron()) return false; - - const process = utils.dynamicRequire("process"); - const { execSync } = utils.dynamicRequire("child_process"); - - // Only check on macOS - if (process.platform !== "darwin") return false; - - try { - // Use sysctl.proc_translated to check if process is being translated by Rosetta 2 - // This is the proper way to detect Rosetta 2 translation - const result = execSync("sysctl -n sysctl.proc_translated 2>/dev/null", { - encoding: "utf8", - timeout: 1000 - }).trim(); - - // Returns "1" if running under Rosetta 2, "0" if native ARM64 - // Returns empty string or error on Intel Macs (where the key doesn't exist) - return result === "1"; - } catch (error) { - // If the command fails (e.g., on Intel Macs), assume not running under Rosetta 2 - console.debug("Could not check Rosetta 2 status:", error); - return false; - } -} diff --git a/apps/server/src/routes/api/system_info.ts b/apps/server/src/routes/api/system_info.ts new file mode 100644 index 000000000..4d49f4ca2 --- /dev/null +++ b/apps/server/src/routes/api/system_info.ts @@ -0,0 +1,12 @@ +import { isRunningUnderRosetta2 } from "../../services/utils.js"; +import type { Request, Response } from "express"; + +function rosettaCheck(req: Request, res: Response) { + res.json({ + isRunningUnderRosetta2: isRunningUnderRosetta2() + }); +} + +export default { + rosettaCheck +}; diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index b988ecb11..eee979859 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -58,6 +58,7 @@ import ollamaRoute from "./api/ollama.js"; import openaiRoute from "./api/openai.js"; import anthropicRoute from "./api/anthropic.js"; import llmRoute from "./api/llm.js"; +import systemInfoRoute from "./api/system_info.js"; import etapiAuthRoutes from "../etapi/auth.js"; import etapiAppInfoRoutes from "../etapi/app_info.js"; @@ -238,6 +239,7 @@ function register(app: express.Application) { apiRoute(PST, "/api/recent-notes", recentNotesRoute.addRecentNote); apiRoute(GET, "/api/app-info", appInfoRoute.getAppInfo); apiRoute(GET, "/api/metrics", metricsRoute.getMetrics); + apiRoute(GET, "/api/system-info/rosetta-check", systemInfoRoute.rosettaCheck); // docker health check route(GET, "/api/health-check", [], () => ({ status: "ok" }), apiResultHandler); diff --git a/apps/server/src/services/utils.ts b/apps/server/src/services/utils.ts index 7555e1a7c..c02a325d8 100644 --- a/apps/server/src/services/utils.ts +++ b/apps/server/src/services/utils.ts @@ -23,7 +23,33 @@ export const isElectron = !!process.versions["electron"]; export const isDev = !!(process.env.TRILIUM_ENV && process.env.TRILIUM_ENV === "dev"); +/** + * Detects if the application is running under Rosetta 2 translation on Apple Silicon. + * This happens when an x64 version of the app is run on an M1/M2/M3 Mac. + * Uses the macOS sysctl.proc_translated to properly detect translation. + * @returns true if running under Rosetta 2, false otherwise + */ +export const isRunningUnderRosetta2 = () => { + if (!isMac) return false; + try { + // Use child_process to check sysctl.proc_translated + // This is the proper way to detect Rosetta 2 translation + const { execSync } = require("child_process"); + const result = execSync("sysctl -n sysctl.proc_translated 2>/dev/null", { + encoding: "utf8", + timeout: 1000 + }).trim(); + + // 1 means the process is being translated by Rosetta 2 + // 0 means native execution + // If the sysctl doesn't exist (on Intel Macs), this will return empty/error + return result === "1"; + } catch (error) { + // If sysctl fails or doesn't exist (Intel Macs), not running under Rosetta 2 + return false; + } +}; export function newEntityId() { return randomString(12); @@ -397,6 +423,7 @@ export default { isElectron, isEmptyOrWhitespace, isMac, + isRunningUnderRosetta2, isStringNote, isWindows, md5,