mirror of
https://github.com/immich-app/immich.git
synced 2024-12-28 06:31:58 +00:00
fix(server): use default values when not provided
This commit is contained in:
parent
62a490eca2
commit
62a8e66564
26 changed files with 392 additions and 287 deletions
125
server/package-lock.json
generated
125
server/package-lock.json
generated
|
@ -11,7 +11,6 @@
|
|||
"dependencies": {
|
||||
"@nestjs/bullmq": "^10.0.1",
|
||||
"@nestjs/common": "^10.2.2",
|
||||
"@nestjs/config": "^3.0.0",
|
||||
"@nestjs/core": "^10.2.2",
|
||||
"@nestjs/event-emitter": "^2.0.4",
|
||||
"@nestjs/platform-express": "^10.2.2",
|
||||
|
@ -41,7 +40,6 @@
|
|||
"handlebars": "^4.7.8",
|
||||
"i18n-iso-countries": "^7.6.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"joi": "^17.10.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.4.2",
|
||||
|
@ -1323,19 +1321,6 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="
|
||||
},
|
||||
"node_modules/@hapi/topo": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
|
||||
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/module-importer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||
|
@ -2075,20 +2060,6 @@
|
|||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
|
||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
|
||||
},
|
||||
"node_modules/@nestjs/config": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.3.tgz",
|
||||
"integrity": "sha512-p6yv/CvoBewJ72mBq4NXgOAi2rSQNWx3a+IMJLVKS2uiwFCOQQuiIatGwq6MRjXV3Jr+B41iUO8FIf4xBrZ4/w==",
|
||||
"dependencies": {
|
||||
"dotenv": "16.4.5",
|
||||
"dotenv-expand": "10.0.0",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||
"rxjs": "^7.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/core": {
|
||||
"version": "10.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.4.tgz",
|
||||
|
@ -4801,24 +4772,6 @@
|
|||
"url": "https://ko-fi.com/killymxi"
|
||||
}
|
||||
},
|
||||
"node_modules/@sideway/address": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
||||
"integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sideway/formula": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
|
||||
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="
|
||||
},
|
||||
"node_modules/@sideway/pinpoint": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
|
||||
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
|
@ -7926,14 +7879,6 @@
|
|||
"url": "https://dotenvx.com"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv-expand": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
|
||||
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
|
@ -9834,18 +9779,6 @@
|
|||
"jiti": "bin/jiti.js"
|
||||
}
|
||||
},
|
||||
"node_modules/joi": {
|
||||
"version": "17.13.3",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
|
||||
"integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.3.0",
|
||||
"@hapi/topo": "^5.1.0",
|
||||
"@sideway/address": "^4.1.5",
|
||||
"@sideway/formula": "^3.0.1",
|
||||
"@sideway/pinpoint": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "4.15.9",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
|
||||
|
@ -16111,19 +16044,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ=="
|
||||
},
|
||||
"@hapi/topo": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
|
||||
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
|
||||
"requires": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"@humanwhocodes/module-importer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
|
||||
|
@ -16528,16 +16448,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@nestjs/config": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.3.tgz",
|
||||
"integrity": "sha512-p6yv/CvoBewJ72mBq4NXgOAi2rSQNWx3a+IMJLVKS2uiwFCOQQuiIatGwq6MRjXV3Jr+B41iUO8FIf4xBrZ4/w==",
|
||||
"requires": {
|
||||
"dotenv": "16.4.5",
|
||||
"dotenv-expand": "10.0.0",
|
||||
"lodash": "4.17.21"
|
||||
}
|
||||
},
|
||||
"@nestjs/core": {
|
||||
"version": "10.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.4.tgz",
|
||||
|
@ -18236,24 +18146,6 @@
|
|||
"selderee": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"@sideway/address": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
||||
"integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
|
||||
"requires": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"@sideway/formula": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
|
||||
"integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg=="
|
||||
},
|
||||
"@sideway/pinpoint": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
|
||||
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
|
||||
},
|
||||
"@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
|
@ -20601,11 +20493,6 @@
|
|||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg=="
|
||||
},
|
||||
"dotenv-expand": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
|
||||
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A=="
|
||||
},
|
||||
"eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
|
@ -21998,18 +21885,6 @@
|
|||
"integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
|
||||
"peer": true
|
||||
},
|
||||
"joi": {
|
||||
"version": "17.13.3",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
|
||||
"integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
|
||||
"requires": {
|
||||
"@hapi/hoek": "^9.3.0",
|
||||
"@hapi/topo": "^5.1.0",
|
||||
"@sideway/address": "^4.1.5",
|
||||
"@sideway/formula": "^3.0.1",
|
||||
"@sideway/pinpoint": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"jose": {
|
||||
"version": "4.15.9",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
"dependencies": {
|
||||
"@nestjs/bullmq": "^10.0.1",
|
||||
"@nestjs/common": "^10.2.2",
|
||||
"@nestjs/config": "^3.0.0",
|
||||
"@nestjs/core": "^10.2.2",
|
||||
"@nestjs/event-emitter": "^2.0.4",
|
||||
"@nestjs/platform-express": "^10.2.2",
|
||||
|
@ -67,7 +66,6 @@
|
|||
"handlebars": "^4.7.8",
|
||||
"i18n-iso-countries": "^7.6.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"joi": "^17.10.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.4.2",
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { Inject, Module, OnModuleDestroy, OnModuleInit, ValidationPipe } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE, ModuleRef } from '@nestjs/core';
|
||||
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||
import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule';
|
||||
|
@ -9,7 +8,7 @@ import _ from 'lodash';
|
|||
import { ClsModule } from 'nestjs-cls';
|
||||
import { OpenTelemetryModule } from 'nestjs-otel';
|
||||
import { commands } from 'src/commands';
|
||||
import { bullConfig, bullQueues, clsConfig, immichAppConfig } from 'src/config';
|
||||
import { bullConfig, bullQueues, clsConfig } from 'src/config';
|
||||
import { controllers } from 'src/controllers';
|
||||
import { databaseConfig } from 'src/database.config';
|
||||
import { entities } from 'src/entities';
|
||||
|
@ -41,7 +40,6 @@ const imports = [
|
|||
BullModule.forRoot(bullConfig),
|
||||
BullModule.registerQueue(...bullQueues),
|
||||
ClsModule.forRoot(clsConfig),
|
||||
ConfigModule.forRoot(immichAppConfig),
|
||||
EventEmitterModule.forRoot(),
|
||||
OpenTelemetryModule.forRoot(otelConfig),
|
||||
TypeOrmModule.forRootAsync({
|
||||
|
@ -117,7 +115,6 @@ export class ImmichAdminModule {}
|
|||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot(immichAppConfig),
|
||||
EventEmitterModule.forRoot(),
|
||||
TypeOrmModule.forRoot(databaseConfig),
|
||||
TypeOrmModule.forFeature(entities),
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { RegisterQueueOptions } from '@nestjs/bullmq';
|
||||
import { ConfigModuleOptions } from '@nestjs/config';
|
||||
import { CronExpression } from '@nestjs/schedule';
|
||||
import { QueueOptions } from 'bullmq';
|
||||
import { Request, Response } from 'express';
|
||||
import { RedisOptions } from 'ioredis';
|
||||
import Joi, { Root } from 'joi';
|
||||
import { CLS_ID, ClsModuleOptions } from 'nestjs-cls';
|
||||
import { ImmichHeader } from 'src/dtos/auth.dto';
|
||||
import { LogLevel } from 'src/enum';
|
||||
import { envData, ImmichEnv } from 'src/env';
|
||||
import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface';
|
||||
|
||||
export enum TranscodePolicy {
|
||||
|
@ -75,15 +75,6 @@ export enum ImageFormat {
|
|||
WEBP = 'webp',
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
VERBOSE = 'verbose',
|
||||
DEBUG = 'debug',
|
||||
LOG = 'log',
|
||||
WARN = 'warn',
|
||||
ERROR = 'error',
|
||||
FATAL = 'fatal',
|
||||
}
|
||||
|
||||
export interface SystemConfig {
|
||||
ffmpeg: {
|
||||
crf: number;
|
||||
|
@ -265,8 +256,8 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||
level: LogLevel.LOG,
|
||||
},
|
||||
machineLearning: {
|
||||
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false',
|
||||
url: process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003',
|
||||
enabled: envData.machineLearning.enabled,
|
||||
url: envData.machineLearning.url,
|
||||
clip: {
|
||||
enabled: true,
|
||||
modelName: 'ViT-B-32__openai',
|
||||
|
@ -372,55 +363,8 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||
},
|
||||
});
|
||||
|
||||
const WHEN_DB_URL_SET = Joi.when('DB_URL', {
|
||||
is: Joi.exist(),
|
||||
then: Joi.string().optional(),
|
||||
otherwise: Joi.string().required(),
|
||||
});
|
||||
|
||||
export const immichAppConfig: ConfigModuleOptions = {
|
||||
envFilePath: '.env',
|
||||
isGlobal: true,
|
||||
validationSchema: Joi.object({
|
||||
IMMICH_ENV: Joi.string().optional().valid('development', 'testing', 'production').default('production'),
|
||||
IMMICH_LOG_LEVEL: Joi.string()
|
||||
.optional()
|
||||
.valid(...Object.values(LogLevel)),
|
||||
|
||||
DB_USERNAME: WHEN_DB_URL_SET,
|
||||
DB_PASSWORD: WHEN_DB_URL_SET,
|
||||
DB_DATABASE_NAME: WHEN_DB_URL_SET,
|
||||
DB_URL: Joi.string().optional(),
|
||||
DB_VECTOR_EXTENSION: Joi.string().optional().valid('pgvector', 'pgvecto.rs').default('pgvecto.rs'),
|
||||
DB_SKIP_MIGRATIONS: Joi.boolean().optional().default(false),
|
||||
|
||||
IMMICH_PORT: Joi.number().optional(),
|
||||
IMMICH_API_METRICS_PORT: Joi.number().optional(),
|
||||
IMMICH_MICROSERVICES_METRICS_PORT: Joi.number().optional(),
|
||||
|
||||
IMMICH_TRUSTED_PROXIES: Joi.extend((joi: Root) => ({
|
||||
type: 'stringArray',
|
||||
base: joi.array(),
|
||||
coerce: (value) => (value.split ? value.split(',') : value),
|
||||
}))
|
||||
.stringArray()
|
||||
.single()
|
||||
.items(
|
||||
Joi.string().ip({
|
||||
version: ['ipv4', 'ipv6'],
|
||||
cidr: 'optional',
|
||||
}),
|
||||
),
|
||||
|
||||
IMMICH_METRICS: Joi.boolean().optional().default(false),
|
||||
IMMICH_HOST_METRICS: Joi.boolean().optional().default(false),
|
||||
IMMICH_API_METRICS: Joi.boolean().optional().default(false),
|
||||
IMMICH_IO_METRICS: Joi.boolean().optional().default(false),
|
||||
}),
|
||||
};
|
||||
|
||||
export function parseRedisConfig(): RedisOptions {
|
||||
const redisUrl = process.env.REDIS_URL;
|
||||
const redisUrl = envData.redis.url;
|
||||
if (redisUrl && redisUrl.startsWith('ioredis://')) {
|
||||
try {
|
||||
const decodedString = Buffer.from(redisUrl.slice(10), 'base64').toString();
|
||||
|
@ -430,12 +374,12 @@ export function parseRedisConfig(): RedisOptions {
|
|||
}
|
||||
}
|
||||
return {
|
||||
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: envData.redis.hostname,
|
||||
port: envData.redis.port,
|
||||
db: envData.redis.dbIndex,
|
||||
username: envData.redis.username,
|
||||
password: envData.redis.password,
|
||||
path: envData.redis.socket,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -465,18 +409,6 @@ export const clsConfig: ClsModuleOptions = {
|
|||
},
|
||||
};
|
||||
|
||||
export const getBuildMetadata = () => ({
|
||||
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,
|
||||
});
|
||||
|
||||
const clientLicensePublicKeyProd =
|
||||
'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF2LzdTMzJjUkE1KysxTm5WRHNDTQpzcFAvakpISU1xT0pYRm5oNE53QTJPcHorUk1mZGNvOTJQc09naCt3d1FlRXYxVTJjMnBqelRpUS8ybHJLcS9rCnpKUmxYd2M0Y1Vlc1FETUpPRitQMnFPTlBiQUprWHZDWFlCVUxpdENJa29Md2ZoU0dOanlJS2FSRGhkL3ROeU4KOCtoTlJabllUMWhTSWo5U0NrS3hVQ096YXRQVjRtQ0RlclMrYkUrZ0VVZVdwOTlWOWF6dkYwRkltblRXcFFTdwpjOHdFWmdPTWg0c3ZoNmFpY3dkemtQQ3dFTGFrMFZhQkgzMUJFVUNRTGI5K0FJdEhBVXRKQ0t4aGI1V2pzMXM5CmJyWGZpMHZycGdjWi82RGFuWTJxZlNQem5PbXZEMkZycmxTMXE0SkpOM1ZvN1d3LzBZeS95TWNtelRXWmhHdWgKVVFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=';
|
||||
|
||||
|
@ -484,7 +416,7 @@ const clientLicensePublicKeyStaging =
|
|||
'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFuSUNyTm5jbGpPSC9JdTNtWVVaRQp0dGJLV1c3OGRuajl5M0U2ekk3dU1NUndEckdYWFhkTGhkUDFxSWtlZHh0clVVeUpCMWR4R04yQW91S082MlNGCldrbU9PTmNGQlRBWFZTdjhUNVY0S0VwWnFQYWEwaXpNaGxMaE5sRXEvY1ZKdllrWlh1Z2x6b1o3cG1nbzFSdHgKam1iRm5NNzhrYTFRUUJqOVdLaEw2eWpWRUl2MDdVS0lKWHBNTnNuS2g1V083MjZhYmMzSE9udTlETjY5VnFFRQo3dGZrUnRWNmx2U1NzMkFVMngzT255cHA4ek53b0lPTWRibGsyb09aWWROZzY0Y3l2SzJoU0FlU3NVMFRyOVc5Ckgra0Y5QlNCNlk0QXl0QlVkSmkrK2pMSW5HM2Q5cU9ieFVzTlYrN05mRkF5NjJkL0xNR0xSOC9OUFc0U0s3c0MKRlFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=';
|
||||
|
||||
export const getClientLicensePublicKey = (): string => {
|
||||
if (process.env.IMMICH_ENV === 'production') {
|
||||
if (envData.environment === ImmichEnv.PRODUCTION) {
|
||||
return clientLicensePublicKeyProd;
|
||||
}
|
||||
return clientLicensePublicKeyStaging;
|
||||
|
@ -497,7 +429,7 @@ const serverLicensePublicKeyStaging =
|
|||
'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUE3Sy8yd3ZLUS9NdU8ydi9MUm5saAoyUy9zTHhDOGJiTEw1UUlKOGowQ3BVZW40YURlY2dYMUpKUmtGNlpUVUtpNTdTbEhtS3RSM2JOTzJmdTBUUVg5Ck5WMEJzVzllZVB0MmlTMWl4VVFmTzRObjdvTjZzbEtac01qd29RNGtGRGFmM3VHTlZJc0dMb3UxVWRLUVhpeDEKUlRHcXVTb3NZVjNWRlk3Q1hGYTVWaENBL3poVXNsNGFuVXp3eEF6M01jUFVlTXBaenYvbVZiQlRKVzBPSytWZgpWQUJvMXdYMkVBanpBekVHVzQ3Vko4czhnMnQrNHNPaHFBNStMQjBKVzlORUg5QUpweGZzWE4zSzVtM00yNUJVClZXcTlRYStIdHRENnJ0bnAvcUFweXVkWUdwZk9HYTRCUlZTR1MxMURZM0xrb2FlRzYwUEU5NHpoYjduOHpMWkgKelFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=';
|
||||
|
||||
export const getServerLicensePublicKey = (): string => {
|
||||
if (process.env.IMMICH_ENV === 'production') {
|
||||
if (envData.environment === ImmichEnv.PRODUCTION) {
|
||||
return serverLicensePublicKeyProd;
|
||||
}
|
||||
return serverLicensePublicKeyStaging;
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Duration } from 'luxon';
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { SemVer } from 'semver';
|
||||
import { envData } from 'src/env';
|
||||
|
||||
export const POSTGRES_VERSION_RANGE = '>=14.0.0';
|
||||
export const VECTORS_VERSION_RANGE = '>=0.2 <0.4';
|
||||
|
@ -19,17 +20,11 @@ export const serverVersion = new SemVer(version);
|
|||
|
||||
export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 });
|
||||
export const ONE_HOUR = Duration.fromObject({ hours: 1 });
|
||||
|
||||
export const envName = (process.env.IMMICH_ENV || 'production').toUpperCase();
|
||||
export const isDev = () => process.env.IMMICH_ENV === 'development';
|
||||
export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload';
|
||||
export const WEB_ROOT = process.env.IMMICH_WEB_ROOT || '/usr/src/app/www';
|
||||
const HOST_SERVER_PORT = process.env.IMMICH_PORT || '2283';
|
||||
export const DEFAULT_EXTERNAL_DOMAIN = 'http://localhost:' + HOST_SERVER_PORT;
|
||||
export const DEFAULT_EXTERNAL_DOMAIN = 'http://localhost:2283';
|
||||
|
||||
export const citiesFile = 'cities500.txt';
|
||||
|
||||
const buildFolder = process.env.IMMICH_BUILD_DATA || '/build';
|
||||
const { buildFolder } = envData;
|
||||
|
||||
const folders = {
|
||||
geodata: join(buildFolder, 'geodata'),
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { randomUUID } from 'node:crypto';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { ImageFormat } from 'src/config';
|
||||
import { APP_MEDIA_LOCATION } from 'src/constants';
|
||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { AssetPathType, PathType, PersonPathType } from 'src/entities/move.entity';
|
||||
import { PersonEntity } from 'src/entities/person.entity';
|
||||
import { AssetFileType } from 'src/enum';
|
||||
import { envData } from 'src/env';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
|
@ -24,8 +24,8 @@ export enum StorageFolder {
|
|||
THUMBNAILS = 'thumbs',
|
||||
}
|
||||
|
||||
export const THUMBNAIL_DIR = resolve(join(APP_MEDIA_LOCATION, StorageFolder.THUMBNAILS));
|
||||
export const ENCODED_VIDEO_DIR = resolve(join(APP_MEDIA_LOCATION, StorageFolder.ENCODED_VIDEO));
|
||||
export const THUMBNAIL_DIR = resolve(join(envData.mediaLocation, StorageFolder.THUMBNAILS));
|
||||
export const ENCODED_VIDEO_DIR = resolve(join(envData.mediaLocation, StorageFolder.ENCODED_VIDEO));
|
||||
|
||||
export interface MoveRequest {
|
||||
entityId: string;
|
||||
|
@ -94,7 +94,7 @@ export class StorageCore {
|
|||
}
|
||||
|
||||
static getBaseFolder(folder: StorageFolder) {
|
||||
return join(APP_MEDIA_LOCATION, folder);
|
||||
return join(envData.mediaLocation, folder);
|
||||
}
|
||||
|
||||
static getPersonThumbnailPath(person: PersonEntity) {
|
||||
|
@ -119,7 +119,7 @@ export class StorageCore {
|
|||
|
||||
static isImmichPath(path: string) {
|
||||
const resolvedPath = resolve(path);
|
||||
const resolvedAppMediaLocation = resolve(APP_MEDIA_LOCATION);
|
||||
const resolvedAppMediaLocation = resolve(envData.mediaLocation);
|
||||
const normalizedPath = resolvedPath.endsWith('/') ? resolvedPath : resolvedPath + '/';
|
||||
const normalizedAppMediaLocation = resolvedAppMediaLocation.endsWith('/')
|
||||
? resolvedAppMediaLocation
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Subject } from 'rxjs';
|
|||
import { SystemConfig, defaults } from 'src/config';
|
||||
import { SystemConfigDto } from 'src/dtos/system-config.dto';
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
import { envData } from 'src/env';
|
||||
import { DatabaseLock } from 'src/interfaces/database.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
|
@ -85,13 +86,13 @@ export class SystemConfigCore {
|
|||
}
|
||||
|
||||
isUsingConfigFile() {
|
||||
return !!process.env.IMMICH_CONFIG_FILE;
|
||||
return !!envData.configFile;
|
||||
}
|
||||
|
||||
private async buildConfig() {
|
||||
// load partial
|
||||
const partial = this.isUsingConfigFile()
|
||||
? await this.loadFromFile(process.env.IMMICH_CONFIG_FILE as string)
|
||||
? await this.loadFromFile(envData.configFile as string)
|
||||
: await this.repository.get(SystemMetadataKey.SYSTEM_CONFIG);
|
||||
|
||||
// merge with defaults
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import { envData } from 'src/env';
|
||||
import { DatabaseExtension } from 'src/interfaces/database.interface';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js';
|
||||
|
||||
const url = process.env.DB_URL;
|
||||
const url = envData.database.url;
|
||||
const urlOrParts = url
|
||||
? { url }
|
||||
: {
|
||||
host: process.env.DB_HOSTNAME || 'database',
|
||||
port: Number.parseInt(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: envData.database.hostname,
|
||||
port: envData.database.port,
|
||||
username: envData.database.username,
|
||||
password: envData.database.password,
|
||||
database: envData.database.name,
|
||||
};
|
||||
|
||||
/* eslint unicorn/prefer-module: "off" -- We can fix this when migrating to ESM*/
|
||||
|
@ -34,4 +35,4 @@ export const databaseConfig: PostgresConnectionOptions = {
|
|||
export const dataSource = new DataSource({ ...databaseConfig, host: 'localhost' });
|
||||
|
||||
export const getVectorExtension = () =>
|
||||
process.env.DB_VECTOR_EXTENSION === 'pgvector' ? DatabaseExtension.VECTOR : DatabaseExtension.VECTORS;
|
||||
envData.database.vectorExtension === 'pgvector' ? DatabaseExtension.VECTOR : DatabaseExtension.VECTORS;
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
CQMode,
|
||||
Colorspace,
|
||||
ImageFormat,
|
||||
LogLevel,
|
||||
SystemConfig,
|
||||
ToneMapping,
|
||||
TranscodeHWAccel,
|
||||
|
@ -32,6 +31,7 @@ import {
|
|||
VideoContainer,
|
||||
} from 'src/config';
|
||||
import { CLIPConfig, DuplicateDetectionConfig, FacialRecognitionConfig } from 'src/dtos/model-config.dto';
|
||||
import { LogLevel } from 'src/enum';
|
||||
import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface';
|
||||
import { ValidateBoolean, validateCronExpression } from 'src/validation';
|
||||
|
||||
|
|
|
@ -31,6 +31,15 @@ export enum EntityType {
|
|||
ALBUM = 'ALBUM',
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
VERBOSE = 'verbose',
|
||||
DEBUG = 'debug',
|
||||
LOG = 'log',
|
||||
WARN = 'warn',
|
||||
ERROR = 'error',
|
||||
FATAL = 'fatal',
|
||||
}
|
||||
|
||||
export enum MemoryType {
|
||||
/** pictures taken on this day X years ago */
|
||||
ON_THIS_DAY = 'on_this_day',
|
||||
|
|
299
server/src/env.ts
Normal file
299
server/src/env.ts
Normal file
|
@ -0,0 +1,299 @@
|
|||
import { plainToInstance, Transform, Type } from 'class-transformer';
|
||||
import {
|
||||
buildMessage,
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsIpVersion,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateBy,
|
||||
ValidateNested,
|
||||
validateSync,
|
||||
ValidationOptions,
|
||||
} from 'class-validator';
|
||||
import { LogLevel } from 'src/enum';
|
||||
import { Optional } from 'src/validation';
|
||||
import { isIPRange } from 'validator';
|
||||
|
||||
export enum ImmichEnv {
|
||||
DEVELOPMENT = 'development',
|
||||
TESTING = 'testing',
|
||||
PRODUCTION = 'production',
|
||||
}
|
||||
|
||||
enum VectorExtension {
|
||||
PG_VECTOR = 'pgvector',
|
||||
PG_VECTORS = 'pgvecto.rs',
|
||||
}
|
||||
|
||||
function IsIPRange(version?: IsIpVersion, validationOptions?: ValidationOptions): PropertyDecorator {
|
||||
return ValidateBy(
|
||||
{
|
||||
name: 'isIpRange',
|
||||
constraints: [version],
|
||||
validator: {
|
||||
validate: (value, args): boolean => isIPRange(value, args?.constraints[0]),
|
||||
defaultMessage: buildMessage((eachPrefix) => eachPrefix + '$property must be an ip address', validationOptions),
|
||||
},
|
||||
},
|
||||
validationOptions,
|
||||
);
|
||||
}
|
||||
|
||||
class BuildMetadata {
|
||||
@IsString()
|
||||
build!: string;
|
||||
|
||||
@IsString()
|
||||
buildUrl!: string;
|
||||
|
||||
@IsString()
|
||||
buildImage!: string;
|
||||
|
||||
@IsString()
|
||||
buildImageUrl!: string;
|
||||
|
||||
@IsString()
|
||||
repository!: string;
|
||||
|
||||
@IsString()
|
||||
repositoryUrl!: string;
|
||||
|
||||
@IsString()
|
||||
sourceRef!: string;
|
||||
|
||||
@IsString()
|
||||
sourceCommit!: string;
|
||||
|
||||
@IsString()
|
||||
sourceUrl!: string;
|
||||
}
|
||||
|
||||
class Database {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
url?: string;
|
||||
|
||||
@IsString()
|
||||
username!: string;
|
||||
|
||||
@IsString()
|
||||
password!: string;
|
||||
|
||||
@IsString()
|
||||
hostname!: string;
|
||||
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
port!: number;
|
||||
|
||||
@IsString()
|
||||
name!: string;
|
||||
|
||||
@IsEnum(VectorExtension)
|
||||
vectorExtension!: VectorExtension;
|
||||
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
skipMigrations!: boolean;
|
||||
}
|
||||
|
||||
class MachineLearning {
|
||||
@IsBoolean()
|
||||
enabled!: boolean;
|
||||
|
||||
@IsString()
|
||||
url!: string;
|
||||
}
|
||||
|
||||
class Metrics {
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
apiPort!: number;
|
||||
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
microservicesPort!: number;
|
||||
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
enabled!: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
hostEnabled!: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
apiEnabled!: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
ioEnabled!: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
repoEnabled!: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
jobEnabled!: boolean;
|
||||
}
|
||||
|
||||
class Redis {
|
||||
@IsString()
|
||||
@Optional()
|
||||
url?: string;
|
||||
|
||||
@IsString()
|
||||
hostname = 'redis';
|
||||
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
port = 6379;
|
||||
|
||||
dbIndex = 0;
|
||||
|
||||
username?: string;
|
||||
password?: string;
|
||||
socket?: string;
|
||||
}
|
||||
|
||||
export class EnvData {
|
||||
@IsString()
|
||||
@Optional()
|
||||
configFile?: string;
|
||||
|
||||
@IsEnum(ImmichEnv)
|
||||
environment!: ImmichEnv;
|
||||
|
||||
@IsEnum(LogLevel)
|
||||
@Optional()
|
||||
logLevel?: LogLevel;
|
||||
|
||||
@IsString()
|
||||
mediaLocation!: string;
|
||||
|
||||
@IsString()
|
||||
buildFolder!: string;
|
||||
|
||||
@IsString()
|
||||
@Optional()
|
||||
host?: string;
|
||||
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
port!: number;
|
||||
|
||||
@IsBoolean()
|
||||
@Type(() => Boolean)
|
||||
processInvalidImages!: boolean;
|
||||
|
||||
@IsIPRange(undefined, { each: true })
|
||||
@Transform(({ value }) => (typeof value === 'string' ? value.split(',') : value))
|
||||
@Optional()
|
||||
trustedProxies!: string[];
|
||||
|
||||
@IsString()
|
||||
@Optional()
|
||||
nodeVersion?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@Optional()
|
||||
noColor?: boolean;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => BuildMetadata)
|
||||
buildMetadata!: BuildMetadata;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => Database)
|
||||
database!: Database;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => MachineLearning)
|
||||
machineLearning!: MachineLearning;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => Metrics)
|
||||
metrics!: Metrics;
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => Redis)
|
||||
redis!: Redis;
|
||||
}
|
||||
|
||||
const env = plainToInstance(EnvData, {
|
||||
host: process.env.HOST,
|
||||
port: process.env.IMMICH_PORT || 3001,
|
||||
|
||||
environment: process.env.IMMICH_ENV || ImmichEnv.PRODUCTION,
|
||||
configFile: process.env.IMMICH_CONFIG_FILE,
|
||||
logLevel: process.env.IMMICH_LOG_LEVEL,
|
||||
mediaLocation: process.env.IMMICH_MEDIA_LOCATION || './upload',
|
||||
trustedProxies: process.env.IMMICH_TRUSTED_PROXIES || [],
|
||||
buildFolder: process.env.IMMICH_BUILD_DATA || '/build',
|
||||
|
||||
// TODO move to system config
|
||||
processInvalidImages: process.env.IMMICH_PROCESS_INVALID_IMAGES ?? false,
|
||||
|
||||
nodeVersion: process.env.NODE_VERSION,
|
||||
noColor: !!process.env.NO_COLOR,
|
||||
|
||||
database: {
|
||||
url: process.env.DB_URL,
|
||||
username: process.env.DB_USERNAME || 'postgres',
|
||||
password: process.env.DB_PASSWORD || 'postgres',
|
||||
hostname: process.env.DB_HOSTNAME || 'immich',
|
||||
name: process.env.DB_DATABASE_NAME || 'immich',
|
||||
port: process.env.DB_PORT || 5432,
|
||||
vectorExtension: process.env.DB_VECTOR_EXTENSION || VectorExtension.PG_VECTORS,
|
||||
skipMigrations: process.env.DB_SKIP_MIGRATIONS ?? false,
|
||||
},
|
||||
|
||||
machineLearning: {
|
||||
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED ?? true,
|
||||
url: process.env.IMMICH_MACHINE_LEARNING_URL || 'http://immich-machine-learning:3003',
|
||||
},
|
||||
|
||||
metadata: {
|
||||
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,
|
||||
},
|
||||
|
||||
metrics: {
|
||||
enabled: process.env.IMMICH_METRICS === 'true',
|
||||
apiPort: process.env.IMMICH_METRICS_API_PORT || 8081,
|
||||
microservicesPort: process.env.IMMICH_METRICS_MICROSERVICES_PORT || 8082,
|
||||
hostEnabled: process.env.IMMICH_HOST_METRICS === 'true',
|
||||
apiEnabled: process.env.IMMICH_HOST_METRICS === 'true',
|
||||
ioEnabled: process.env.IMMICH_IO_METRICS === 'true',
|
||||
repoEnabled: process.env.IMMICH_REPO_METRICS === 'true',
|
||||
jobEnabled: process.env.IMMICH_JOB_METRICS === 'true',
|
||||
},
|
||||
|
||||
redis: {
|
||||
url: process.env.REDIS_URL,
|
||||
hostname: process.env.REDIS_HOSTNAME || 'redis',
|
||||
port: process.env.REDIS_PORT || 6379,
|
||||
dbIndex: process.env.REDIS_DBINDEX || 0,
|
||||
username: process.env.REDIS_USERNAME || undefined,
|
||||
password: process.env.REDIS_PASSWORD || undefined,
|
||||
socket: process.env.REDIS_SOCKET || undefined,
|
||||
},
|
||||
});
|
||||
const errors = validateSync(env, {});
|
||||
if (errors.length > 0) {
|
||||
console.error(errors);
|
||||
throw new Error('Invalid environment variables');
|
||||
}
|
||||
|
||||
export const envData = env;
|
|
@ -1,11 +1,11 @@
|
|||
import { LogLevel } from 'src/config';
|
||||
import { LogLevel } from 'src/enum';
|
||||
|
||||
export const ILoggerRepository = 'ILoggerRepository';
|
||||
|
||||
export interface ILoggerRepository {
|
||||
setAppName(name: string): void;
|
||||
setContext(message: string): void;
|
||||
setLogLevel(level: LogLevel): void;
|
||||
setLogLevel(level: LogLevel | false): void;
|
||||
|
||||
verbose(message: any, ...args: any): void;
|
||||
debug(message: any, ...args: any): void;
|
||||
|
|
|
@ -2,7 +2,7 @@ import { CommandFactory } from 'nest-commander';
|
|||
import { fork } from 'node:child_process';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
import { ImmichAdminModule } from 'src/app.module';
|
||||
import { LogLevel } from 'src/config';
|
||||
import { LogLevel } from 'src/enum';
|
||||
import { getWorkers } from 'src/utils/workers';
|
||||
const immichApp = process.argv[2] || process.env.IMMICH_APP;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ConsoleLogger, Injectable, Scope } from '@nestjs/common';
|
||||
import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { LogLevel } from 'src/config';
|
||||
import { LogLevel } from 'src/enum';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { LogColor } from 'src/utils/logger';
|
||||
|
||||
|
@ -25,8 +25,8 @@ export class LoggerRepository extends ConsoleLogger implements ILoggerRepository
|
|||
return isLogLevelEnabled(level, LoggerRepository.logLevels);
|
||||
}
|
||||
|
||||
setLogLevel(level: LogLevel): void {
|
||||
LoggerRepository.logLevels = LOG_LEVELS.slice(LOG_LEVELS.indexOf(level));
|
||||
setLogLevel(level: LogLevel | false): void {
|
||||
LoggerRepository.logLevels = level === false ? [] : LOG_LEVELS.slice(LOG_LEVELS.indexOf(level));
|
||||
}
|
||||
|
||||
protected formatContext(context: string): string {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { readFile } from 'node:fs/promises';
|
|||
import { promisify } from 'node:util';
|
||||
import sharp from 'sharp';
|
||||
import { resourcePaths } from 'src/constants';
|
||||
import { envData } from 'src/env';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { GitHubRelease, IServerInfoRepository, ServerBuildVersions } from 'src/interfaces/server-info.interface';
|
||||
import { Instrumentation } from 'src/utils/instrumentation';
|
||||
|
@ -67,7 +68,7 @@ export class ServerInfoRepository implements IServerInfoRepository {
|
|||
.catch(() => this.logger.warn(`Failed to read ${resourcePaths.lockFile}`));
|
||||
|
||||
return {
|
||||
nodejs: nodejsOutput || process.env.NODE_VERSION || '',
|
||||
nodejs: nodejsOutput || envData.nodeVersion || '',
|
||||
exiftool: await exiftool.version(),
|
||||
ffmpeg: getLockfileVersion('ffmpeg', lockfile) || ffmpegOutput.replaceAll('ffmpeg version', '') || '',
|
||||
libvips: getLockfileVersion('libvips', lockfile) || sharp.versions.vips,
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Duration } from 'luxon';
|
|||
import semver from 'semver';
|
||||
import { getVectorExtension } from 'src/database.config';
|
||||
import { OnEmit } from 'src/decorators';
|
||||
import { envData } from 'src/env';
|
||||
import {
|
||||
DatabaseExtension,
|
||||
DatabaseLock,
|
||||
|
@ -116,7 +117,7 @@ export class DatabaseService {
|
|||
|
||||
await this.checkReindexing();
|
||||
|
||||
if (process.env.DB_SKIP_MIGRATIONS !== 'true') {
|
||||
if (!envData.database.skipMigrations) {
|
||||
await this.databaseRepository.runMigrations();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
|
|||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { AssetPathType } from 'src/entities/move.entity';
|
||||
import { AssetFileType, AssetType } from 'src/enum';
|
||||
import { envData } from 'src/env';
|
||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import {
|
||||
|
@ -224,7 +225,7 @@ export class MediaService {
|
|||
size,
|
||||
colorspace,
|
||||
quality: image.quality,
|
||||
processInvalidImages: process.env.IMMICH_PROCESS_INVALID_IMAGES === 'true',
|
||||
processInvalidImages: envData.processInvalidImages,
|
||||
};
|
||||
|
||||
const outputPath = useExtracted ? extractedPath : asset.originalPath;
|
||||
|
|
|
@ -26,6 +26,7 @@ import { AssetEntity } from 'src/entities/asset.entity';
|
|||
import { PersonPathType } from 'src/entities/move.entity';
|
||||
import { PersonEntity } from 'src/entities/person.entity';
|
||||
import { AssetType, Permission, SourceType, SystemMetadataKey } from 'src/enum';
|
||||
import { envData } from 'src/env';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
|
@ -570,7 +571,7 @@ export class PersonService {
|
|||
colorspace: image.colorspace,
|
||||
quality: image.quality,
|
||||
crop: this.getCrop({ old: { width: oldWidth, height: oldHeight }, new: { width, height } }, { x1, y1, x2, y2 }),
|
||||
processInvalidImages: process.env.IMMICH_PROCESS_INVALID_IMAGES === 'true',
|
||||
processInvalidImages: envData.processInvalidImages,
|
||||
} as const;
|
||||
|
||||
await this.mediaRepository.generateThumbnail(inputPath, thumbnailPath, thumbnailOptions);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { getBuildMetadata, getServerLicensePublicKey } from 'src/config';
|
||||
import { getServerLicensePublicKey } from 'src/config';
|
||||
import { serverVersion } from 'src/constants';
|
||||
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
|
||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||
|
@ -16,6 +16,7 @@ import {
|
|||
UsageByUserDto,
|
||||
} from 'src/dtos/server.dto';
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
import { envData } from 'src/env';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
|
||||
|
@ -55,7 +56,6 @@ export class ServerService {
|
|||
|
||||
async getAboutInfo(): Promise<ServerAboutResponseDto> {
|
||||
const version = `v${serverVersion.toString()}`;
|
||||
const buildMetadata = getBuildMetadata();
|
||||
const buildVersions = await this.serverInfoRepository.getBuildVersions();
|
||||
const licensed = await this.systemMetadataRepository.get(SystemMetadataKey.LICENSE);
|
||||
|
||||
|
@ -63,7 +63,7 @@ export class ServerService {
|
|||
version,
|
||||
versionUrl: `https://github.com/immich-app/immich/releases/tag/${version}`,
|
||||
licensed: !!licensed,
|
||||
...buildMetadata,
|
||||
...envData.buildMetadata,
|
||||
...buildVersions,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
import { instanceToPlain } from 'class-transformer';
|
||||
import _ from 'lodash';
|
||||
import { LogLevel, SystemConfig, defaults } from 'src/config';
|
||||
import { SystemConfig, defaults } from 'src/config';
|
||||
import {
|
||||
supportedDayTokens,
|
||||
supportedHourTokens,
|
||||
|
@ -15,6 +15,7 @@ import {
|
|||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||
import { OnEmit, OnServerEvent } from 'src/decorators';
|
||||
import { SystemConfigDto, SystemConfigTemplateStorageOptionDto, mapConfig } from 'src/dtos/system-config.dto';
|
||||
import { envData } from 'src/env';
|
||||
import { ArgOf, ClientEvent, IEventRepository, ServerEvent } from 'src/interfaces/event.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
|
@ -114,6 +115,6 @@ export class SystemConfigService {
|
|||
}
|
||||
|
||||
private getEnvLogLevel() {
|
||||
return process.env.IMMICH_LOG_LEVEL as LogLevel;
|
||||
return envData.logLevel;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DateTime } from 'luxon';
|
||||
import semver, { SemVer } from 'semver';
|
||||
import { isDev, serverVersion } from 'src/constants';
|
||||
import { serverVersion } from 'src/constants';
|
||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
||||
import { OnEmit, OnServerEvent } from 'src/decorators';
|
||||
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
||||
import { VersionCheckMetadata } from 'src/entities/system-metadata.entity';
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
import { envData, ImmichEnv } from 'src/env';
|
||||
import { ClientEvent, IEventRepository, ServerEvent, ServerEventMap } from 'src/interfaces/event.interface';
|
||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
|
@ -54,7 +55,7 @@ export class VersionService {
|
|||
try {
|
||||
this.logger.debug('Running version check');
|
||||
|
||||
if (isDev()) {
|
||||
if (envData.environment === ImmichEnv.DEVELOPMENT) {
|
||||
return JobStatus.SKIPPED;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,16 +12,14 @@ import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetr
|
|||
import { performance } from 'node:perf_hooks';
|
||||
import { excludePaths, serverVersion } from 'src/constants';
|
||||
import { DecorateAll } from 'src/decorators';
|
||||
import { envData } from 'src/env';
|
||||
|
||||
let metricsEnabled = process.env.IMMICH_METRICS === 'true';
|
||||
export const hostMetrics =
|
||||
process.env.IMMICH_HOST_METRICS == null ? metricsEnabled : process.env.IMMICH_HOST_METRICS === 'true';
|
||||
export const apiMetrics =
|
||||
process.env.IMMICH_API_METRICS == null ? metricsEnabled : process.env.IMMICH_API_METRICS === 'true';
|
||||
export const repoMetrics =
|
||||
process.env.IMMICH_IO_METRICS == null ? metricsEnabled : process.env.IMMICH_IO_METRICS === 'true';
|
||||
export const jobMetrics =
|
||||
process.env.IMMICH_JOB_METRICS == null ? metricsEnabled : process.env.IMMICH_JOB_METRICS === 'true';
|
||||
let metricsEnabled = envData.metrics.enabled;
|
||||
|
||||
export const hostMetrics = metricsEnabled && envData.metrics.hostEnabled;
|
||||
export const apiMetrics = metricsEnabled && envData.metrics.apiEnabled;
|
||||
export const repoMetrics = metricsEnabled && envData.metrics.repoEnabled;
|
||||
export const jobMetrics = metricsEnabled && envData.metrics.jobEnabled;
|
||||
|
||||
metricsEnabled ||= hostMetrics || apiMetrics || repoMetrics || jobMetrics;
|
||||
if (!metricsEnabled && process.env.OTEL_SDK_DISABLED === undefined) {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { HttpException } from '@nestjs/common';
|
||||
import { envData } from 'src/env';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { TypeORMError } from 'typeorm';
|
||||
|
||||
type ColorTextFn = (text: string) => string;
|
||||
|
||||
const isColorAllowed = () => !process.env.NO_COLOR;
|
||||
const isColorAllowed = () => !envData.noColor;
|
||||
const colorIfAllowed = (colorFn: ColorTextFn) => (text: string) => (isColorAllowed() ? colorFn(text) : text);
|
||||
|
||||
export const LogColor = {
|
||||
|
|
|
@ -11,8 +11,9 @@ import _ from 'lodash';
|
|||
import { writeFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { SystemConfig } from 'src/config';
|
||||
import { CLIP_MODEL_INFO, isDev, serverVersion } from 'src/constants';
|
||||
import { CLIP_MODEL_INFO, serverVersion } from 'src/constants';
|
||||
import { ImmichCookie, ImmichHeader } from 'src/dtos/auth.dto';
|
||||
import { envData, ImmichEnv } from 'src/env';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { Metadata } from 'src/middleware/auth.guard';
|
||||
|
||||
|
@ -230,7 +231,7 @@ export const useSwagger = (app: INestApplication, force = false) => {
|
|||
|
||||
SwaggerModule.setup('doc', app, specification, customOptions);
|
||||
|
||||
if (isDev() || force) {
|
||||
if (envData.environment === ImmichEnv.DEVELOPMENT || force) {
|
||||
// Generate API Documentation only in development mode
|
||||
const outputPath = path.resolve(process.cwd(), '../open-api/immich-openapi-specs.json');
|
||||
writeFileSync(outputPath, JSON.stringify(patchOpenAPI(specification), null, 2), { encoding: 'utf8' });
|
||||
|
|
|
@ -5,7 +5,8 @@ import cookieParser from 'cookie-parser';
|
|||
import { existsSync } from 'node:fs';
|
||||
import sirv from 'sirv';
|
||||
import { ApiModule } from 'src/app.module';
|
||||
import { envName, excludePaths, isDev, resourcePaths, serverVersion } from 'src/constants';
|
||||
import { excludePaths, resourcePaths, serverVersion } from 'src/constants';
|
||||
import { envData, ImmichEnv } from 'src/env';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
||||
import { ApiService } from 'src/services/api.service';
|
||||
|
@ -13,35 +14,24 @@ import { isStartUpError } from 'src/utils/events';
|
|||
import { otelStart } from 'src/utils/instrumentation';
|
||||
import { useSwagger } from 'src/utils/misc';
|
||||
|
||||
const host = process.env.HOST;
|
||||
|
||||
function parseTrustedProxy(input?: string) {
|
||||
if (!input) {
|
||||
return [];
|
||||
}
|
||||
// Split on ',' char to allow multiple IPs
|
||||
return input.split(',');
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
process.title = 'immich-api';
|
||||
const otelPort = Number.parseInt(process.env.IMMICH_API_METRICS_PORT ?? '8081');
|
||||
const trustedProxies = parseTrustedProxy(process.env.IMMICH_TRUSTED_PROXIES ?? '');
|
||||
|
||||
otelStart(otelPort);
|
||||
const { port, metrics } = envData;
|
||||
|
||||
otelStart(metrics.apiPort);
|
||||
|
||||
const port = Number(process.env.IMMICH_PORT) || 3001;
|
||||
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
|
||||
const logger = await app.resolve<ILoggerRepository>(ILoggerRepository);
|
||||
|
||||
logger.setAppName('Api');
|
||||
logger.setContext('Bootstrap');
|
||||
app.useLogger(logger);
|
||||
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal', ...trustedProxies]);
|
||||
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal', ...envData.trustedProxies]);
|
||||
app.set('etag', 'strong');
|
||||
app.use(cookieParser());
|
||||
app.use(json({ limit: '10mb' }));
|
||||
if (isDev()) {
|
||||
if (envData.environment === ImmichEnv.DEVELOPMENT) {
|
||||
app.enableCors();
|
||||
}
|
||||
app.useWebSocketAdapter(new WebSocketAdapter(app));
|
||||
|
@ -67,10 +57,13 @@ async function bootstrap() {
|
|||
}
|
||||
app.use(app.get(ApiService).ssr(excludePaths));
|
||||
|
||||
const { host } = envData;
|
||||
const server = await (host ? app.listen(port, host) : app.listen(port));
|
||||
server.requestTimeout = 30 * 60 * 1000;
|
||||
|
||||
logger.log(`Immich Server is listening on ${await app.getUrl()} [v${serverVersion}] [${envName}] `);
|
||||
logger.log(
|
||||
`Immich Server is listening on ${await app.getUrl()} [v${serverVersion}] [${envData.environment.toUpperCase()}] `,
|
||||
);
|
||||
}
|
||||
|
||||
bootstrap().catch((error) => {
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { isMainThread } from 'node:worker_threads';
|
||||
import { MicroservicesModule } from 'src/app.module';
|
||||
import { envName, serverVersion } from 'src/constants';
|
||||
import { serverVersion } from 'src/constants';
|
||||
import { envData } from 'src/env';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
||||
import { isStartUpError } from 'src/utils/events';
|
||||
import { otelStart } from 'src/utils/instrumentation';
|
||||
|
||||
export async function bootstrap() {
|
||||
const otelPort = Number.parseInt(process.env.IMMICH_MICROSERVICES_METRICS_PORT ?? '8082');
|
||||
|
||||
otelStart(otelPort);
|
||||
otelStart(envData.metrics.microservicesPort);
|
||||
|
||||
const app = await NestFactory.create(MicroservicesModule, { bufferLogs: true });
|
||||
const logger = await app.resolve(ILoggerRepository);
|
||||
|
@ -21,7 +20,7 @@ export async function bootstrap() {
|
|||
|
||||
await app.listen(0);
|
||||
|
||||
logger.log(`Immich Microservices is running [v${serverVersion}] [${envName}] `);
|
||||
logger.log(`Immich Microservices is running [v${serverVersion}] [${envData.environment.toUpperCase()}] `);
|
||||
}
|
||||
|
||||
if (!isMainThread) {
|
||||
|
|
Loading…
Reference in a new issue