From 1a3d05ffc31761ef594e6e31815987de605740ff Mon Sep 17 00:00:00 2001
From: Zack Pollard <zackpollard@ymail.com>
Date: Fri, 24 Jun 2022 04:18:50 +0100
Subject: [PATCH] chore: improve default setup (#234)

* chore: remove UPLOAD_LOCATION as it isn't used in the server

* docker: remove network in docker compose as docker creates one by default

* nginx: update reverse proxy to put web at root and api at /api

* docker: remove unneeded exposed ports and docker network

Align dev setup with prod, but with ports exposed for direct connection
Most communication between services happens on the internal network, so we don't need to expose all these services.
With the nginx changes, the api and web panel are both server through the reverse proxy on / for web and /api for the API.
The only service that should expose ports is nginx as that is the entrypoint to the application.

* chore: remove CORS now we serve the api on /api in the default setup

* docs: update README.md to include /api

* Fixed docket-compose file for dev environment and websocket on web and mobile

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
---
 README.md                                     |  8 +-
 docker/.env.example                           |  2 +-
 docker/.env.test                              |  2 +-
 docker/docker-compose.dev.yml                 | 24 +----
 docker/docker-compose.gpu.yml                 | 92 -------------------
 docker/docker-compose.staging.yml             | 24 -----
 docker/docker-compose.test.yml                | 12 +--
 docker/docker-compose.yml                     | 22 -----
 docker/settings/nginx-conf/nginx.conf         | 29 +++++-
 .../shared/providers/websocket.provider.dart  | 17 ++--
 server/apps/immich/src/config/app.config.ts   |  1 -
 server/apps/immich/src/main.ts                |  6 +-
 web/package.json                              |  2 +-
 web/src/lib/constants.ts                      |  2 +-
 web/src/lib/stores/websocket.ts               | 26 ++++--
 15 files changed, 67 insertions(+), 202 deletions(-)
 delete mode 100644 docker/docker-compose.gpu.yml

diff --git a/README.md b/README.md
index 08485affca..a1261ccba4 100644
--- a/README.md
+++ b/README.md
@@ -143,8 +143,8 @@ MAPBOX_KEY=
 # This is the URL of your vm/server where you host Immich, so that the web frontend
 # know where can it make the request to.
 # For example: If your server IP address is 10.1.11.50, the environment variable will
-# be VITE_SERVER_ENDPOINT=http://10.1.11.50:2283
-VITE_SERVER_ENDPOINT=http://192.168.1.216:2283
+# be VITE_SERVER_ENDPOINT=http://10.1.11.50:2283/api
+VITE_SERVER_ENDPOINT=http://192.168.1.216:2283/api
 ```
 
 ## Step 2: Start the server
@@ -167,11 +167,11 @@ To *update* docker-compose with newest image (if you have started the docker-com
 docker-compose -f ./docker/docker-compose.yml pull && docker-compose -f ./docker/docker-compose.yml up
 ```
 
-The server will be running at `http://your-ip:2283` through `Nginx`
+The server will be running at `http://your-ip:2283/api` through `Nginx`
 
 ## Step 3: Register User
 
-Access the web interface at `http://your-ip:2285` to register an admin account.
+Access the web interface at `http://your-ip:2283` to register an admin account.
 
 <p align="left">
   <img src="design/admin-registration-form.png" width="300" title="Admin Registration">
diff --git a/docker/.env.example b/docker/.env.example
index e9f12364ab..35cc56f104 100644
--- a/docker/.env.example
+++ b/docker/.env.example
@@ -57,7 +57,7 @@ MAPBOX_KEY=
 # This is the URL of your vm/server where you host Immich, so that the web frontend
 # know where can it make the request to.
 # For example: If your server IP address is 10.1.11.50, the environment variable will
-# be VITE_SERVER_ENDPOINT=http://10.1.11.50:2283
+# be VITE_SERVER_ENDPOINT=http://10.1.11.50:2283/api
 # !CAUTION! THERE IS NO FORWARD SLASH AT THE END
 
 VITE_SERVER_ENDPOINT=
diff --git a/docker/.env.test b/docker/.env.test
index 7171aa3822..95b4f25a53 100644
--- a/docker/.env.test
+++ b/docker/.env.test
@@ -19,4 +19,4 @@ ENABLE_MAPBOX=false
 
 # WEB
 MAPBOX_KEY=
-VITE_SERVER_ENDPOINT=http://localhost:2283
\ No newline at end of file
+VITE_SERVER_ENDPOINT=http://localhost:2283/api
\ No newline at end of file
diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml
index 34635f6971..bb095cd0fc 100644
--- a/docker/docker-compose.dev.yml
+++ b/docker/docker-compose.dev.yml
@@ -2,7 +2,7 @@ version: "3.8"
 
 services:
   immich-server:
-    image: immich-server-dev:1.9.0
+    image: immich-server-dev:latest
     build:
       context: ../server
       dockerfile: Dockerfile
@@ -20,11 +20,9 @@ services:
     depends_on:
       - redis
       - database
-    networks:
-      - immich-network
 
   immich-machine-learning:
-    image: immich-machine-learning-dev:1.9.0
+    image: immich-machine-learning-dev:latest
     build:
       context: ../machine-learning
       dockerfile: Dockerfile
@@ -41,11 +39,9 @@ services:
       - NODE_ENV=development
     depends_on:
       - database
-    networks:
-      - immich-network
 
   immich-microservices:
-    image: immich-microservices:1.9.0
+    image: immich-microservices:latest
     build:
       context: ../server
       dockerfile: Dockerfile
@@ -61,8 +57,6 @@ services:
     depends_on:
       - database
       - immich-server
-    networks:
-      - immich-network
 
   immich-web:
     image: immich-web-dev:1.9.0
@@ -74,20 +68,16 @@ services:
     env_file:
       - .env
     ports:
-      - 3002:3002
+      - 3002:3000
       - 24678:24678
     volumes:
       - ../web:/usr/src/app
       - /usr/src/app/node_modules
-    networks:
-      - immich-network
     restart: always
 
   redis:
     container_name: immich_redis
     image: redis:6.2
-    networks:
-      - immich-network
 
   database:
     container_name: immich_postgres
@@ -103,8 +93,6 @@ services:
       - pgdata:/var/lib/postgresql/data
     ports:
       - 5432:5432
-    networks:
-      - immich-network
 
   nginx:
     container_name: proxy_nginx
@@ -116,12 +104,8 @@ services:
       - 2284:443
     logging:
       driver: none
-    networks:
-      - immich-network
     depends_on:
       - immich-server
 
-networks:
-  immich-network:
 volumes:
   pgdata:
diff --git a/docker/docker-compose.gpu.yml b/docker/docker-compose.gpu.yml
deleted file mode 100644
index e085c4d48f..0000000000
--- a/docker/docker-compose.gpu.yml
+++ /dev/null
@@ -1,92 +0,0 @@
-version: "3.8"
-
-services:
-  immich-server:
-    image: immich-server-dev:1.9.0
-    build:
-      context: ../server
-      dockerfile: Dockerfile
-    command: npm run start:dev
-    expose:
-      - "3000"
-    volumes:
-      - ../server:/usr/src/app
-      - ${UPLOAD_LOCATION}:/usr/src/app/upload
-      - /usr/src/app/node_modules
-    env_file:
-      - .env
-    depends_on:
-      - redis
-      - database
-    networks:
-      - immich-network
-
-  immich-microservices:
-    image: immich-microservices-dev:1.9.0
-    build:
-      context: ../microservices
-      dockerfile: Dockerfile
-    command: npm run start:dev
-    deploy:
-      resources:
-        reservations:
-          devices:
-            - driver: nvidia
-              count: 1
-              capabilities: [ gpu ]
-    expose:
-      - "3001"
-    volumes:
-      - ../microservices:/usr/src/app
-      - ${UPLOAD_LOCATION}:/usr/src/app/upload
-      - /usr/src/app/node_modules
-    env_file:
-      - .env
-    depends_on:
-      - database
-      - immich_server
-    networks:
-      - immich-network
-
-  redis:
-    container_name: immich_redis
-    image: redis:6.2
-    networks:
-      - immich-network
-
-  database:
-    container_name: immich_postgres
-    image: postgres:14
-    env_file:
-      - .env
-    environment:
-      POSTGRES_PASSWORD: ${DB_PASSWORD}
-      POSTGRES_USER: ${DB_USERNAME}
-      POSTGRES_DB: ${DB_DATABASE_NAME}
-      PG_DATA: /var/lib/postgresql/data
-    volumes:
-      - pgdata:/var/lib/postgresql/data
-    ports:
-      - 5432:5432
-    networks:
-      - immich-network
-
-  nginx:
-    container_name: proxy_nginx
-    image: nginx:latest
-    volumes:
-      - ./settings/nginx-conf:/etc/nginx/conf.d
-    ports:
-      - 2283:80
-      - 2284:443
-    logging:
-      driver: none
-    networks:
-      - immich-network
-    depends_on:
-      - immich-server
-
-networks:
-  immich-network:
-volumes:
-  pgdata:
diff --git a/docker/docker-compose.staging.yml b/docker/docker-compose.staging.yml
index 55b442d832..57dab629c8 100644
--- a/docker/docker-compose.staging.yml
+++ b/docker/docker-compose.staging.yml
@@ -4,8 +4,6 @@ services:
   immich-server:
     image: altran1502/immich-server:staging
     entrypoint: ["/bin/sh", "./start-server.sh"]
-    expose:
-      - "3000"
     volumes:
       - ${UPLOAD_LOCATION}:/usr/src/app/upload
     env_file:
@@ -15,8 +13,6 @@ services:
     depends_on:
       - redis
       - database
-    networks:
-      - immich-network
     restart: always
 
   immich-microservices:
@@ -31,15 +27,11 @@ services:
     depends_on:
       - redis
       - database
-    networks:
-      - immich-network
     restart: always
 
   immich-machine-learning:
     image: altran1502/immich-machine-learning:staging
     entrypoint: ["/bin/sh", "./entrypoint.sh"]
-    expose:
-      - "3001"
     volumes:
       - ${UPLOAD_LOCATION}:/usr/src/app/upload
     env_file:
@@ -48,8 +40,6 @@ services:
       - NODE_ENV=production
     depends_on:
       - database
-    networks:
-      - immich-network
     restart: always
 
   immich-web:
@@ -57,17 +47,11 @@ services:
     entrypoint: ["/bin/sh", "./entrypoint.sh"]
     env_file:
       - .env
-    ports:
-      - 2285:3000
-    networks:
-      - immich-network
     restart: always
 
   redis:
     container_name: immich_redis
     image: redis:6.2
-    networks:
-      - immich-network
     restart: always
 
   database:
@@ -82,10 +66,6 @@ services:
       PG_DATA: /var/lib/postgresql/data
     volumes:
       - pgdata:/var/lib/postgresql/data
-    ports:
-      - 5432:5432
-    networks:
-      - immich-network
     restart: always
 
   nginx:
@@ -98,13 +78,9 @@ services:
       - 2284:443
     logging:
       driver: none
-    networks:
-      - immich-network
     depends_on:
       - immich-server
     restart: always
 
-networks:
-  immich-network:
 volumes:
   pgdata:
diff --git a/docker/docker-compose.test.yml b/docker/docker-compose.test.yml
index 603e05d601..fd28ec33b6 100644
--- a/docker/docker-compose.test.yml
+++ b/docker/docker-compose.test.yml
@@ -2,7 +2,7 @@ version: "3.8"
 
 services:
   immich_server_test:
-    image: immich-server-dev:1.9.0
+    image: immich-server-dev:latest
     build:
       context: ../server
       dockerfile: Dockerfile
@@ -19,15 +19,10 @@ services:
     depends_on:
       - redis
       - database
-    networks:
-      - immich_network_test
-
 
   redis:
     container_name: immich_redis_test
     image: redis:6.2
-    networks:
-      - immich_network_test
 
   database:
     container_name: immich_postgres_test
@@ -43,8 +38,3 @@ services:
       - /var/lib/postgresql/data
     ports:
       - 5432:5432
-    networks:
-      - immich_network_test
-
-networks:
-  immich_network_test:
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 8792aae007..38a98a41a3 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -4,8 +4,6 @@ services:
   immich-server:
     image: altran1502/immich-server:latest
     entrypoint: ["/bin/sh", "./start-server.sh"]
-    expose:
-      - "3000"
     volumes:
       - ${UPLOAD_LOCATION}:/usr/src/app/upload
     env_file:
@@ -15,8 +13,6 @@ services:
     depends_on:
       - redis
       - database
-    networks:
-      - immich-network
     restart: always
 
   immich-microservices:
@@ -31,15 +27,11 @@ services:
     depends_on:
       - redis
       - database
-    networks:
-      - immich-network
     restart: always
 
   immich-machine-learning:
     image: altran1502/immich-machine-learning:latest
     entrypoint: ["/bin/sh", "./entrypoint.sh"]
-    expose:
-      - "3001"
     volumes:
       - ${UPLOAD_LOCATION}:/usr/src/app/upload
     env_file:
@@ -48,8 +40,6 @@ services:
       - NODE_ENV=production
     depends_on:
       - database
-    networks:
-      - immich-network
     restart: always
 
   immich-web:
@@ -57,17 +47,11 @@ services:
     entrypoint: ["/bin/sh", "./entrypoint.sh"]
     env_file:
       - .env
-    ports:
-      - 2285:3000
-    networks:
-      - immich-network
     restart: always
 
   redis:
     container_name: immich_redis
     image: redis:6.2
-    networks:
-      - immich-network
     restart: always
 
   database:
@@ -82,8 +66,6 @@ services:
       PG_DATA: /var/lib/postgresql/data
     volumes:
       - pgdata:/var/lib/postgresql/data
-    networks:
-      - immich-network
     restart: always
 
   nginx:
@@ -96,13 +78,9 @@ services:
       - 2284:443
     logging:
       driver: none
-    networks:
-      - immich-network
     depends_on:
       - immich-server
     restart: always
 
-networks:
-  immich-network:
 volumes:
   pgdata:
diff --git a/docker/settings/nginx-conf/nginx.conf b/docker/settings/nginx-conf/nginx.conf
index 92bca82162..722cd5103f 100644
--- a/docker/settings/nginx-conf/nginx.conf
+++ b/docker/settings/nginx-conf/nginx.conf
@@ -19,6 +19,33 @@ server {
   listen 80;
   access_log off;
 
+  location /api {
+
+    # Compression
+    gzip_static         on;
+    gzip_min_length     1000;
+    gzip_comp_level     2;
+
+    proxy_buffering off;
+    proxy_buffer_size 16k;
+    proxy_busy_buffers_size 24k;
+    proxy_buffers 64 4k;
+    proxy_force_ranges on;
+
+    proxy_http_version 1.1;
+    proxy_set_header Host $host;
+    proxy_set_header X-Real-IP $remote_addr;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header X-Forwarded-Proto $scheme;
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection "upgrade";
+    proxy_set_header Host $host;
+
+    rewrite /api/(.*) /$1  break;
+
+    proxy_pass http://immich-server:3000;
+  }
+
   location / {
 
     # Compression
@@ -41,6 +68,6 @@ server {
     proxy_set_header Connection "upgrade";
     proxy_set_header Host $host;
 
-    proxy_pass http://immich-server:3000;
+    proxy_pass http://immich-web:3000;
   }
 }
diff --git a/mobile/lib/shared/providers/websocket.provider.dart b/mobile/lib/shared/providers/websocket.provider.dart
index e514a8286e..e837eb983e 100644
--- a/mobile/lib/shared/providers/websocket.provider.dart
+++ b/mobile/lib/shared/providers/websocket.provider.dart
@@ -29,16 +29,13 @@ class WebscoketState {
   }
 
   @override
-  String toString() =>
-      'WebscoketState(socket: $socket, isConnected: $isConnected)';
+  String toString() => 'WebscoketState(socket: $socket, isConnected: $isConnected)';
 
   @override
   bool operator ==(Object other) {
     if (identical(this, other)) return true;
 
-    return other is WebscoketState &&
-        other.socket == socket &&
-        other.isConnected == isConnected;
+    return other is WebscoketState && other.socket == socket && other.isConnected == isConnected;
   }
 
   @override
@@ -46,8 +43,7 @@ class WebscoketState {
 }
 
 class WebsocketNotifier extends StateNotifier<WebscoketState> {
-  WebsocketNotifier(this.ref)
-      : super(WebscoketState(socket: null, isConnected: false)) {
+  WebsocketNotifier(this.ref) : super(WebscoketState(socket: null, isConnected: false)) {
     debugPrint("Init websocket instance");
   }
 
@@ -62,10 +58,10 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
       try {
         debugPrint("[WEBSOCKET] Attempting to connect to ws");
         // Configure socket transports must be sepecified
-
         Socket socket = io(
-          endpoint,
+          endpoint.toString().replaceAll('/api', ''),
           OptionBuilder()
+              .setPath('/api/socket.io')
               .setTransports(['websocket'])
               .enableReconnection()
               .enableForceNew()
@@ -126,7 +122,6 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
   }
 }
 
-final websocketProvider =
-    StateNotifierProvider<WebsocketNotifier, WebscoketState>((ref) {
+final websocketProvider = StateNotifierProvider<WebsocketNotifier, WebscoketState>((ref) {
   return WebsocketNotifier(ref);
 });
diff --git a/server/apps/immich/src/config/app.config.ts b/server/apps/immich/src/config/app.config.ts
index b792245f3b..b2509db908 100644
--- a/server/apps/immich/src/config/app.config.ts
+++ b/server/apps/immich/src/config/app.config.ts
@@ -9,7 +9,6 @@ export const immichAppConfig: ConfigModuleOptions = {
     DB_USERNAME: Joi.string().required(),
     DB_PASSWORD: Joi.string().required(),
     DB_DATABASE_NAME: Joi.string().required(),
-    UPLOAD_LOCATION: Joi.string().required(),
     JWT_SECRET: Joi.string().required(),
     ENABLE_MAPBOX: Joi.boolean().required().valid(true, false),
     MAPBOX_KEY: Joi.any().when('ENABLE_MAPBOX', {
diff --git a/server/apps/immich/src/main.ts b/server/apps/immich/src/main.ts
index a61c78caf5..4d62c5576b 100644
--- a/server/apps/immich/src/main.ts
+++ b/server/apps/immich/src/main.ts
@@ -7,10 +7,12 @@ import { RedisIoAdapter } from './middlewares/redis-io.adapter.middleware';
 async function bootstrap() {
   const app = await NestFactory.create<NestExpressApplication>(AppModule);
 
-  app.enableCors();
-
   app.set('trust proxy');
 
+  if (process.env.NODE_ENV === 'development') {
+    app.enableCors();
+  }
+
   app.useWebSocketAdapter(new RedisIoAdapter(app));
 
   await app.listen(3000, () => {
diff --git a/web/package.json b/web/package.json
index 4a4ea786a0..c494c69118 100644
--- a/web/package.json
+++ b/web/package.json
@@ -2,7 +2,7 @@
 	"name": "web",
 	"version": "0.0.1",
 	"scripts": {
-		"dev": "svelte-kit dev --host 0.0.0.0 --port 3002",
+		"dev": "svelte-kit dev --host 0.0.0.0",
 		"build": "svelte-kit build",
 		"package": "svelte-kit package",
 		"preview": "svelte-kit preview",
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts
index c9e952536f..2a403bfb31 100644
--- a/web/src/lib/constants.ts
+++ b/web/src/lib/constants.ts
@@ -1 +1 @@
-export const serverEndpoint = import.meta.env.VITE_SERVER_ENDPOINT
\ No newline at end of file
+export const serverEndpoint: string = import.meta.env.VITE_SERVER_ENDPOINT;
diff --git a/web/src/lib/stores/websocket.ts b/web/src/lib/stores/websocket.ts
index d37f220922..ae1def81a2 100644
--- a/web/src/lib/stores/websocket.ts
+++ b/web/src/lib/stores/websocket.ts
@@ -4,17 +4,23 @@ import type { ImmichAsset } from '../models/immich-asset';
 import { assets } from './assets';
 
 export const openWebsocketConnection = (accessToken: string) => {
-	const websocket = io(serverEndpoint, {
-		transports: ['polling'],
-		reconnection: true,
-		forceNew: true,
-		autoConnect: true,
-		extraHeaders: {
-			Authorization: 'Bearer ' + accessToken,
-		},
-	});
+	const websocketEndpoint = serverEndpoint.replace('/api', '');
+	try {
+		const websocket = io(websocketEndpoint, {
+			path: '/api/socket.io',
+			transports: ['polling'],
+			reconnection: true,
+			forceNew: true,
+			autoConnect: true,
+			extraHeaders: {
+				Authorization: 'Bearer ' + accessToken,
+			},
+		});
 
-	listenToEvent(websocket);
+		listenToEvent(websocket);
+	} catch (e) {
+		console.log('Cannot connect to websocket ', e);
+	}
 };
 
 const listenToEvent = (socket: Socket) => {