nsg / immich-distribution

Experimental Immich distribution inside a snap
MIT License
25 stars 0 forks source link

Bump v1.91.4 #146

Closed github-actions[bot] closed 8 months ago

github-actions[bot] commented 9 months ago

This PR bumps the version from v1.90.2 to v1.91.4. Please review the changes and merge this PR if everything looks good.

Upstream release notes

Monitored upstream files

diff --git a/server/src/infra/migrations/1700713871511-UsePgVectors.ts b/server/src/infra/migrations/1700713871511-UsePgVectors.ts
new file mode 100644
index 00000000..9b13f836
--- /dev/null
+++ b/server/src/infra/migrations/1700713871511-UsePgVectors.ts
@@ -0,0 +1,53 @@
+import { getCLIPModelInfo } from '@app/domain/smart-info/smart-info.constant';
+import { MigrationInterface, QueryRunner } from 'typeorm';
+import { assertVectors } from '../database.config';
+
+export class UsePgVectors1700713871511 implements MigrationInterface {
+  name = 'UsePgVectors1700713871511';
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await assertVectors(queryRunner);
+
+    const faceDimQuery = await queryRunner.query(`
+        SELECT CARDINALITY(embedding::real[]) as dimsize
+        FROM asset_faces
+        LIMIT 1`);
+    const faceDimSize = faceDimQuery?.[0]?.['dimsize'] ?? 512;
+
+    const clipModelNameQuery = await queryRunner.query(`SELECT value FROM system_config WHERE key = 'machineLearning.clip.modelName'`);
+    const clipModelName: string = clipModelNameQuery?.[0]?.['value'] ?? 'ViT-B-32__openai';
+    const clipDimSize = getCLIPModelInfo(clipModelName.replace(/"/g, '')).dimSize;
+
+    await queryRunner.query(`
+        ALTER TABLE asset_faces 
+        ALTER COLUMN embedding SET NOT NULL,
+        ALTER COLUMN embedding TYPE vector(${faceDimSize})`);
+
+    await queryRunner.query(`
+        CREATE TABLE smart_search (
+        "assetId"  uuid PRIMARY KEY NOT NULL REFERENCES assets(id) ON DELETE CASCADE,
+        embedding  vector(${clipDimSize}) NOT NULL )`);
+
+    await queryRunner.query(`
+        INSERT INTO smart_search("assetId", embedding)
+        SELECT si."assetId", si."clipEmbedding"
+        FROM smart_info si
+        WHERE "clipEmbedding" IS NOT NULL
+        AND CARDINALITY("clipEmbedding"::real[]) = ${clipDimSize}
+        AND array_position(si."clipEmbedding", NULL) IS NULL`);
+
+    await queryRunner.query(`ALTER TABLE smart_info DROP COLUMN IF EXISTS "clipEmbedding"`);
+    }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`ALTER TABLE asset_faces ALTER COLUMN embedding TYPE real array`);
+    await queryRunner.query(`ALTER TABLE smart_info ADD COLUMN IF NOT EXISTS "clipEmbedding" TYPE real array`);
+    await queryRunner.query(`
+        INSERT INTO smart_info
+        ("assetId", "clipEmbedding")
+        SELECT s."assetId", s.embedding
+        FROM smart_search s
+        ON CONFLICT (s."assetId") DO UPDATE SET "clipEmbedding" = s.embedding`);
+    await queryRunner.query(`DROP TABLE IF EXISTS smart_search`);
+  }
+}
diff --git a/server/src/infra/migrations/1700713994428-AddCLIPEmbeddingIndex.ts b/server/src/infra/migrations/1700713994428-AddCLIPEmbeddingIndex.ts
new file mode 100644
index 00000000..7a1a1144
--- /dev/null
+++ b/server/src/infra/migrations/1700713994428-AddCLIPEmbeddingIndex.ts
@@ -0,0 +1,19 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddCLIPEmbeddingIndex1700713994428 implements MigrationInterface {
+  name = 'AddCLIPEmbeddingIndex1700713994428';
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`
+      CREATE INDEX IF NOT EXISTS clip_index ON smart_search
+      USING vectors (embedding cosine_ops) WITH (options = $$
+      [indexing.hnsw]
+      m = 16
+      ef_construction = 300
+      $$);`);
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`DROP INDEX IF EXISTS clip_index`);
+  }
+}
diff --git a/server/src/infra/migrations/1700714033632-AddFaceEmbeddingIndex.ts b/server/src/infra/migrations/1700714033632-AddFaceEmbeddingIndex.ts
new file mode 100644
index 00000000..0ac7b0cd
--- /dev/null
+++ b/server/src/infra/migrations/1700714033632-AddFaceEmbeddingIndex.ts
@@ -0,0 +1,19 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddFaceEmbeddingIndex1700714033632 implements MigrationInterface {
+  name = 'AddFaceEmbeddingIndex1700714033632';
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`
+      CREATE INDEX IF NOT EXISTS face_index ON asset_faces
+      USING vectors (embedding cosine_ops) WITH (options = $$
+      [indexing.hnsw]
+      m = 16
+      ef_construction = 300
+      $$);`);
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`DROP INDEX IF EXISTS face_index`);
+  }
+}
diff --git a/server/src/infra/migrations/1700714072055-AddSmartInfoTagsIndex.ts b/server/src/infra/migrations/1700714072055-AddSmartInfoTagsIndex.ts
new file mode 100644
index 00000000..b850d3da
--- /dev/null
+++ b/server/src/infra/migrations/1700714072055-AddSmartInfoTagsIndex.ts
@@ -0,0 +1,13 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddSmartInfoTagsIndex1700714072055 implements MigrationInterface {
+  name = 'AddSmartInfoTagsIndex1700714072055';
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`CREATE INDEX IF NOT EXISTS si_tags ON smart_info USING GIN (tags);`);
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`DROP INDEX IF EXISTS si_tags;`);
+  }
+}
diff --git a/server/src/infra/migrations/1700714140297-CreateSmartInfoTextSearchIndex.ts b/server/src/infra/migrations/1700714140297-CreateSmartInfoTextSearchIndex.ts
new file mode 100644
index 00000000..b42291f6
--- /dev/null
+++ b/server/src/infra/migrations/1700714140297-CreateSmartInfoTextSearchIndex.ts
@@ -0,0 +1,37 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class CreateSmartInfoTextSearchIndex1700714140297 implements MigrationInterface {
+  name = 'CreateSmartInfoTextSearchIndex1700714140297';
+
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    // https://dba.stackexchange.com/a/164081
+    await queryRunner.query(`
+        CREATE OR REPLACE FUNCTION f_concat_ws(text, text[])
+        RETURNS text
+        LANGUAGE sql IMMUTABLE PARALLEL SAFE AS
+        'SELECT array_to_string($2, $1)'`);
+
+    await queryRunner.query(`
+        ALTER TABLE smart_info ADD "smartInfoTextSearchableColumn" tsvector
+        GENERATED ALWAYS AS (
+            TO_TSVECTOR(
+                'english', 
+                f_concat_ws(
+                    ' '::text, 
+                    COALESCE(tags, array[]::text[]) || COALESCE(objects, array[]::text[])
+                )
+            )
+        )
+        STORED NOT NULL`);
+
+    await queryRunner.query(`
+        CREATE INDEX smart_info_text_searchable_idx
+        ON smart_info
+        USING GIN ("smartInfoTextSearchableColumn")`);
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`DROP FUNCTION IF EXISTS immutable_concat_ws`);
+    await queryRunner.query(`ALTER TABLE smart_info DROP IF EXISTS "smartInfoTextSearchableColumn"`);
+  }
+}
diff --git a/server/src/infra/migrations/1701665867595-AddExifCityIndex.ts b/server/src/infra/migrations/1701665867595-AddExifCityIndex.ts
new file mode 100644
index 00000000..9979762d
--- /dev/null
+++ b/server/src/infra/migrations/1701665867595-AddExifCityIndex.ts
@@ -0,0 +1,14 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class AddExifCityIndex1701665867595 implements MigrationInterface {
+    name = 'AddExifCityIndex1701665867595'
+
+    public async up(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`CREATE INDEX "exif_city" ON "exif" ("city") `);
+    }
+
+    public async down(queryRunner: QueryRunner): Promise<void> {
+        await queryRunner.query(`DROP INDEX "public"."exif_city"`);
+    }
+
+}
diff --git a/server/src/infra/migrations/1702084989965-AddWebSocketAttachmentTable.ts b/server/src/infra/migrations/1702084989965-AddWebSocketAttachmentTable.ts
new file mode 100644
index 00000000..c2fc0b22
--- /dev/null
+++ b/server/src/infra/migrations/1702084989965-AddWebSocketAttachmentTable.ts
@@ -0,0 +1,13 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class AddWebSocketAttachmentTable1702084989965 implements MigrationInterface {
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(
+      'CREATE TABLE IF NOT EXISTS "socket_io_attachments" (id bigserial UNIQUE, created_at timestamptz DEFAULT NOW(), payload bytea);',
+    );
+  }
+
+  public async down(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(`DROP TABLE "socket_io_attachments"`);
+  }
+}
diff --git a/server/src/infra/migrations/1702257380990-DropNullIslandLatLong.ts b/server/src/infra/migrations/1702257380990-DropNullIslandLatLong.ts
new file mode 100644
index 00000000..173b2c59
--- /dev/null
+++ b/server/src/infra/migrations/1702257380990-DropNullIslandLatLong.ts
@@ -0,0 +1,14 @@
+import { MigrationInterface, QueryRunner } from 'typeorm';
+
+export class DropNullIslandLatLong1702257380990 implements MigrationInterface {
+  public async up(queryRunner: QueryRunner): Promise<void> {
+    await queryRunner.query(
+      'UPDATE "exif" SET latitude = NULL, longitude = NULL WHERE latitude = 0 AND longitude = 0;',
+    );
+  }
+
+  public async down(): Promise<void> {
+    // There's no way to know which assets used to have 0/0 lat-long if we've
+    // already run this migration.
+  }
+}
diff --git a/server/Dockerfile b/server/Dockerfile
index b43058d5..3c2a0522 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -1,29 +1,37 @@
 # dev build
