mirror of
https://github.com/immich-app/immich.git
synced 2024-11-24 08:52:28 +02:00
refactor(server): build resources (#10958)
This commit is contained in:
parent
04d0f575b7
commit
5f25e2ce82
@ -45,8 +45,6 @@ Regardless of filesystem, it is not recommended to use a network share for your
|
|||||||
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
|
||||||
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
|
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
|
||||||
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
|
||||||
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server | api |
|
|
||||||
| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | server | microservices |
|
|
||||||
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
|
||||||
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
|
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
|
||||||
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# dev build
|
# dev build
|
||||||
FROM ghcr.io/immich-app/base-server-dev:20240702@sha256:5d675b67826ac643ee64ecf2ef78adac1e491eef9a845f30818a1c0d1338ecc8 as dev
|
FROM ghcr.io/immich-app/base-server-dev:20240708@sha256:2a9e3231c34493cb861299d475c84c031e7f04519dbc895bbebb5017d479a3cb as dev
|
||||||
|
|
||||||
RUN apt-get install --no-install-recommends -yqq tini
|
RUN apt-get install --no-install-recommends -yqq tini
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
@ -41,7 +41,7 @@ RUN npm run build
|
|||||||
|
|
||||||
|
|
||||||
# prod build
|
# prod build
|
||||||
FROM ghcr.io/immich-app/base-server-prod:20240702@sha256:419a873052cf2f012ed1977e4a771a38e68ce64ea1c66047cc06232b1a79bafe
|
FROM ghcr.io/immich-app/base-server-prod:20240708@sha256:0af3a5bb036c9a4b6a5a51becaa6e94fe182f6bc97480d57e8f2e6f994bfa453
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
@ -50,7 +50,7 @@ ENV NODE_ENV=production \
|
|||||||
COPY --from=prod /usr/src/app/node_modules ./node_modules
|
COPY --from=prod /usr/src/app/node_modules ./node_modules
|
||||||
COPY --from=prod /usr/src/app/dist ./dist
|
COPY --from=prod /usr/src/app/dist ./dist
|
||||||
COPY --from=prod /usr/src/app/bin ./bin
|
COPY --from=prod /usr/src/app/bin ./bin
|
||||||
COPY --from=web /usr/src/app/build ./www
|
COPY --from=web /usr/src/app/build /build/www
|
||||||
COPY server/resources resources
|
COPY server/resources resources
|
||||||
COPY server/package.json server/package-lock.json ./
|
COPY server/package.json server/package-lock.json ./
|
||||||
COPY server/start*.sh ./
|
COPY server/start*.sh ./
|
||||||
|
@ -27,13 +27,28 @@ export const WEB_ROOT = process.env.IMMICH_WEB_ROOT || '/usr/src/app/www';
|
|||||||
const HOST_SERVER_PORT = process.env.IMMICH_PORT || '2283';
|
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:' + HOST_SERVER_PORT;
|
||||||
|
|
||||||
const GEODATA_ROOT_PATH = process.env.IMMICH_REVERSE_GEOCODING_ROOT || '/usr/src/resources';
|
|
||||||
|
|
||||||
export const citiesFile = 'cities500.txt';
|
export const citiesFile = 'cities500.txt';
|
||||||
export const geodataDatePath = join(GEODATA_ROOT_PATH, 'geodata-date.txt');
|
|
||||||
export const geodataAdmin1Path = join(GEODATA_ROOT_PATH, 'admin1CodesASCII.txt');
|
const buildFolder = process.env.IMMICH_BUILD_DATA || '/build';
|
||||||
export const geodataAdmin2Path = join(GEODATA_ROOT_PATH, 'admin2Codes.txt');
|
|
||||||
export const geodataCities500Path = join(GEODATA_ROOT_PATH, citiesFile);
|
const folders = {
|
||||||
|
geodata: join(buildFolder, 'geodata'),
|
||||||
|
web: join(buildFolder, 'www'),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resourcePaths = {
|
||||||
|
lockFile: join(buildFolder, 'build-lock.json'),
|
||||||
|
geodata: {
|
||||||
|
dateFile: join(folders.geodata, 'geodata-date.txt'),
|
||||||
|
admin1: join(folders.geodata, 'admin1CodesASCII.txt'),
|
||||||
|
admin2: join(folders.geodata, 'admin2Codes.txt'),
|
||||||
|
cities500: join(folders.geodata, citiesFile),
|
||||||
|
},
|
||||||
|
web: {
|
||||||
|
root: folders.web,
|
||||||
|
indexHtml: join(folders.web, 'index.html'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const MOBILE_REDIRECT = 'app.immich:/';
|
export const MOBILE_REDIRECT = 'app.immich:/';
|
||||||
export const LOGIN_URL = '/auth/login?autoLaunch=0';
|
export const LOGIN_URL = '/auth/login?autoLaunch=0';
|
||||||
|
@ -4,7 +4,7 @@ import { getName } from 'i18n-iso-countries';
|
|||||||
import { createReadStream, existsSync } from 'node:fs';
|
import { createReadStream, existsSync } from 'node:fs';
|
||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import readLine from 'node:readline';
|
import readLine from 'node:readline';
|
||||||
import { citiesFile, geodataAdmin1Path, geodataAdmin2Path, geodataCities500Path, geodataDatePath } from 'src/constants';
|
import { citiesFile, resourcePaths } from 'src/constants';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
|
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
|
||||||
import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
|
import { SystemMetadataKey } from 'src/entities/system-metadata.entity';
|
||||||
@ -37,7 +37,7 @@ export class MapRepository implements IMapRepository {
|
|||||||
|
|
||||||
async init(): Promise<void> {
|
async init(): Promise<void> {
|
||||||
this.logger.log('Initializing metadata repository');
|
this.logger.log('Initializing metadata repository');
|
||||||
const geodataDate = await readFile(geodataDatePath, 'utf8');
|
const geodataDate = await readFile(resourcePaths.geodata.dateFile, 'utf8');
|
||||||
|
|
||||||
// TODO move to service init
|
// TODO move to service init
|
||||||
const geocodingMetadata = await this.metadataRepository.get(SystemMetadataKey.REVERSE_GEOCODING_STATE);
|
const geocodingMetadata = await this.metadataRepository.get(SystemMetadataKey.REVERSE_GEOCODING_STATE);
|
||||||
@ -150,8 +150,8 @@ export class MapRepository implements IMapRepository {
|
|||||||
const queryRunner = this.dataSource.createQueryRunner();
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
await queryRunner.connect();
|
await queryRunner.connect();
|
||||||
|
|
||||||
const admin1 = await this.loadAdmin(geodataAdmin1Path);
|
const admin1 = await this.loadAdmin(resourcePaths.geodata.admin1);
|
||||||
const admin2 = await this.loadAdmin(geodataAdmin2Path);
|
const admin2 = await this.loadAdmin(resourcePaths.geodata.admin2);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await queryRunner.startTransaction();
|
await queryRunner.startTransaction();
|
||||||
@ -221,7 +221,7 @@ export class MapRepository implements IMapRepository {
|
|||||||
admin1Name: admin1Map.get(`${lineSplit[8]}.${lineSplit[10]}`),
|
admin1Name: admin1Map.get(`${lineSplit[8]}.${lineSplit[10]}`),
|
||||||
admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`),
|
admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`),
|
||||||
}),
|
}),
|
||||||
geodataCities500Path,
|
resourcePaths.geodata.cities500,
|
||||||
{ entityFilter: (lineSplit) => lineSplit[7] != 'PPLX' },
|
{ entityFilter: (lineSplit) => lineSplit[7] != 'PPLX' },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { exec as execCallback } from 'node:child_process';
|
|||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
|
import { resourcePaths } from 'src/constants';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { GitHubRelease, IServerInfoRepository, ServerBuildVersions } from 'src/interfaces/server-info.interface';
|
import { GitHubRelease, IServerInfoRepository, ServerBuildVersions } from 'src/interfaces/server-info.interface';
|
||||||
import { Instrumentation } from 'src/utils/instrumentation';
|
import { Instrumentation } from 'src/utils/instrumentation';
|
||||||
@ -61,9 +62,9 @@ export class ServerInfoRepository implements IServerInfoRepository {
|
|||||||
maybeFirstLine('convert --version'),
|
maybeFirstLine('convert --version'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const lockfile = await readFile('build-lock.json')
|
const lockfile = await readFile(resourcePaths.lockFile)
|
||||||
.then((buffer) => JSON.parse(buffer.toString()))
|
.then((buffer) => JSON.parse(buffer.toString()))
|
||||||
.catch(() => this.logger.warn('Failed to read build-lock.json'));
|
.catch(() => this.logger.warn(`Failed to read ${resourcePaths.lockFile}`));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
nodejs: nodejsOutput || process.env.NODE_VERSION || '',
|
nodejs: nodejsOutput || process.env.NODE_VERSION || '',
|
||||||
|
@ -2,8 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||||||
import { Cron, CronExpression, Interval } from '@nestjs/schedule';
|
import { Cron, CronExpression, Interval } from '@nestjs/schedule';
|
||||||
import { NextFunction, Request, Response } from 'express';
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { ONE_HOUR, resourcePaths } from 'src/constants';
|
||||||
import { ONE_HOUR, WEB_ROOT } from 'src/constants';
|
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { AuthService } from 'src/services/auth.service';
|
import { AuthService } from 'src/services/auth.service';
|
||||||
import { JobService } from 'src/services/job.service';
|
import { JobService } from 'src/services/job.service';
|
||||||
@ -56,9 +55,9 @@ export class ApiService {
|
|||||||
ssr(excludePaths: string[]) {
|
ssr(excludePaths: string[]) {
|
||||||
let index = '';
|
let index = '';
|
||||||
try {
|
try {
|
||||||
index = readFileSync(join(WEB_ROOT, 'index.html')).toString();
|
index = readFileSync(resourcePaths.web.indexHtml).toString();
|
||||||
} catch {
|
} catch {
|
||||||
this.logger.warn('Unable to open `www/index.html, skipping SSR.');
|
this.logger.warn(`Unable to open ${resourcePaths.web.indexHtml}, skipping SSR.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return async (request: Request, res: Response, next: NextFunction) => {
|
return async (request: Request, res: Response, next: NextFunction) => {
|
||||||
|
@ -5,7 +5,7 @@ import cookieParser from 'cookie-parser';
|
|||||||
import { existsSync } from 'node:fs';
|
import { existsSync } from 'node:fs';
|
||||||
import sirv from 'sirv';
|
import sirv from 'sirv';
|
||||||
import { ApiModule } from 'src/app.module';
|
import { ApiModule } from 'src/app.module';
|
||||||
import { envName, excludePaths, isDev, serverVersion, WEB_ROOT } from 'src/constants';
|
import { envName, excludePaths, isDev, resourcePaths, serverVersion } from 'src/constants';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
|
||||||
import { ApiService } from 'src/services/api.service';
|
import { ApiService } from 'src/services/api.service';
|
||||||
@ -38,11 +38,11 @@ async function bootstrap() {
|
|||||||
useSwagger(app);
|
useSwagger(app);
|
||||||
|
|
||||||
app.setGlobalPrefix('api', { exclude: excludePaths });
|
app.setGlobalPrefix('api', { exclude: excludePaths });
|
||||||
if (existsSync(WEB_ROOT)) {
|
if (existsSync(resourcePaths.web.root)) {
|
||||||
// copied from https://github.com/sveltejs/kit/blob/679b5989fe62e3964b9a73b712d7b41831aa1f07/packages/adapter-node/src/handler.js#L46
|
// copied from https://github.com/sveltejs/kit/blob/679b5989fe62e3964b9a73b712d7b41831aa1f07/packages/adapter-node/src/handler.js#L46
|
||||||
// provides serving of precompressed assets and caching of immutable assets
|
// provides serving of precompressed assets and caching of immutable assets
|
||||||
app.use(
|
app.use(
|
||||||
sirv(WEB_ROOT, {
|
sirv(resourcePaths.web.root, {
|
||||||
etag: true,
|
etag: true,
|
||||||
gzip: true,
|
gzip: true,
|
||||||
brotli: true,
|
brotli: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user