Skip to content

Commit 661ea35

Browse files
committed
feat: microservices be gone and api is a worker now too
1 parent 581b467 commit 661ea35

File tree

9 files changed

+151
-120
lines changed

9 files changed

+151
-120
lines changed

Diff for: docker/docker-compose.dev.yml

+1-14
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ x-server-build: &server-common
2828
services:
2929
immich-server:
3030
container_name: immich_server
31-
command: ['/usr/src/app/bin/immich-dev', 'immich']
31+
command: ['/usr/src/app/bin/immich-dev']
3232
<<: *server-common
3333
ports:
3434
- 3001:3001
@@ -37,19 +37,6 @@ services:
3737
- redis
3838
- database
3939

40-
immich-microservices:
41-
container_name: immich_microservices
42-
command: ['/usr/src/app/bin/immich-dev', 'microservices']
43-
<<: *server-common
44-
# extends:
45-
# file: hwaccel.transcoding.yml
46-
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
47-
ports:
48-
- 9231:9230
49-
depends_on:
50-
- database
51-
- immich-server
52-
5340
immich-web:
5441
container_name: immich_web
5542
image: immich-web-dev:latest

Diff for: docker/docker-compose.prod.yml

-13
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,13 @@ x-server-build: &server-common
1515
services:
1616
immich-server:
1717
container_name: immich_server
18-
command: ['start.sh', 'immich']
1918
<<: *server-common
2019
ports:
2120
- 2283:3001
2221
depends_on:
2322
- redis
2423
- database
2524

26-
immich-microservices:
27-
container_name: immich_microservices
28-
command: ['start.sh', 'microservices']
29-
<<: *server-common
30-
# extends:
31-
# file: hwaccel.transcoding.yml
32-
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
33-
depends_on:
34-
- redis
35-
- database
36-
- immich-server
37-
3825
immich-machine-learning:
3926
container_name: immich_machine_learning
4027
image: immich-machine-learning:latest

Diff for: docker/docker-compose.yml

-18
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ services:
1212
immich-server:
1313
container_name: immich_server
1414
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
15-
command: ['start.sh', 'immich']
1615
volumes:
1716
- ${UPLOAD_LOCATION}:/usr/src/app/upload
1817
- /etc/localtime:/etc/localtime:ro
@@ -25,23 +24,6 @@ services:
2524
- database
2625
restart: always
2726

28-
immich-microservices:
29-
container_name: immich_microservices
30-
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
31-
# extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/hardware-transcoding
32-
# file: hwaccel.transcoding.yml
33-
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
34-
command: ['start.sh', 'microservices']
35-
volumes:
36-
- ${UPLOAD_LOCATION}:/usr/src/app/upload
37-
- /etc/localtime:/etc/localtime:ro
38-
env_file:
39-
- .env
40-
depends_on:
41-
- redis
42-
- database
43-
restart: always
44-
4527
immich-machine-learning:
4628
container_name: immich_machine_learning
4729
# For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.

Diff for: server/Dockerfile

+1
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,4 @@ ENV PATH="${PATH}:/usr/src/app/bin"
6161
VOLUME /usr/src/app/upload
6262
EXPOSE 3001
6363
ENTRYPOINT ["tini", "--", "/bin/bash"]
64+
CMD ["start.sh"]

Diff for: server/src/main.ts

+20-73
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,9 @@
1-
import { NestFactory } from '@nestjs/core';
2-
import { NestExpressApplication } from '@nestjs/platform-express';
3-
import { json } from 'body-parser';
4-
import cookieParser from 'cookie-parser';
51
import { CommandFactory } from 'nest-commander';
62
import { existsSync } from 'node:fs';
73
import { Worker } from 'node:worker_threads';
8-
import sirv from 'sirv';
9-
import { ApiModule, ImmichAdminModule } from 'src/app.module';
4+
import { ImmichAdminModule } from 'src/app.module';
105
import { LogLevel } from 'src/config';
11-
import { WEB_ROOT, envName, excludePaths, isDev, serverVersion } from 'src/constants';
12-
import { ILoggerRepository } from 'src/interfaces/logger.interface';
13-
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
14-
import { ApiService } from 'src/services/api.service';
15-
import { otelSDK } from 'src/utils/instrumentation';
16-
import { useSwagger } from 'src/utils/misc';
17-
18-
const host = process.env.HOST;
19-
20-
async function bootstrapApi() {
21-
otelSDK.start();
22-
23-
const port = Number(process.env.SERVER_PORT) || 3001;
24-
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
25-
const logger = await app.resolve(ILoggerRepository);
26-
27-
logger.setAppName('ImmichServer');
28-
logger.setContext('ImmichServer');
29-
app.useLogger(logger);
30-
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
31-
app.set('etag', 'strong');
32-
app.use(cookieParser());
33-
app.use(json({ limit: '10mb' }));
34-
if (isDev) {
35-
app.enableCors();
36-
}
37-
app.useWebSocketAdapter(new WebSocketAdapter(app));
38-
useSwagger(app, isDev);
39-
40-
app.setGlobalPrefix('api', { exclude: excludePaths });
41-
if (existsSync(WEB_ROOT)) {
42-
// copied from https://github.com/sveltejs/kit/blob/679b5989fe62e3964b9a73b712d7b41831aa1f07/packages/adapter-node/src/handler.js#L46
43-
// provides serving of precompressed assets and caching of immutable assets
44-
app.use(
45-
sirv(WEB_ROOT, {
46-
etag: true,
47-
gzip: true,
48-
brotli: true,
49-
setHeaders: (res, pathname) => {
50-
if (pathname.startsWith(`/_app/immutable`) && res.statusCode === 200) {
51-
res.setHeader('cache-control', 'public,max-age=31536000,immutable');
52-
}
53-
},
54-
}),
55-
);
56-
}
57-
app.use(app.get(ApiService).ssr(excludePaths));
58-
59-
const server = await (host ? app.listen(port, host) : app.listen(port));
60-
server.requestTimeout = 30 * 60 * 1000;
61-
62-
logger.log(`Immich Server is listening on ${await app.getUrl()} [v${serverVersion}] [${envName}] `);
63-
}
64-
6+
import { getWorkers } from 'src/utils/workers';
657
const immichApp = process.argv[2] || process.env.IMMICH_APP;
668

