From c8d68834742aafaf968f1a870665d9dae4baa479 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Wed, 18 Mar 2026 19:27:53 +0200 Subject: [PATCH] refactor(desktop): replace resolve-env with build-time dotenv injection --- .vscode/settings.json | 3 +- surfsense_desktop/.env | 14 ++-- surfsense_desktop/package.json | 1 + surfsense_desktop/pnpm-lock.yaml | 9 +++ surfsense_desktop/scripts/build-electron.mjs | 8 +++ surfsense_desktop/src/main.ts | 11 +--- surfsense_desktop/src/resolve-env.ts | 69 -------------------- 7 files changed, 27 insertions(+), 88 deletions(-) delete mode 100644 surfsense_desktop/src/resolve-env.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index f134660b6..05bd30702 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "biome.configurationPath": "./surfsense_web/biome.json" + "biome.configurationPath": "./surfsense_web/biome.json", + "deepscan.ignoreConfirmWarning": true } \ No newline at end of file diff --git a/surfsense_desktop/.env b/surfsense_desktop/.env index c797d207b..d053aac97 100644 --- a/surfsense_desktop/.env +++ b/surfsense_desktop/.env @@ -1,10 +1,6 @@ -# Placeholders — replaced at app startup with real values (see src/env-replace.ts) +# Electron-specific build-time configuration. +# Set before running pnpm dist:mac / dist:win / dist:linux. -NEXT_PUBLIC_FASTAPI_BACKEND_URL=__NEXT_PUBLIC_FASTAPI_BACKEND_URL__ -NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE=__NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE__ -NEXT_PUBLIC_ETL_SERVICE=__NEXT_PUBLIC_ETL_SERVICE__ -NEXT_PUBLIC_ELECTRIC_URL=__NEXT_PUBLIC_ELECTRIC_URL__ -NEXT_PUBLIC_ELECTRIC_AUTH_MODE=__NEXT_PUBLIC_ELECTRIC_AUTH_MODE__ -NEXT_PUBLIC_DEPLOYMENT_MODE=__NEXT_PUBLIC_DEPLOYMENT_MODE__ -NEXT_PUBLIC_POSTHOG_KEY= -NEXT_PUBLIC_POSTHOG_HOST= +# The hosted web frontend URL. Used to intercept OAuth redirects and keep them +# inside the desktop app. Set to your production frontend domain. +HOSTED_FRONTEND_URL=https://surfsense.net diff --git a/surfsense_desktop/package.json b/surfsense_desktop/package.json index cba82c9d7..750508124 100644 --- a/surfsense_desktop/package.json +++ b/surfsense_desktop/package.json @@ -19,6 +19,7 @@ "devDependencies": { "@types/node": "^25.5.0", "concurrently": "^9.2.1", + "dotenv": "^17.3.1", "electron": "^41.0.2", "electron-builder": "^26.8.1", "esbuild": "^0.27.4", diff --git a/surfsense_desktop/pnpm-lock.yaml b/surfsense_desktop/pnpm-lock.yaml index 6ab91c919..654f927f2 100644 --- a/surfsense_desktop/pnpm-lock.yaml +++ b/surfsense_desktop/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: concurrently: specifier: ^9.2.1 version: 9.2.1 + dotenv: + specifier: ^17.3.1 + version: 17.3.1 electron: specifier: ^41.0.2 version: 41.0.2 @@ -612,6 +615,10 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} + dotenv@17.3.1: + resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2154,6 +2161,8 @@ snapshots: dotenv@16.6.1: {} + dotenv@17.3.1: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 diff --git a/surfsense_desktop/scripts/build-electron.mjs b/surfsense_desktop/scripts/build-electron.mjs index 36892ab64..2c59061b4 100644 --- a/surfsense_desktop/scripts/build-electron.mjs +++ b/surfsense_desktop/scripts/build-electron.mjs @@ -1,6 +1,9 @@ import { build } from 'esbuild'; import fs from 'fs'; import path from 'path'; +import dotenv from 'dotenv'; + +const desktopEnv = dotenv.config().parsed || {}; const STANDALONE_ROOT = path.join( '..', 'surfsense_web', '.next', 'standalone', 'surfsense_web' @@ -104,6 +107,11 @@ async function buildElectron() { external: ['electron'], sourcemap: true, minify: false, + define: { + 'process.env.HOSTED_FRONTEND_URL': JSON.stringify( + desktopEnv.HOSTED_FRONTEND_URL || 'https://surfsense.net' + ), + }, }; await build({ diff --git a/surfsense_desktop/src/main.ts b/surfsense_desktop/src/main.ts index a17905c34..b3782e29b 100644 --- a/surfsense_desktop/src/main.ts +++ b/surfsense_desktop/src/main.ts @@ -1,6 +1,5 @@ import { app, BrowserWindow, shell, ipcMain, session } from 'electron'; import path from 'path'; -import { resolveEnv } from './resolve-env'; const isDev = !app.isPackaged; let mainWindow: BrowserWindow | null = null; @@ -8,12 +7,8 @@ let deepLinkUrl: string | null = null; let serverPort: number = 3000; const PROTOCOL = 'surfsense'; -// TODO: Hardcoded URL is fragile — production domain may change and -// self-hosted users have their own. Two options: -// 1. Load from .env file using dotenv — users edit the file to change it. -// 2. Backend endpoint (GET /api/v1/config/frontend-url) that returns -// the backend's NEXT_FRONTEND_URL — automatic, no file to manage. -const HOSTED_FRONTEND_URL = 'https://surfsense.net'; +// Injected at compile time from .env.desktop via esbuild define +const HOSTED_FRONTEND_URL = process.env.HOSTED_FRONTEND_URL as string; function getStandalonePath(): string { if (isDev) { @@ -39,8 +34,6 @@ async function startNextServer(): Promise { if (isDev) return; const standalonePath = getStandalonePath(); - resolveEnv(standalonePath); - const serverScript = path.join(standalonePath, 'server.js'); // The standalone server.js reads PORT / HOSTNAME from process.env and diff --git a/surfsense_desktop/src/resolve-env.ts b/surfsense_desktop/src/resolve-env.ts deleted file mode 100644 index 3f8a5f2e9..000000000 --- a/surfsense_desktop/src/resolve-env.ts +++ /dev/null @@ -1,69 +0,0 @@ -// TODO: Placeholders are gone after the first run. Self-hosted users -// cannot change their backend URL without reinstalling. - -import fs from 'fs'; -import path from 'path'; - -const DEFAULTS: Record = { - __NEXT_PUBLIC_FASTAPI_BACKEND_URL__: 'http://localhost:8000', - __NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE__: 'LOCAL', - __NEXT_PUBLIC_ETL_SERVICE__: 'DOCLING', - __NEXT_PUBLIC_ELECTRIC_URL__: 'http://localhost:5133', - __NEXT_PUBLIC_ELECTRIC_AUTH_MODE__: 'insecure', - __NEXT_PUBLIC_DEPLOYMENT_MODE__: 'self-hosted', -}; - -function walk(dir: string, replacements: [string, string][]) { - let entries: fs.Dirent[]; - try { - entries = fs.readdirSync(dir, { withFileTypes: true }); - } catch { - return; - } - for (const entry of entries) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) { - walk(full, replacements); - } else if (entry.name.endsWith('.js')) { - let content = fs.readFileSync(full, 'utf8'); - let changed = false; - for (const [placeholder, value] of replacements) { - if (content.includes(placeholder)) { - content = content.replaceAll(placeholder, value); - changed = true; - } - } - if (changed) { - fs.writeFileSync(full, content); - } - } - } -} - -export function resolveEnv(standalonePath: string, overrides?: Record) { - const replacements: [string, string][] = Object.entries(DEFAULTS).map( - ([placeholder, defaultValue]) => { - const envKey = placeholder.replace(/^__|__$/g, ''); - const value = overrides?.[envKey] ?? process.env[envKey] ?? defaultValue; - return [placeholder, value]; - } - ); - - console.log('[resolve-env] Replacing placeholders in standalone build:'); - for (const [placeholder, value] of replacements) { - console.log(` ${placeholder} -> ${value}`); - } - - walk(path.join(standalonePath, '.next'), replacements); - - const serverJs = path.join(standalonePath, 'server.js'); - if (fs.existsSync(serverJs)) { - let content = fs.readFileSync(serverJs, 'utf8'); - for (const [placeholder, value] of replacements) { - if (content.includes(placeholder)) { - content = content.replaceAll(placeholder, value); - } - } - fs.writeFileSync(serverJs, content); - } -}