refactor(desktop): replace resolve-env with build-time dotenv injection

This commit is contained in:
CREDO23 2026-03-18 19:27:53 +02:00
parent 14b561bc39
commit c8d6883474
7 changed files with 27 additions and 88 deletions

View file

@ -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<void> {
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

View file

@ -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<string, string> = {
__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<string, string>) {
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);
}
}