-FROM ghcr.io/immich-app/base-server-dev:20231201@sha256:4701c0c5920c78e73040dd2b74d22042ffce393f1a9d3453d90a0ecf81ff8649 as dev
+FROM ghcr.io/immich-app/base-server-dev:20231214@sha256:cd5be516b27c0c402bee3a6a93d8c83dfd5a827c18a2343cb97b55f3be98151b as dev

+RUN apt-get install --no-install-recommends -yqq tini
 WORKDIR /usr/src/app
 COPY server/package.json server/package-lock.json ./
-RUN npm ci
+RUN npm ci && \
+    # sharp-linux-x64 and sharp-linux-arm64 are the only ones we need
+    # they're marked as optional dependencies, so we need to copy them manually after pruning
+    rm -rf node_modules/@img/sharp-libvips* && \
+    rm -rf node_modules/@img/sharp-linuxmusl-x64
 COPY server .
+ENV PATH="${PATH}:/usr/src/app/bin"
+ENTRYPOINT ["tini", "--", "/bin/sh"]

 FROM dev AS prod

 RUN npm run build
 RUN npm prune --omit=dev --omit=optional
+COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img

 # web build
 FROM node:iron-alpine3.18 as web

 WORKDIR /usr/src/app
-COPY web/package.json web/package-lock.json ./
+COPY web/package*.json web/svelte.config.js .
 RUN npm ci
 COPY web .
 RUN npm run build

 # prod build
