From 5d726fe3403f2cc64c321567ac371aa7f53bae8d Mon Sep 17 00:00:00 2001 From: zhayujie Date: Sat, 27 Jun 2026 11:13:39 +0800 Subject: [PATCH] fix(desktop): run bundled backend from writable data dir --- common/log.py | 29 +++++++++++++++++++++-------- desktop/src/main/python-manager.ts | 16 +++++++++++++--- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/common/log.py b/common/log.py index c310703c..edc144b2 100644 --- a/common/log.py +++ b/common/log.py @@ -33,15 +33,28 @@ def _reset_logger(log): datefmt="%Y-%m-%d %H:%M:%S", ) ) - file_handle = logging.FileHandler(_log_path(), encoding="utf-8") - file_handle.setFormatter( - logging.Formatter( - "[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d] - %(message)s", - datefmt="%Y-%m-%d %H:%M:%S", - ) - ) - log.addHandler(file_handle) log.addHandler(console_handle) + # File logging is best-effort: if the log path isn't writable (e.g. a + # packaged app installed under Program Files run by a non-admin user, with + # an unwritable CWD), fall back to console-only instead of crashing the + # whole process at import time. + try: + file_handle = logging.FileHandler(_log_path(), encoding="utf-8") + file_handle.setFormatter( + logging.Formatter( + "[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d] - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + ) + log.addHandler(file_handle) + except OSError: + console_handle.handle( + logging.LogRecord( + "log", logging.WARNING, __file__, 0, + "[log] file logging disabled (log path not writable): %s", + (_log_path(),), None, + ) + ) def _get_logger(): diff --git a/desktop/src/main/python-manager.ts b/desktop/src/main/python-manager.ts index 33857f20..8edf5766 100644 --- a/desktop/src/main/python-manager.ts +++ b/desktop/src/main/python-manager.ts @@ -114,9 +114,19 @@ export class PythonBackend extends EventEmitter { if (bundled) { command = bundled args = [] - // The onedir bundle reads data files relative to the executable's dir. - cwd = path.dirname(bundled) - this.emit('log', `Starting bundled backend: ${bundled}`) + // Run from the writable data dir (~/.cow), NOT the install dir. When the + // app is installed under Program Files, a non-admin user has no write + // permission to the executable's folder, so any relative-path write + // during startup would crash the backend (works only as admin). The + // bundle reads its read-only resources via sys._MEIPASS, so cwd is free + // to point elsewhere. + try { + fs.mkdirSync(COW_DATA_DIR, { recursive: true }) + } catch { + // ignore — get_data_root() also ensures the dir on the Python side + } + cwd = COW_DATA_DIR + this.emit('log', `Starting bundled backend: ${bundled} (cwd=${cwd})`) } else { const pythonPath = this.findPython() const appPath = path.join(this.backendPath, 'app.py')