mirror of
https://github.com/immich-app/immich.git
synced 2026-03-04 09:57:33 +03:00
refactor(server): env validation (#13817)
This commit is contained in:
@@ -1,17 +1,18 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { validateSync } from 'class-validator';
|
||||
import { Request, Response } from 'express';
|
||||
import { CLS_ID } from 'nestjs-cls';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { citiesFile, excludePaths } from 'src/constants';
|
||||
import { Telemetry } from 'src/decorators';
|
||||
import { ImmichEnvironment, ImmichHeader, ImmichTelemetry, ImmichWorker, LogLevel } from 'src/enum';
|
||||
import { EnvDto } from 'src/dtos/env.dto';
|
||||
import { ImmichEnvironment, ImmichHeader, ImmichTelemetry, ImmichWorker } from 'src/enum';
|
||||
import { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
|
||||
import { DatabaseExtension } from 'src/interfaces/database.interface';
|
||||
import { QueueName } from 'src/interfaces/job.interface';
|
||||
import { setDifference } from 'src/utils/set';
|
||||
|
||||
// TODO replace src/config validation with class-validator, here
|
||||
|
||||
const productionKeys = {
|
||||
client:
|
||||
'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2LzdTMzJjUkE1KysxTm5WRHNDTQpzcFAvakpISU1xT0pYRm5oNE53QTJPcHorUk1mZGNvOTJQc09naCt3d1FlRXYxVTJjMnBqelRpUS8ybHJLcS9rCnpKUmxYd2M0Y1Vlc1FETUpPRitQMnFPTlBiQUprWHZDWFlCVUxpdENJa29Md2ZoU0dOanlJS2FSRGhkL3ROeU4KOCtoTlJabllUMWhTSWo5U0NrS3hVQ096YXRQVjRtQ0RlclMrYkUrZ0VVZVdwOTlWOWF6dkYwRkltblRXcFFTdwpjOHdFWmdPTWg0c3ZoNmFpY3dkemtQQ3dFTGFrMFZhQkgzMUJFVUNRTGI5K0FJdEhBVXRKQ0t4aGI1V2pzMXM5CmJyWGZpMHZycGdjWi82RGFuWTJxZlNQem5PbXZEMkZycmxTMXE0SkpOM1ZvN1d3LzBZeS95TWNtelRXWmhHdWgKVVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=',
|
||||
@@ -35,8 +36,16 @@ const asSet = <T>(value: string | undefined, defaults: T[]) => {
|
||||
};
|
||||
|
||||
const getEnv = (): EnvData => {
|
||||
const includedWorkers = asSet(process.env.IMMICH_WORKERS_INCLUDE, [ImmichWorker.API, ImmichWorker.MICROSERVICES]);
|
||||
const excludedWorkers = asSet(process.env.IMMICH_WORKERS_EXCLUDE, []);
|
||||
const dto = plainToInstance(EnvDto, process.env);
|
||||
const errors = validateSync(dto);
|
||||
if (errors.length > 0) {
|
||||
throw new Error(
|
||||
`Invalid environment variables: ${errors.map((error) => `${error.property}=${error.value}`).join(', ')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.API, ImmichWorker.MICROSERVICES]);
|
||||
const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []);
|
||||
const workers = [...setDifference(includedWorkers, excludedWorkers)];
|
||||
for (const worker of workers) {
|
||||
if (!WORKER_TYPES.has(worker)) {
|
||||
@@ -44,9 +53,9 @@ const getEnv = (): EnvData => {
|
||||
}
|
||||
}
|
||||
|
||||
const environment = process.env.IMMICH_ENV as ImmichEnvironment;
|
||||
const environment = dto.IMMICH_ENV || ImmichEnvironment.PRODUCTION;
|
||||
const isProd = environment === ImmichEnvironment.PRODUCTION;
|
||||
const buildFolder = process.env.IMMICH_BUILD_DATA || '/build';
|
||||
const buildFolder = dto.IMMICH_BUILD_DATA || '/build';
|
||||
const folders = {
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
dist: resolve(`${__dirname}/..`),
|
||||
@@ -54,18 +63,18 @@ const getEnv = (): EnvData => {
|
||||
web: join(buildFolder, 'www'),
|
||||
};
|
||||
|
||||
const databaseUrl = process.env.DB_URL;
|
||||
const databaseUrl = dto.DB_URL;
|
||||
|
||||
let redisConfig = {
|
||||
host: process.env.REDIS_HOSTNAME || 'redis',
|
||||
port: Number.parseInt(process.env.REDIS_PORT || '') || 6379,
|
||||
db: Number.parseInt(process.env.REDIS_DBINDEX || '') || 0,
|
||||
username: process.env.REDIS_USERNAME || undefined,
|
||||
password: process.env.REDIS_PASSWORD || undefined,
|
||||
path: process.env.REDIS_SOCKET || undefined,
|
||||
host: dto.REDIS_HOSTNAME || 'redis',
|
||||
port: dto.REDIS_PORT || 6379,
|
||||
db: dto.REDIS_DBINDEX || 0,
|
||||
username: dto.REDIS_USERNAME || undefined,
|
||||
password: dto.REDIS_PASSWORD || undefined,
|
||||
path: dto.REDIS_SOCKET || undefined,
|
||||
};
|
||||
|
||||
const redisUrl = process.env.REDIS_URL;
|
||||
const redisUrl = dto.REDIS_URL;
|
||||
if (redisUrl && redisUrl.startsWith('ioredis://')) {
|
||||
try {
|
||||
redisConfig = JSON.parse(Buffer.from(redisUrl.slice(10), 'base64').toString());
|
||||
@@ -75,11 +84,11 @@ const getEnv = (): EnvData => {
|
||||
}
|
||||
|
||||
const includedTelemetries =
|
||||
process.env.IMMICH_TELEMETRY_INCLUDE === 'all'
|
||||
dto.IMMICH_TELEMETRY_INCLUDE === 'all'
|
||||
? new Set(Object.values(ImmichTelemetry))
|
||||
: asSet<ImmichTelemetry>(process.env.IMMICH_TELEMETRY_INCLUDE, []);
|
||||
: asSet<ImmichTelemetry>(dto.IMMICH_TELEMETRY_INCLUDE, []);
|
||||
|
||||
const excludedTelemetries = asSet<ImmichTelemetry>(process.env.IMMICH_TELEMETRY_EXCLUDE, []);
|
||||
const excludedTelemetries = asSet<ImmichTelemetry>(dto.IMMICH_TELEMETRY_EXCLUDE, []);
|
||||
const telemetries = setDifference(includedTelemetries, excludedTelemetries);
|
||||
for (const telemetry of telemetries) {
|
||||
if (!TELEMETRY_TYPES.has(telemetry)) {
|
||||
@@ -88,26 +97,26 @@ const getEnv = (): EnvData => {
|
||||
}
|
||||
|
||||
return {
|
||||
host: process.env.IMMICH_HOST,
|
||||
port: Number(process.env.IMMICH_PORT) || 2283,
|
||||
host: dto.IMMICH_HOST,
|
||||
port: dto.IMMICH_PORT || 2283,
|
||||
environment,
|
||||
configFile: process.env.IMMICH_CONFIG_FILE,
|
||||
logLevel: process.env.IMMICH_LOG_LEVEL as LogLevel,
|
||||
configFile: dto.IMMICH_CONFIG_FILE,
|
||||
logLevel: dto.IMMICH_LOG_LEVEL,
|
||||
|
||||
buildMetadata: {
|
||||
build: process.env.IMMICH_BUILD,
|
||||
buildUrl: process.env.IMMICH_BUILD_URL,
|
||||
buildImage: process.env.IMMICH_BUILD_IMAGE,
|
||||
buildImageUrl: process.env.IMMICH_BUILD_IMAGE_URL,
|
||||
repository: process.env.IMMICH_REPOSITORY,
|
||||
repositoryUrl: process.env.IMMICH_REPOSITORY_URL,
|
||||
sourceRef: process.env.IMMICH_SOURCE_REF,
|
||||
sourceCommit: process.env.IMMICH_SOURCE_COMMIT,
|
||||
sourceUrl: process.env.IMMICH_SOURCE_URL,
|
||||
thirdPartySourceUrl: process.env.IMMICH_THIRD_PARTY_SOURCE_URL,
|
||||
thirdPartyBugFeatureUrl: process.env.IMMICH_THIRD_PARTY_BUG_FEATURE_URL,
|
||||
thirdPartyDocumentationUrl: process.env.IMMICH_THIRD_PARTY_DOCUMENTATION_URL,
|
||||
thirdPartySupportUrl: process.env.IMMICH_THIRD_PARTY_SUPPORT_URL,
|
||||
build: dto.IMMICH_BUILD,
|
||||
buildUrl: dto.IMMICH_BUILD_URL,
|
||||
buildImage: dto.IMMICH_BUILD_IMAGE,
|
||||
buildImageUrl: dto.IMMICH_BUILD_IMAGE_URL,
|
||||
repository: dto.IMMICH_REPOSITORY,
|
||||
repositoryUrl: dto.IMMICH_REPOSITORY_URL,
|
||||
sourceRef: dto.IMMICH_SOURCE_REF,
|
||||
sourceCommit: dto.IMMICH_SOURCE_COMMIT,
|
||||
sourceUrl: dto.IMMICH_SOURCE_URL,
|
||||
thirdPartySourceUrl: dto.IMMICH_THIRD_PARTY_SOURCE_URL,
|
||||
thirdPartyBugFeatureUrl: dto.IMMICH_THIRD_PARTY_BUG_FEATURE_URL,
|
||||
thirdPartyDocumentationUrl: dto.IMMICH_THIRD_PARTY_DOCUMENTATION_URL,
|
||||
thirdPartySupportUrl: dto.IMMICH_THIRD_PARTY_SUPPORT_URL,
|
||||
},
|
||||
|
||||
bull: {
|
||||
@@ -153,26 +162,22 @@ const getEnv = (): EnvData => {
|
||||
? { connectionType: 'url', url: databaseUrl }
|
||||
: {
|
||||
connectionType: 'parts',
|
||||
host: process.env.DB_HOSTNAME || 'database',
|
||||
port: Number(process.env.DB_PORT) || 5432,
|
||||
username: process.env.DB_USERNAME || 'postgres',
|
||||
password: process.env.DB_PASSWORD || 'postgres',
|
||||
database: process.env.DB_DATABASE_NAME || 'immich',
|
||||
host: dto.DB_HOSTNAME || 'database',
|
||||
port: dto.DB_PORT || 5432,
|
||||
username: dto.DB_USERNAME || 'postgres',
|
||||
password: dto.DB_PASSWORD || 'postgres',
|
||||
database: dto.DB_DATABASE_NAME || 'immich',
|
||||
}),
|
||||
},
|
||||
|
||||
skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true',
|
||||
vectorExtension:
|
||||
process.env.DB_VECTOR_EXTENSION === 'pgvector' ? DatabaseExtension.VECTOR : DatabaseExtension.VECTORS,
|
||||
skipMigrations: dto.DB_SKIP_MIGRATIONS ?? false,
|
||||
vectorExtension: dto.DB_VECTOR_EXTENSION === 'pgvector' ? DatabaseExtension.VECTOR : DatabaseExtension.VECTORS,
|
||||
},
|
||||
|
||||
licensePublicKey: isProd ? productionKeys : stagingKeys,
|
||||
|
||||
network: {
|
||||
trustedProxies: (process.env.IMMICH_TRUSTED_PROXIES ?? '')
|
||||
.split(',')
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean),
|
||||
trustedProxies: dto.IMMICH_TRUSTED_PROXIES ?? [],
|
||||
},
|
||||
|
||||
otel: {
|
||||
@@ -203,18 +208,18 @@ const getEnv = (): EnvData => {
|
||||
},
|
||||
|
||||
storage: {
|
||||
ignoreMountCheckErrors: process.env.IMMICH_IGNORE_MOUNT_CHECK_ERRORS === 'true',
|
||||
ignoreMountCheckErrors: !!dto.IMMICH_IGNORE_MOUNT_CHECK_ERRORS,
|
||||
},
|
||||
|
||||
telemetry: {
|
||||
apiPort: Number(process.env.IMMICH_API_METRICS_PORT || '') || 8081,
|
||||
microservicesPort: Number(process.env.IMMICH_MICROSERVICES_METRICS_PORT || '') || 8082,
|
||||
apiPort: dto.IMMICH_API_METRICS_PORT || 8081,
|
||||
microservicesPort: dto.IMMICH_MICROSERVICES_METRICS_PORT || 8082,
|
||||
metrics: telemetries,
|
||||
},
|
||||
|
||||
workers,
|
||||
|
||||
noColor: !!process.env.NO_COLOR,
|
||||
noColor: !!dto.NO_COLOR,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user