-FROM ghcr.io/immich-app/base-server-prod:20231201@sha256:b8e86cf4c3cad872f54bab25a83f7503480049eea5c0ae36a8b8460b13cad3b5
+FROM ghcr.io/immich-app/base-server-prod:20231214@sha256:b214f86683fde081b09beed2d7bfc28bec55c829751ccf2e02ad7dd18293f5e0

 WORKDIR /usr/src/app
 ENV NODE_ENV=production
diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile
index 01b08381..81a7be58 100644
--- a/machine-learning/Dockerfile
+++ b/machine-learning/Dockerfile
@@ -13,7 +13,7 @@ ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
 COPY poetry.lock pyproject.toml ./
 RUN poetry install --sync --no-interaction --no-ansi --no-root --only main

-FROM python:3.11-slim-bookworm@sha256:cc758519481092eb5a4a5ab0c1b303e288880d59afc601958d19e95b300bc86b
+FROM python:3.11-slim-bookworm@sha256:cfd7ed5c11a88ce533d69a1da2fd932d647f9eb6791c5b4ddce081aedf7f7876

 RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*

@@ -25,6 +25,11 @@ ENV NODE_ENV=production \
   PATH="/opt/venv/bin:$PATH" \
   PYTHONPATH=/usr/src

+# prevent core dumps
+RUN echo "hard core 0" >> /etc/security/limits.conf && \
+    echo "fs.suid_dumpable 0" >> /etc/sysctl.conf && \
+    echo 'ulimit -S -c 0 > /dev/null 2>&1' >> /etc/profile
+
 COPY --from=builder /opt/venv /opt/venv
 COPY start.sh log_conf.json ./
 COPY app .
