mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
refactor(desktop): replace resolve-env with build-time dotenv injection
This commit is contained in:
parent
14b561bc39
commit
c8d6883474
7 changed files with 27 additions and 88 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"biome.configurationPath": "./surfsense_web/biome.json"
|
"biome.configurationPath": "./surfsense_web/biome.json",
|
||||||
|
"deepscan.ignoreConfirmWarning": true
|
||||||
}
|
}
|
||||||
|
|
@ -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__
|
# The hosted web frontend URL. Used to intercept OAuth redirects and keep them
|
||||||
NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE=__NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE__
|
# inside the desktop app. Set to your production frontend domain.
|
||||||
NEXT_PUBLIC_ETL_SERVICE=__NEXT_PUBLIC_ETL_SERVICE__
|
HOSTED_FRONTEND_URL=https://surfsense.net
|
||||||
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=
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.5.0",
|
"@types/node": "^25.5.0",
|
||||||
"concurrently": "^9.2.1",
|
"concurrently": "^9.2.1",
|
||||||
|
"dotenv": "^17.3.1",
|
||||||
"electron": "^41.0.2",
|
"electron": "^41.0.2",
|
||||||
"electron-builder": "^26.8.1",
|
"electron-builder": "^26.8.1",
|
||||||
"esbuild": "^0.27.4",
|
"esbuild": "^0.27.4",
|
||||||
|
|
|
||||||
9
surfsense_desktop/pnpm-lock.yaml
generated
9
surfsense_desktop/pnpm-lock.yaml
generated
|
|
@ -18,6 +18,9 @@ importers:
|
||||||
concurrently:
|
concurrently:
|
||||||
specifier: ^9.2.1
|
specifier: ^9.2.1
|
||||||
version: 9.2.1
|
version: 9.2.1
|
||||||
|
dotenv:
|
||||||
|
specifier: ^17.3.1
|
||||||
|
version: 17.3.1
|
||||||
electron:
|
electron:
|
||||||
specifier: ^41.0.2
|
specifier: ^41.0.2
|
||||||
version: 41.0.2
|
version: 41.0.2
|
||||||
|
|
@ -612,6 +615,10 @@ packages:
|
||||||
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
|
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
dotenv@17.3.1:
|
||||||
|
resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
@ -2154,6 +2161,8 @@ snapshots:
|
||||||
|
|
||||||
dotenv@16.6.1: {}
|
dotenv@16.6.1: {}
|
||||||
|
|
||||||
|
dotenv@17.3.1: {}
|
||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind-apply-helpers: 1.0.2
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { build } from 'esbuild';
|
import { build } from 'esbuild';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
const desktopEnv = dotenv.config().parsed || {};
|
||||||
|
|
||||||
const STANDALONE_ROOT = path.join(
|
const STANDALONE_ROOT = path.join(
|
||||||
'..', 'surfsense_web', '.next', 'standalone', 'surfsense_web'
|
'..', 'surfsense_web', '.next', 'standalone', 'surfsense_web'
|
||||||
|
|
@ -104,6 +107,11 @@ async function buildElectron() {
|
||||||
external: ['electron'],
|
external: ['electron'],
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
minify: false,
|
minify: false,
|
||||||
|
define: {
|
||||||
|
'process.env.HOSTED_FRONTEND_URL': JSON.stringify(
|
||||||
|
desktopEnv.HOSTED_FRONTEND_URL || 'https://surfsense.net'
|
||||||
|
),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await build({
|
await build({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { app, BrowserWindow, shell, ipcMain, session } from 'electron';
|
import { app, BrowserWindow, shell, ipcMain, session } from 'electron';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { resolveEnv } from './resolve-env';
|
|
||||||
|
|
||||||
const isDev = !app.isPackaged;
|
const isDev = !app.isPackaged;
|
||||||
let mainWindow: BrowserWindow | null = null;
|
let mainWindow: BrowserWindow | null = null;
|
||||||
|
|
@ -8,12 +7,8 @@ let deepLinkUrl: string | null = null;
|
||||||
let serverPort: number = 3000;
|
let serverPort: number = 3000;
|
||||||
|
|
||||||
const PROTOCOL = 'surfsense';
|
const PROTOCOL = 'surfsense';
|
||||||
// TODO: Hardcoded URL is fragile — production domain may change and
|
// Injected at compile time from .env.desktop via esbuild define
|
||||||
// self-hosted users have their own. Two options:
|
const HOSTED_FRONTEND_URL = process.env.HOSTED_FRONTEND_URL as string;
|
||||||
// 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';
|
|
||||||
|
|
||||||
function getStandalonePath(): string {
|
function getStandalonePath(): string {
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
|
|
@ -39,8 +34,6 @@ async function startNextServer(): Promise<void> {
|
||||||
if (isDev) return;
|
if (isDev) return;
|
||||||
|
|
||||||
const standalonePath = getStandalonePath();
|
const standalonePath = getStandalonePath();
|
||||||
resolveEnv(standalonePath);
|
|
||||||
|
|
||||||
const serverScript = path.join(standalonePath, 'server.js');
|
const serverScript = path.join(standalonePath, 'server.js');
|
||||||
|
|
||||||
// The standalone server.js reads PORT / HOSTNAME from process.env and
|
// The standalone server.js reads PORT / HOSTNAME from process.env and
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue