diff --git a/apps/client/src/components/app_context.ts b/apps/client/src/components/app_context.ts index 520f9e68b..3c7873e8b 100644 --- a/apps/client/src/components/app_context.ts +++ b/apps/client/src/components/app_context.ts @@ -129,7 +129,7 @@ export type CommandMappings = { openAboutDialog: CommandData; hideFloatingButtons: {}; hideLeftPane: CommandData; - showRosettaWarning: CommandData; + showCpuArchWarning: CommandData; showLeftPane: CommandData; hoistNote: CommandData & { noteId: string }; leaveProtectedSession: CommandData; diff --git a/apps/client/src/components/startup_checks.ts b/apps/client/src/components/startup_checks.ts index f40175afb..025eec98b 100644 --- a/apps/client/src/components/startup_checks.ts +++ b/apps/client/src/components/startup_checks.ts @@ -1,6 +1,11 @@ import server from "../services/server"; import Component from "./component"; +// TODO: Deduplicate. +interface CpuArchResponse { + isCpuArchMismatch: boolean; +} + export class StartupChecks extends Component { constructor() { @@ -11,10 +16,10 @@ export class StartupChecks extends Component { async checkRosetta2Warning() { try { // Check if running under Rosetta 2 by calling the server - const response = await server.get("system-info/rosetta-check") as { isRunningUnderRosetta2: boolean }; - if (response.isRunningUnderRosetta2) { + const response = await server.get("system-checks") as CpuArchResponse; + if (response.isCpuArchMismatch) { // Trigger the Rosetta 2 warning dialog - this.triggerCommand("showRosettaWarning", {}); + this.triggerCommand("showCpuArchWarning", {}); } } catch (error) { console.warn("Could not check Rosetta 2 status:", error); diff --git a/apps/client/src/layouts/layout_commons.ts b/apps/client/src/layouts/layout_commons.ts index 9c0716c4f..e53839839 100644 --- a/apps/client/src/layouts/layout_commons.ts +++ b/apps/client/src/layouts/layout_commons.ts @@ -21,7 +21,7 @@ import ConfirmDialog from "../widgets/dialogs/confirm.js"; import RevisionsDialog from "../widgets/dialogs/revisions.js"; import DeleteNotesDialog from "../widgets/dialogs/delete_notes.js"; import InfoDialog from "../widgets/dialogs/info.js"; -import RosettaWarningDialog from "../widgets/dialogs/rosetta_warning.js"; +import IncorrectCpuArchDialog from "../widgets/dialogs/incorrect_cpu_arch.js"; export function applyModals(rootContainer: RootContainer) { rootContainer @@ -46,5 +46,5 @@ export function applyModals(rootContainer: RootContainer) { .child(new InfoDialog()) .child(new ConfirmDialog()) .child(new PromptDialog()) - .child(new RosettaWarningDialog()) + .child(new IncorrectCpuArchDialog()) } diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index ec9b8e2fa..e9cb43fd0 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -1919,7 +1919,7 @@ "word_wrapping": "Word wrapping", "color-scheme": "Color scheme" }, - "rosetta_warning": { + "cpu_arch_warning": { "title": "Performance Warning: Running Under Rosetta 2", "message": "TriliumNext is currently running under Rosetta 2 translation, which means you're using the Intel (x64) version on an Apple Silicon Mac.", "performance_impact": "This will significantly impact performance and battery life.", diff --git a/apps/client/src/widgets/dialogs/rosetta_warning.ts b/apps/client/src/widgets/dialogs/incorrect_cpu_arch.ts similarity index 79% rename from apps/client/src/widgets/dialogs/rosetta_warning.ts rename to apps/client/src/widgets/dialogs/incorrect_cpu_arch.ts index 37b83f840..f47ec6e50 100644 --- a/apps/client/src/widgets/dialogs/rosetta_warning.ts +++ b/apps/client/src/widgets/dialogs/incorrect_cpu_arch.ts @@ -10,31 +10,31 @@ const TPL = /*html*/` @@ -45,13 +45,11 @@ const TPL = /*html*/` export default class RosettaWarningDialog extends BasicWidget { private modal!: Modal; private $downloadButton!: JQuery; - private $continueButton!: JQuery; doRender() { this.$widget = $(TPL); this.modal = Modal.getOrCreateInstance(this.$widget[0]); this.$downloadButton = this.$widget.find(".download-correct-version-button"); - this.$continueButton = this.$widget.find(".continue-anyway-button"); this.$downloadButton.on("click", () => { // Open the releases page where users can download the correct version @@ -69,7 +67,7 @@ export default class RosettaWarningDialog extends BasicWidget { }); } - showRosettaWarningEvent() { + showCpuArchWarningEvent() { this.modal.show(); } } diff --git a/apps/server/src/routes/api/system_info.ts b/apps/server/src/routes/api/system_info.ts index 9720ecdf7..02d6a90d5 100644 --- a/apps/server/src/routes/api/system_info.ts +++ b/apps/server/src/routes/api/system_info.ts @@ -1,12 +1,39 @@ -import { isRunningUnderRosetta2 } from "../../services/utils.js"; -import type { Request, Response } from "express"; +import { execSync } from "child_process"; +import { isMac } from "../../services/utils"; -function rosettaCheck(req: Request, res: Response) { +function systemChecks() { return { - isRunningUnderRosetta2: isRunningUnderRosetta2() + isCpuArchMismatch: isRunningUnderRosetta2() } } -export default { - rosettaCheck +/** + * 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 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 default { + systemChecks }; diff --git a/apps/server/src/routes/routes.ts b/apps/server/src/routes/routes.ts index eee979859..6b984aed4 100644 --- a/apps/server/src/routes/routes.ts +++ b/apps/server/src/routes/routes.ts @@ -239,7 +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); + apiRoute(GET, "/api/system-checks", systemInfoRoute.systemChecks); // 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 c02a325d8..89aad1bbb 100644 --- a/apps/server/src/services/utils.ts +++ b/apps/server/src/services/utils.ts @@ -23,34 +23,6 @@ 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); } @@ -423,7 +395,6 @@ export default { isElectron, isEmptyOrWhitespace, isMac, - isRunningUnderRosetta2, isStringNote, isWindows, md5,