diff --git a/machine-learning/start.sh b/machine-learning/start.sh
index 0836213e..d522f114 100755
--- a/machine-learning/start.sh
+++ b/machine-learning/start.sh
@@ -1,6 +1,7 @@
 #!/usr/bin/env sh

 export LD_PRELOAD="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
+export LD_BIND_NOW=1

 : "${MACHINE_LEARNING_HOST:=0.0.0.0}"
 : "${MACHINE_LEARNING_PORT:=3003}"
@@ -8,8 +9,9 @@ export LD_PRELOAD="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
 : "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}"

 gunicorn app.main:app \
-   -k uvicorn.workers.UvicornWorker \
+   -k app.config.CustomUvicornWorker \
    -w $MACHINE_LEARNING_WORKERS \
    -b $MACHINE_LEARNING_HOST:$MACHINE_LEARNING_PORT \
    -t $MACHINE_LEARNING_WORKER_TIMEOUT \
-   --log-config-json log_conf.json
+   --log-config-json log_conf.json \
+   --graceful-timeout 0
diff --git a/docker/example.env b/docker/example.env
index 319bb39c..40d379f4 100644
--- a/docker/example.env
+++ b/docker/example.env
@@ -6,8 +6,7 @@ UPLOAD_LOCATION=./library
 # The Immich version to use. You can pin this to a specific version like "v1.71.0"
 IMMICH_VERSION=release

-# Connection secrets for postgres and typesense. You should change these to random passwords
-TYPESENSE_API_KEY=some-random-text
+# Connection secret for postgres. You should change it to a random password
 DB_PASSWORD=postgres

 # The values below this line do not need to be changed
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 9452975f..909c0238 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -25,7 +25,6 @@ services:
     depends_on:
       - redis
       - database
-      - typesense
     restart: always

   immich-microservices:
@@ -43,7 +42,6 @@ services:
     depends_on:
       - redis
       - database
-      - typesense
     restart: always

   immich-machine-learning:
@@ -55,26 +53,14 @@ services:
       - .env
     restart: always

