mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
refactor(server): remaining nest domains read env via app-config
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
|||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { readEnv } from '../../app-config';
|
||||||
import type { User } from '../../types';
|
import type { User } from '../../types';
|
||||||
import { BackupService } from './backup.service';
|
import { BackupService } from './backup.service';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||||
@@ -148,7 +149,7 @@ export class BackupController {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[backup] PUT auto-settings:', err);
|
console.error('[backup] PUT auto-settings:', err);
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
throw new HttpException({ error: 'Could not save auto-backup settings', detail: process.env.NODE_ENV?.toLowerCase() !== 'production' ? msg : undefined }, 500);
|
throw new HttpException({ error: 'Could not save auto-backup settings', detail: !readEnv().app.isProduction ? msg : undefined }, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { existsSync, readdirSync, writeFileSync, unlinkSync } from 'node:fs';
|
|||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { join, extname } from 'node:path';
|
import { join, extname } from 'node:path';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import { readEnv } from '../../app-config';
|
||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import type { KiReservation } from './kitinerary.types';
|
import type { KiReservation } from './kitinerary.types';
|
||||||
@@ -78,7 +79,7 @@ export class KitineraryExtractorService implements OnModuleInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private findBinary(): string | null {
|
private findBinary(): string | null {
|
||||||
const envPath = process.env.KITINERARY_EXTRACTOR_PATH;
|
const envPath = readEnv().integrations.kitineraryExtractorPath;
|
||||||
if (envPath) {
|
if (envPath) {
|
||||||
if (existsSync(envPath)) return envPath;
|
if (existsSync(envPath)) return envPath;
|
||||||
console.warn(`[KItinerary] KITINERARY_EXTRACTOR_PATH="${envPath}" not found`);
|
console.warn(`[KItinerary] KITINERARY_EXTRACTOR_PATH="${envPath}" not found`);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type { Request } from 'express';
|
|||||||
import { diskStorage } from 'multer';
|
import { diskStorage } from 'multer';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { readEnv } from '../../app-config';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import type { User } from '../../types';
|
import type { User } from '../../types';
|
||||||
import { CollectionsService } from './collections.service';
|
import { CollectionsService } from './collections.service';
|
||||||
@@ -195,7 +196,7 @@ export class CollectionsController {
|
|||||||
@UploadedFile() file: Express.Multer.File | undefined,
|
@UploadedFile() file: Express.Multer.File | undefined,
|
||||||
@Headers('x-socket-id') socketId?: string,
|
@Headers('x-socket-id') socketId?: string,
|
||||||
) {
|
) {
|
||||||
if (process.env.DEMO_MODE?.toLowerCase() === 'true' && isDemoEmail(user.email)) {
|
if (readEnv().demo.enabled && isDemoEmail(user.email)) {
|
||||||
throw new HttpException({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }, 403);
|
throw new HttpException({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }, 403);
|
||||||
}
|
}
|
||||||
if (!file) throw new HttpException({ error: 'No image uploaded' }, 400);
|
if (!file) throw new HttpException({ error: 'No image uploaded' }, 400);
|
||||||
@@ -366,7 +367,7 @@ export class CollectionsController {
|
|||||||
@Post(':id/cover')
|
@Post(':id/cover')
|
||||||
@UseInterceptors(FileInterceptor('cover', COVER_UPLOAD))
|
@UseInterceptors(FileInterceptor('cover', COVER_UPLOAD))
|
||||||
uploadCover(@CurrentUser() user: User, @Param('id') id: string, @UploadedFile() file: Express.Multer.File | undefined, @Headers('x-socket-id') socketId?: string) {
|
uploadCover(@CurrentUser() user: User, @Param('id') id: string, @UploadedFile() file: Express.Multer.File | undefined, @Headers('x-socket-id') socketId?: string) {
|
||||||
if (process.env.DEMO_MODE?.toLowerCase() === 'true' && isDemoEmail(user.email)) {
|
if (readEnv().demo.enabled && isDemoEmail(user.email)) {
|
||||||
throw new HttpException({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }, 403);
|
throw new HttpException({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }, 403);
|
||||||
}
|
}
|
||||||
if (!file) throw new HttpException({ error: 'No image uploaded' }, 400);
|
if (!file) throw new HttpException({ error: 'No image uploaded' }, 400);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
|
import { readEnv } from '../../app-config';
|
||||||
import { FeedsService } from './feeds.service';
|
import { FeedsService } from './feeds.service';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||||
import { CurrentUser } from '../auth/current-user.decorator';
|
import { CurrentUser } from '../auth/current-user.decorator';
|
||||||
@@ -23,7 +24,8 @@ import { db } from '../../db/database';
|
|||||||
// the link is still absolute and copy-pasteable as webcal:// instead of a dead
|
// the link is still absolute and copy-pasteable as webcal:// instead of a dead
|
||||||
// relative path.
|
// relative path.
|
||||||
function resolveFeedBase(req: Request): string {
|
function resolveFeedBase(req: Request): string {
|
||||||
const configured = (process.env.APP_URL || '').replace(/\/$/, '');
|
// Single trailing slash stripped on purpose (legacy parity; notifications strips all).
|
||||||
|
const configured = (readEnv().app.appUrl || '').replace(/\/$/, '');
|
||||||
if (configured) return configured;
|
if (configured) return configured;
|
||||||
const host = req.get('host');
|
const host = req.get('host');
|
||||||
return host ? `${req.protocol}://${host}` : '';
|
return host ? `${req.protocol}://${host}` : '';
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { diskStorage } from 'multer';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { readEnv } from '../../app-config';
|
||||||
import type { User } from '../../types';
|
import type { User } from '../../types';
|
||||||
import { FilesService } from './files.service';
|
import { FilesService } from './files.service';
|
||||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||||
@@ -106,7 +107,7 @@ export class FilesController {
|
|||||||
const cleanup = () => { if (file?.path) { try { fs.unlinkSync(file.path); } catch { /* best-effort */ } } };
|
const cleanup = () => { if (file?.path) { try { fs.unlinkSync(file.path); } catch { /* best-effort */ } } };
|
||||||
try {
|
try {
|
||||||
const trip = this.requireTrip(tripId, user);
|
const trip = this.requireTrip(tripId, user);
|
||||||
if (process.env.DEMO_MODE?.toLowerCase() === 'true' && isDemoEmail(user.email)) {
|
if (readEnv().demo.enabled && isDemoEmail(user.email)) {
|
||||||
throw new HttpException({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }, 403);
|
throw new HttpException({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }, 403);
|
||||||
}
|
}
|
||||||
if (!this.files.can('file_upload', trip, user)) {
|
if (!this.files.can('file_upload', trip, user)) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { memoryStorage } from 'multer';
|
import { memoryStorage } from 'multer';
|
||||||
|
import { readEnv } from '../../app-config';
|
||||||
import type { User } from '../../types';
|
import type { User } from '../../types';
|
||||||
import { PlacesService } from './places.service';
|
import { PlacesService } from './places.service';
|
||||||
import { isUpdateConflict } from '../../services/conflictResult';
|
import { isUpdateConflict } from '../../services/conflictResult';
|
||||||
@@ -283,7 +284,7 @@ export class PlacesController {
|
|||||||
) {
|
) {
|
||||||
const trip = this.requireTrip(tripId, user);
|
const trip = this.requireTrip(tripId, user);
|
||||||
this.requireEdit(trip, user);
|
this.requireEdit(trip, user);
|
||||||
if (process.env.DEMO_MODE?.toLowerCase() === 'true' && isDemoEmail(user.email)) {
|
if (readEnv().demo.enabled && isDemoEmail(user.email)) {
|
||||||
throw new HttpException({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }, 403);
|
throw new HttpException({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }, 403);
|
||||||
}
|
}
|
||||||
if (!file) {
|
if (!file) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ArgumentsHost, Catch, ExceptionFilter, NotFoundException } from '@nestjs/common';
|
import { ArgumentsHost, Catch, ExceptionFilter, NotFoundException } from '@nestjs/common';
|
||||||
import type { Request, Response } from 'express';
|
import type { Request, Response } from 'express';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { readEnv } from '../../app-config';
|
||||||
import { PUBLIC_DIR } from './platform.routes';
|
import { PUBLIC_DIR } from './platform.routes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,7 +25,8 @@ export class SpaFallbackFilter implements ExceptionFilter {
|
|||||||
const req = ctx.getRequest<Request>();
|
const req = ctx.getRequest<Request>();
|
||||||
const res = ctx.getResponse<Response>();
|
const res = ctx.getResponse<Response>();
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'production' && req.method === 'GET') {
|
// Case-sensitive on purpose (legacy parity).
|
||||||
|
if (readEnv().app.nodeEnv === 'production' && req.method === 'GET') {
|
||||||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
||||||
res.sendFile(path.join(PUBLIC_DIR, 'index.html'));
|
res.sendFile(path.join(PUBLIC_DIR, 'index.html'));
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -11,11 +11,13 @@
|
|||||||
* touches that gate — but the two properties above are why loading unsigned local
|
* touches that gate — but the two properties above are why loading unsigned local
|
||||||
* code must be gated behind an explicit flag on top of the admin + kill-switch gates.
|
* code must be gated behind an explicit flag on top of the admin + kill-switch gates.
|
||||||
*
|
*
|
||||||
* Enable ONLY by setting `TREK_PLUGINS_DEV_LINK=1`. Off (absent/any other value)
|
* Enable ONLY by setting `TREK_PLUGINS_DEV_LINK` to an explicit truthy value
|
||||||
* by default, so it can never be silently on in a shared or production deployment.
|
* (1/true/on/yes). Off (absent/any other value) by default, so it can never be
|
||||||
|
* silently on in a shared or production deployment.
|
||||||
*/
|
*/
|
||||||
|
import { readEnv } from '../../app-config';
|
||||||
export function devLinkEnabled(): boolean {
|
export function devLinkEnabled(): boolean {
|
||||||
return process.env.TREK_PLUGINS_DEV_LINK === '1';
|
return readEnv().plugins.devLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provenance marker stamped on a dev-linked plugin row (free-text `source_repo`). */
|
/** Provenance marker stamped on a dev-linked plugin row (free-text `source_repo`). */
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { readEnv } from '../../../app-config';
|
||||||
import { db, canAccessTrip } from '../../../db/database';
|
import { db, canAccessTrip } from '../../../db/database';
|
||||||
import { broadcast, broadcastToUser } from '../../../websocket';
|
import { broadcast, broadcastToUser } from '../../../websocket';
|
||||||
import { listBudgetItems } from '../../../services/budgetService';
|
import { listBudgetItems } from '../../../services/budgetService';
|
||||||
@@ -279,7 +280,7 @@ export function createRealRpcHost(id: string, granted: ReadonlySet<string>, rout
|
|||||||
// bytes to the shared demo instance, even through a plugin's db:write:files.
|
// bytes to the shared demo instance, even through a plugin's db:write:files.
|
||||||
// Only resolve the email when demo mode is actually on — keeps the hot path
|
// Only resolve the email when demo mode is actually on — keeps the hot path
|
||||||
// (and the schema surface) untouched for self-hosted installs.
|
// (and the schema surface) untouched for self-hosted installs.
|
||||||
if (process.env.DEMO_MODE?.toLowerCase() === 'true') {
|
if (readEnv().demo.enabled) {
|
||||||
const uploader = db.prepare('SELECT email FROM users WHERE id = ?').get(actingUserId) as { email?: string } | undefined;
|
const uploader = db.prepare('SELECT email FROM users WHERE id = ?').get(actingUserId) as { email?: string } | undefined;
|
||||||
if (isDemoEmail(uploader?.email)) throw new ForbiddenResource('Uploads are disabled in demo mode.');
|
if (isDemoEmail(uploader?.email)) throw new ForbiddenResource('Uploads are disabled in demo mode.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
*
|
*
|
||||||
* Pure + dependency-free (a clock is injected) so it unit-tests without timers.
|
* Pure + dependency-free (a clock is injected) so it unit-tests without timers.
|
||||||
*/
|
*/
|
||||||
|
import { readEnv } from '../../../app-config';
|
||||||
|
|
||||||
/** A refilling token bucket: `capacity` tokens, refilled at `refillPerSec`. */
|
/** A refilling token bucket: `capacity` tokens, refilled at `refillPerSec`. */
|
||||||
export class TokenBucket {
|
export class TokenBucket {
|
||||||
@@ -51,10 +52,12 @@ export interface RpcLimitConfig {
|
|||||||
|
|
||||||
// Generous defaults: a legitimate plugin handling a request makes a handful of ctx
|
// Generous defaults: a legitimate plugin handling a request makes a handful of ctx
|
||||||
// calls; these only bite a runaway loop. Overridable via env for tuning.
|
// calls; these only bite a runaway loop. Overridable via env for tuning.
|
||||||
|
// Frozen at import on purpose (legacy timing) — the limits are boot-stable.
|
||||||
|
const pluginTuning = readEnv().plugins;
|
||||||
export const DEFAULT_RPC_LIMIT: RpcLimitConfig = {
|
export const DEFAULT_RPC_LIMIT: RpcLimitConfig = {
|
||||||
burst: Number(process.env.TREK_PLUGIN_RPC_BURST) || 60,
|
burst: pluginTuning.rpcBurst,
|
||||||
perSec: Number(process.env.TREK_PLUGIN_RPC_PER_SEC) || 20,
|
perSec: pluginTuning.rpcPerSec,
|
||||||
maxInFlight: Number(process.env.TREK_PLUGIN_RPC_INFLIGHT) || 16,
|
maxInFlight: pluginTuning.rpcInflight,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Plugin log volume (ctx.log.*, stdout/stderr, unknown evt topics) reaches the
|
// Plugin log volume (ctx.log.*, stdout/stderr, unknown evt topics) reaches the
|
||||||
@@ -63,8 +66,8 @@ export const DEFAULT_RPC_LIMIT: RpcLimitConfig = {
|
|||||||
// `while (true) ctx.log.error(...)` loop can't pin the host thread; a legitimate
|
// `while (true) ctx.log.error(...)` loop can't pin the host thread; a legitimate
|
||||||
// plugin logs far below this and never notices. Excess lines are dropped.
|
// plugin logs far below this and never notices. Excess lines are dropped.
|
||||||
export const DEFAULT_LOG_LIMIT = {
|
export const DEFAULT_LOG_LIMIT = {
|
||||||
burst: Number(process.env.TREK_PLUGIN_LOG_BURST) || 50,
|
burst: pluginTuning.logBurst,
|
||||||
perSec: Number(process.env.TREK_PLUGIN_LOG_PER_SEC) || 10,
|
perSec: pluginTuning.logPerSec,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Per-plugin limiter: a token bucket + an in-flight gauge. */
|
/** Per-plugin limiter: a token bucket + an in-flight gauge. */
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
import { readEnv } from '../../../app-config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Host-version compatibility for plugins (#plugins). A manifest declares the TREK
|
* Host-version compatibility for plugins (#plugins). A manifest declares the TREK
|
||||||
@@ -11,7 +12,7 @@ import semver from 'semver';
|
|||||||
|
|
||||||
/** The running TREK version (same source as the rest of the app). */
|
/** The running TREK version (same source as the rest of the app). */
|
||||||
export function hostVersion(): string {
|
export function hostVersion(): string {
|
||||||
return process.env.APP_VERSION || (require('../../../../package.json') as { version: string }).version;
|
return readEnv().app.appVersion || (require('../../../../package.json') as { version: string }).version;
|
||||||
}
|
}
|
||||||
|
|
||||||
let warnedUnparseable = false;
|
let warnedUnparseable = false;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { readEnv } from '../../app-config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filesystem layout for the plugin system (#plugins). Code and data are two
|
* Filesystem layout for the plugin system (#plugins). Code and data are two
|
||||||
@@ -16,10 +17,10 @@ const DATA_ROOT = path.resolve(__dirname, '../../../data');
|
|||||||
// Read lazily so a deployment (or a test) can point these at dedicated volumes
|
// Read lazily so a deployment (or a test) can point these at dedicated volumes
|
||||||
// via env without import-order surprises.
|
// via env without import-order surprises.
|
||||||
export function pluginsCodeRoot(): string {
|
export function pluginsCodeRoot(): string {
|
||||||
return process.env.TREK_PLUGINS_DIR || path.join(DATA_ROOT, 'plugins');
|
return readEnv().plugins.dir || path.join(DATA_ROOT, 'plugins');
|
||||||
}
|
}
|
||||||
export function pluginsDataRoot(): string {
|
export function pluginsDataRoot(): string {
|
||||||
return process.env.TREK_PLUGINS_DATA_DIR || path.join(DATA_ROOT, 'plugins-data');
|
return readEnv().plugins.dataDir || path.join(DATA_ROOT, 'plugins-data');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A plugin's installed code directory (contains trek-plugin.json + server/index.js). */
|
/** A plugin's installed code directory (contains trek-plugin.json + server/index.js). */
|
||||||
@@ -112,7 +113,7 @@ export function serverCodeRoot(): string {
|
|||||||
*/
|
*/
|
||||||
let warnedPermissionsOff = false;
|
let warnedPermissionsOff = false;
|
||||||
export function pluginPermissionArgs(pluginId: string): string[] {
|
export function pluginPermissionArgs(pluginId: string): string[] {
|
||||||
if ((process.env.TREK_PLUGIN_PERMISSIONS ?? 'on').toLowerCase() === 'off') {
|
if (readEnv().plugins.permissionsOff) {
|
||||||
// The OS permission jail is the boundary that stops an installed plugin from
|
// The OS permission jail is the boundary that stops an installed plugin from
|
||||||
// reading trek.db / the secret files / shelling out. Turning it off makes plugin
|
// reading trek.db / the secret files / shelling out. Turning it off makes plugin
|
||||||
// isolation crash-only — surface that loudly once so an operator can't leave it
|
// isolation crash-only — surface that loudly once so an operator can't leave it
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { readEnv } from '../../../app-config';
|
||||||
import { db } from '../../../db/database';
|
import { db } from '../../../db/database';
|
||||||
import { discoverPlugins } from '../install/discovery';
|
import { discoverPlugins } from '../install/discovery';
|
||||||
import { hostSatisfies, hostVersion, normalizedHost } from '../install/host-compat';
|
import { hostSatisfies, hostVersion, normalizedHost } from '../install/host-compat';
|
||||||
@@ -24,9 +25,8 @@ import semver from 'semver';
|
|||||||
* (registers INACTIVE). Nothing executes on install; activation is separate.
|
* (registers INACTIVE). Nothing executes on install; activation is separate.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const REGISTRY_URL =
|
// Frozen at import on purpose (legacy timing) — the registry URL is boot-stable.
|
||||||
process.env.TREK_PLUGIN_REGISTRY_URL ||
|
const REGISTRY_URL = readEnv().plugins.registryUrl;
|
||||||
'https://raw.githubusercontent.com/liketrek/TREK-Plugins/main/dist/index.json';
|
|
||||||
const CACHE_TTL = 30 * 60 * 1000;
|
const CACHE_TTL = 30 * 60 * 1000;
|
||||||
const MANIFEST_MAX_BYTES = 256 * 1024;
|
const MANIFEST_MAX_BYTES = 256 * 1024;
|
||||||
// Sideload upload ceiling — matches the SDK `pack` limit (50 MB) plus zip overhead.
|
// Sideload upload ceiling — matches the SDK `pack` limit (50 MB) plus zip overhead.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { fork, type ChildProcess } from 'node:child_process';
|
import { fork, type ChildProcess } from 'node:child_process';
|
||||||
|
import { readEnv } from '../../../app-config';
|
||||||
import { resolveChildEntry, pluginCodeDir, pluginRealCodeDir, pluginPermissionArgs, ensurePluginModuleType } from '../paths';
|
import { resolveChildEntry, pluginCodeDir, pluginRealCodeDir, pluginPermissionArgs, ensurePluginModuleType } from '../paths';
|
||||||
import type { Envelope, RpcError, RpcRequest } from '../protocol/envelope';
|
import type { Envelope, RpcError, RpcRequest } from '../protocol/envelope';
|
||||||
import type { PluginRpcHost } from '../host/rpc-host';
|
import type { PluginRpcHost } from '../host/rpc-host';
|
||||||
@@ -89,7 +90,7 @@ const DEFAULTS: Required<SupervisorTuning> = {
|
|||||||
// Hard RSS ceiling — the real memory cap. --max-old-space-size only bounds the
|
// Hard RSS ceiling — the real memory cap. --max-old-space-size only bounds the
|
||||||
// V8 heap; Buffers/ArrayBuffers/native allocations sail past it, so a plugin
|
// V8 heap; Buffers/ArrayBuffers/native allocations sail past it, so a plugin
|
||||||
// could OOM the box while staying "under" the heap limit. Overridable via env.
|
// could OOM the box while staying "under" the heap limit. Overridable via env.
|
||||||
maxRssBytes: (Number(process.env.TREK_PLUGIN_MAX_RSS_MB) || 300) * 1024 * 1024,
|
maxRssBytes: readEnv().plugins.maxRssMb * 1024 * 1024,
|
||||||
};
|
};
|
||||||
|
|
||||||
// A plugin may only act as a provider for a hook it BOTH implements (reported by
|
// A plugin may only act as a provider for a hook it BOTH implements (reported by
|
||||||
@@ -482,9 +483,10 @@ export class PluginSupervisor {
|
|||||||
// code and a plugin can never reach a self-hoster's LAN service (a Gotify, an
|
// code and a plugin can never reach a self-hoster's LAN service (a Gotify, an
|
||||||
// ntfy, an Ollama) no matter what the admin sets. Forwarded only when set, so
|
// ntfy, an Ollama) no matter what the admin sets. Forwarded only when set, so
|
||||||
// the default stays the secure block-private policy.
|
// the default stays the secure block-private policy.
|
||||||
...(process.env.TREK_PLUGIN_ALLOW_PRIVATE_EGRESS
|
// Normalized to the literal 'on': the child and plugin-sdk keep their
|
||||||
? { TREK_PLUGIN_ALLOW_PRIVATE_EGRESS: process.env.TREK_PLUGIN_ALLOW_PRIVATE_EGRESS }
|
// `=== 'on'` check (they are exempt from app-config), so the host-side
|
||||||
: {}),
|
// boolean-family coercion must collapse to the one literal they accept.
|
||||||
|
...(readEnv().plugins.allowPrivateEgress ? { TREK_PLUGIN_ALLOW_PRIVATE_EGRESS: 'on' } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
sup.child = child;
|
sup.child = child;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import type { Request, Response } from 'express';
|
|||||||
import { diskStorage } from 'multer';
|
import { diskStorage } from 'multer';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { readEnv } from '../../app-config';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import type { User } from '../../types';
|
import type { User } from '../../types';
|
||||||
import { TripsService } from './trips.service';
|
import { TripsService } from './trips.service';
|
||||||
@@ -177,7 +178,7 @@ export class TripsController {
|
|||||||
@Post(':id/cover')
|
@Post(':id/cover')
|
||||||
@UseInterceptors(FileInterceptor('cover', COVER_UPLOAD))
|
@UseInterceptors(FileInterceptor('cover', COVER_UPLOAD))
|
||||||
cover(@CurrentUser() user: User, @Param('id') id: string, @UploadedFile() file: Express.Multer.File | undefined) {
|
cover(@CurrentUser() user: User, @Param('id') id: string, @UploadedFile() file: Express.Multer.File | undefined) {
|
||||||
if (process.env.DEMO_MODE?.toLowerCase() === 'true' && isDemoEmail(user.email)) {
|
if (readEnv().demo.enabled && isDemoEmail(user.email)) {
|
||||||
throw new HttpException({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }, 403);
|
throw new HttpException({ error: 'Uploads are disabled in demo mode. Self-host TREK for full functionality.' }, 403);
|
||||||
}
|
}
|
||||||
const access = this.trips.canAccessTrip(id, user.id);
|
const access = this.trips.canAccessTrip(id, user.id);
|
||||||
|
|||||||
Reference in New Issue
Block a user