679
if (process.argv[2] === immichApp) {
@@ -73,35 +15,40 @@ async function bootstrapImmichAdmin() {
7315
await CommandFactory.run(ImmichAdminModule);
7416
}
7517

76-
function bootstrapMicroservicesWorker() {
77-
const worker = new Worker('./dist/workers/microservices.js');
18+
function bootstrapWorker(name: string) {
19+
console.log(`Starting ${name} worker`);
20+
if (!existsSync(`./dist/workers/${name}.js`)) {
21+
console.error(`Worker not found with name: ${name}`);
22+
process.exit(1);
23+
}
24+
25+
const worker = new Worker(`./dist/workers/${name}.js`);
7826
worker.on('exit', (exitCode) => {
7927
if (exitCode !== 0) {
80-
console.error(`Microservices worker exited with code ${exitCode}`);
28+
console.error(`${name} worker exited with code ${exitCode}`);
8129
process.exit(exitCode);
8230
}
8331
});
8432
}
8533

8634
function bootstrap() {
8735
switch (immichApp) {
36+
case 'immich-admin': {
37+
process.title = 'immich_admin_cli';
38+
return bootstrapImmichAdmin();
39+
}
8840
case 'immich': {
8941
process.title = 'immich_server';
90-
if (process.env.INTERNAL_MICROSERVICES === 'true') {
91-
bootstrapMicroservicesWorker();
92-
}
93-
return bootstrapApi();
42+
return bootstrapWorker('api');
9443
}
9544
case 'microservices': {
9645
process.title = 'immich_microservices';
97-
return bootstrapMicroservicesWorker();
98-
}
99-
case 'immich-admin': {
100-
process.title = 'immich_admin_cli';
101-
return bootstrapImmichAdmin();
46+
return bootstrapWorker('microservices');
10247
}
10348
default: {
104-
throw new Error(`Invalid app name: ${immichApp}. Expected one of immich|microservices|immich-admin`);
49+
for (const worker of getWorkers()) {
50+
bootstrapWorker(worker);
51+
}
10552
}
10653
}
10754
}

Diff for: server/src/utils/workers.spec.ts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { getWorkers } from 'src/utils/workers';
2+
3+
describe('getWorkers', () => {
4+
beforeEach(() => {
5+
process.env.IMMICH_WORKERS_INCLUDE = '';
6+
process.env.IMMICH_WORKERS_EXCLUDE = '';
7+
});
8+
9+
it('should return default workers', () => {
10+
expect(getWorkers()).toEqual(['api', 'microservices']);
11+
});
12+
13+
it('should return included workers', () => {
14+
process.env.IMMICH_WORKERS_INCLUDE = 'api';
15+
expect(getWorkers()).toEqual(['api']);
16+
});
17+
18+
it('should return excluded workers', () => {
19+
process.env.IMMICH_WORKERS_EXCLUDE = 'api';
20+
expect(getWorkers()).toEqual(['microservices']);
21+
});
22+
23+
it('should return included and excluded workers', () => {
24+
process.env.IMMICH_WORKERS_INCLUDE = 'api,microservices,randomservice';
25+
process.env.IMMICH_WORKERS_EXCLUDE = 'randomservice,microservices';
26+
expect(getWorkers()).toEqual(['api']);
27+
});
28+
29+
it('should return included workers with spaces', () => {
30+
process.env.IMMICH_WORKERS_INCLUDE = 'api, microservices';
31+
expect(getWorkers()).toEqual(['api', 'microservices']);
32+
});
33+
34+
it('should return excluded workers with spaces', () => {
35+
process.env.IMMICH_WORKERS_EXCLUDE = 'api, microservices';
36+
expect(getWorkers()).toEqual([]);
37+
});
38+
39+
it('should return included and excluded workers with spaces', () => {
40+
process.env.IMMICH_WORKERS_INCLUDE = 'api, microservices, randomservice';
41+
process.env.IMMICH_WORKERS_EXCLUDE = 'randomservice,microservices';
42+
expect(getWorkers()).toEqual(['api']);
43+
});
44+
});

Diff for: server/src/utils/workers.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const getWorkers = () => {
2+
let workers = ['api', 'microservices'];
3+
const includedWorkers = process.env.IMMICH_WORKERS_INCLUDE?.replaceAll(/\s/g, '');
4+
const excludedWorkers = process.env.IMMICH_WORKERS_EXCLUDE?.replaceAll(/\s/g, '');
5+
6+
if (includedWorkers) {
7+
workers = includedWorkers.split(',');
8+
}
9+
10+
if (excludedWorkers) {
11+
workers = workers.filter((worker) => !excludedWorkers.split(',').includes(worker));
12+
}
13+
14+
return workers;
15+
};

Diff for: server/src/workers/api.ts

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { NestFactory } from '@nestjs/core';
2+
import { NestExpressApplication } from '@nestjs/platform-express';
3+
import { json } from 'body-parser';
4+
import cookieParser from 'cookie-parser';
5+
import { existsSync } from 'node:fs';
6+
import { isMainThread } from 'node:worker_threads';
7+
import sirv from 'sirv';
8+
import { ApiModule } from 'src/app.module';
9+
import { envName, excludePaths, isDev, serverVersion, WEB_ROOT } from 'src/constants';
10+
import { ILoggerRepository } from 'src/interfaces/logger.interface';
11+
import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
12+
import { ApiService } from 'src/services/api.service';
13+
import { otelSDK } from 'src/utils/instrumentation';
14+
import { useSwagger } from 'src/utils/misc';
15+
16+
const host = process.env.HOST;
17+
18+
async function bootstrap() {
19+
otelSDK.start();
20+
21+
const port = Number(process.env.SERVER_PORT) || 3001;
22+
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
23+
const logger = await app.resolve(ILoggerRepository);
24+
25+
logger.setAppName('ImmichServer');
26+
logger.setContext('ImmichServer');
27+
app.useLogger(logger);
28+
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
29+
app.set('etag', 'strong');
30+
app.use(cookieParser());
31+
app.use(json({ limit: '10mb' }));
32+
if (isDev) {
33+
app.enableCors();
34+
}
35+
app.useWebSocketAdapter(new WebSocketAdapter(app));
36+
useSwagger(app, isDev);
37+
38+
app.setGlobalPrefix('api', { exclude: excludePaths });
39+
if (existsSync(WEB_ROOT)) {
40+
// copied from https://github.com/sveltejs/kit/blob/679b5989fe62e3964b9a73b712d7b41831aa1f07/packages/adapter-node/src/handler.js#L46
41+
// provides serving of precompressed assets and caching of immutable assets
42+
app.use(
43+
sirv(WEB_ROOT, {
44+
etag: true,
45+
gzip: true,
46+
brotli: true,
47+
setHeaders: (res, pathname) => {
48+
if (pathname.startsWith(`/_app/immutable`) && res.statusCode === 200) {
49+
res.setHeader('cache-control', 'public,max-age=31536000,immutable');
50+
}
51+
},
52+
}),
53+
);
54+
}
55+
app.use(app.get(ApiService).ssr(excludePaths));
56+
57+
const server = await (host ? app.listen(port, host) : app.listen(port));
58+
server.requestTimeout = 30 * 60 * 1000;
59+
60+
logger.log(`Immich Server is listening on ${await app.getUrl()} [v${serverVersion}] [${envName}] `);
61+
}
62+
63+
if (!isMainThread) {
64+
bootstrap().catch((error) => {
65+
console.error(error);
66+
process.exit(1);
67+
});
68+
}

Diff for: server/src/workers/microservices.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { otelSDK } from 'src/utils/instrumentation';
88

99
const host = process.env.HOST;
1010

11-
export async function bootstrapMicroservices() {
11+
export async function bootstrap() {
1212
otelSDK.start();
1313

1414
const port = Number(process.env.MICROSERVICES_PORT) || 3002;
@@ -25,7 +25,7 @@ export async function bootstrapMicroservices() {
2525
}
2626

2727
if (!isMainThread) {
28-
bootstrapMicroservices().catch((error) => {
28+
bootstrap().catch((error) => {
2929
console.error(error);
3030
process.exit(1);
3131
});

0 commit comments

Comments
 (0)