-  typesense:
-    container_name: immich_typesense
-    image: typesense/typesense:0.24.1@sha256:9bcff2b829f12074426ca044b56160ca9d777a0c488303469143dd9f8259d4dd
-    environment:
-      - TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
-      - TYPESENSE_DATA_DIR=/data
-      # remove this to get debug messages
-      - GLOG_minloglevel=1
-    volumes:
-      - tsdata:/data
-    restart: always
-
   redis:
     container_name: immich_redis
-    image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
+    image: redis:6.2-alpine@sha256:b6124ab2e45cc332e16398022a411d7e37181f21ff7874835e0180f56a09e82a
     restart: always

   database:
     container_name: immich_postgres
-    image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
+    image: tensorchord/pgvecto-rs:pg14-v0.1.11@sha256:0335a1a22f8c5dd1b697f14f079934f5152eaaa216c09b61e293be285491f8ee
     env_file:
       - .env
     environment:
@@ -88,4 +74,3 @@ services:
 volumes:
   pgdata:
   model-cache:
-  tsdata:
diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md
index 025568a6..20966cfd 100644
--- a/docs/docs/install/environment-variables.md
+++ b/docs/docs/install/environment-variables.md
@@ -18,8 +18,8 @@ If this should not work, try running `docker compose up -d --force-recreate`.
 ## Docker Compose

 | Variable          | Description           |  Default  | Services                                            |
-| :---------------- | :-------------------- | :-------: | :------------------------------------------------------------- |
-| `IMMICH_VERSION`  | Image tags            | `release` | server, microservices, machine learning, web, proxy, typesense |
+| :---------------- | :-------------------- | :-------: | :-------------------------------------------------- |
+| `IMMICH_VERSION`  | Image tags            | `release` | server, microservices, machine learning, web, proxy |
 | `UPLOAD_LOCATION` | Host Path for uploads |           | server, microservices                               |

 :::tip
@@ -31,13 +31,14 @@ These environment variables are used by the `docker-compose.yml` file and do **N
 ## General

 | Variable                    | Description                                  |       Default       | Services                                     |
-| :-------------------------- | :------------------------------------------- | :----------: | :------------------------------------------- |
+| :-------------------------- | :------------------------------------------- | :-----------------: | :------------------------------------------- |
 | `TZ`                        | Timezone                                     |                     | microservices                                |
 | `NODE_ENV`                  | Environment (production, development)        |    `production`     | server, microservices, machine learning, web |
 | `LOG_LEVEL`                 | Log Level (verbose, debug, log, warn, error) |        `log`        | server, microservices                        |
 | `IMMICH_MEDIA_LOCATION`     | Media Location                               |     `./upload`      | server, microservices                        |
 | `PUBLIC_LOGIN_PAGE_MESSAGE` | Public Login Page Message                    |                     | web                                          |
 | `IMMICH_CONFIG_FILE`        | Path to config file                          |                     | server                                       |
+| `IMMICH_WEB_ROOT`           | Path of root index.html                      | `/usr/src/app/www'` | server                                       |

 :::tip

@@ -124,51 +125,6 @@ Redis (Sentinel) URL example JSON before encoding:
 }

-## Typesense

- Variable Description Default Services
- TYPESENSE_ENABLED Enable Typesense server, microservices
- TYPESENSE_URL Typesense URL server, microservices
- TYPESENSE_HOST Typesense Host typesense server, microservices
- TYPESENSE_PORT Typesense Port 8108 server, microservices
- TYPESENSE_PROTOCOL Typesense Protocol http server, microservices
- TYPESENSE_API_KEY Typesense API Key server, microservices, typesense
- TYPESENSE_DATA_DIR Typesense Data Directory /data typesense

- -:::info

-TYPESENSE_URL must start with ha:// and then include a base64 encoded JSON string for the configuration.

-TYPESENSE_ENABLED: Anything other than false, behaves as true. -Even undefined is treated as true.

-- When TYPESENSE_URL is defined, the other typesense (TYPESENSE_*) variables are ignored.

-:::

-Typesense URL example JSON before encoding:

-```json -[

Checklist

ref #142