[BUG] Changing external library "import path" does not update the path of the pre-existing content of the library #1765

Closed
opened 2026-02-05 03:39:22 +03:00 by OVERLORD · 6 comments
Owner

Originally created by @maltokyo on GitHub (Dec 11, 2023).

Originally assigned to: @etnoy on GitHub.

The bug

Changing external library "import path" does not update the path of the pre-existing content of the library.

Lets say for the Library MyPhotos the import path until yesterday was:

/mnt/SmallHarddrive01/MyPhotos

and today you upgraded storage on the machine and moved the entire directory of photos to another path, eg they are now here:

/mnt/BigHarddrive02/MyPhotos

Then rerun all of the jobs to rescan the library etc, nothing gets updated for photos that were already in the Library in the old path, even though they are all now in a new path.

The OS that Immich Server is running on

Debian 12 latest updates applied

Version of Immich Server

v1.90.2

Version of Immich Mobile App

N/A

Platform with the issue

  • Server
  • Web
  • Mobile

Your docker-compose.yml content

version: "3.8"

services:
  immich-server:
    container_name: immich_server
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    command: [ "start.sh", "immich" ]
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro

    env_file:
      - .env
    ports:
      - 2283:3001
    depends_on:
      - redis
      - database
      - typesense
    restart: always

  immich-microservices:
    container_name: immich_microservices
    image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
    extends:
      file: hwaccel.yml
      service: hwaccel
    command: [ "start.sh", "microservices" ]
    volumes:
      - ${UPLOAD_LOCATION}:/usr/src/app/upload
      - /etc/localtime:/etc/localtime:ro

    env_file:
      - .env
    depends_on:
      - redis
      - database
      - typesense
    restart: always

  immich-machine-learning:
    container_name: immich_machine_learning
    image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
    volumes:
      - model-cache:/cache
    env_file:
      - .env
    restart: always

#   immich-web:
#     container_name: immich_web
#     image: ghcr.io/immich-app/immich-web:${IMMICH_VERSION:-release}
#     env_file:
#       - .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
    restart: always

  database:
    container_name: immich_postgres
    image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
    env_file:
      - .env
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_USER: ${DB_USERNAME}
      POSTGRES_DB: ${DB_DATABASE_NAME}
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: always


volumes:
  pgdata:
  model-cache:
  tsdata:

Your .env content

# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables

# The location where your uploaded files are stored
UPLOAD_LOCATION=/mnt/sWes1000/z_immich_msi_metadata

# 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=REDACTED
DB_PASSWORD=REDACTED

# The values below this line do not need to be changed
###################################################################################
DB_HOSTNAME=immich_postgres
DB_USERNAME=postgres

DB_DATABASE_NAME=immich

REDIS_HOSTNAME=immich_redis


###################################################################################
# Log message level - [simple|verbose]
###################################################################################

LOG_LEVEL=verbose

Reproduction steps

1. Run immich normally and create a library with import path: `/mnt/SmallHarddrive01/MyPhotos` and where external path for user to `/mnt/SmallHarddrive01`
2. Change external path for user to `/mnt/BigHarddrive02`
3. Change the library import path to: `/mnt/BigHarddrive02/MyPhotos` (including in the docker-compose mounts (redacted above) 
4. Run `rescan all library files` in library section of the user and run all background jobs for "missing" info.

Even after doing all of this, the path for the files in that library remains as `/mnt/SmallHarddrive01/MyPhotos` in the database, and as a result, all files in the library get marked as "offline" and cannot be downloaded.

Additional information

No response

Originally created by @maltokyo on GitHub (Dec 11, 2023). Originally assigned to: @etnoy on GitHub. ### The bug Changing external library "import path" does not update the path of the pre-existing content of the library. Lets say for the Library `MyPhotos` the import path until yesterday was: `/mnt/SmallHarddrive01/MyPhotos` and today you upgraded storage on the machine and moved the entire directory of photos to another path, eg they are now here: `/mnt/BigHarddrive02/MyPhotos` Then rerun all of the jobs to rescan the library etc, nothing gets updated for photos that were already in the Library in the old path, even though they are all now in a new path. ### The OS that Immich Server is running on Debian 12 latest updates applied ### Version of Immich Server v1.90.2 ### Version of Immich Mobile App N/A ### Platform with the issue - [X] Server - [ ] Web - [ ] Mobile ### Your docker-compose.yml content ```YAML version: "3.8" services: immich-server: container_name: immich_server image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} command: [ "start.sh", "immich" ] volumes: - ${UPLOAD_LOCATION}:/usr/src/app/upload - /etc/localtime:/etc/localtime:ro env_file: - .env ports: - 2283:3001 depends_on: - redis - database - typesense restart: always immich-microservices: container_name: immich_microservices image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release} extends: file: hwaccel.yml service: hwaccel command: [ "start.sh", "microservices" ] volumes: - ${UPLOAD_LOCATION}:/usr/src/app/upload - /etc/localtime:/etc/localtime:ro env_file: - .env depends_on: - redis - database - typesense restart: always immich-machine-learning: container_name: immich_machine_learning image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release} volumes: - model-cache:/cache env_file: - .env restart: always # immich-web: # container_name: immich_web # image: ghcr.io/immich-app/immich-web:${IMMICH_VERSION:-release} # env_file: # - .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 restart: always database: container_name: immich_postgres image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07 env_file: - .env environment: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_USER: ${DB_USERNAME} POSTGRES_DB: ${DB_DATABASE_NAME} volumes: - pgdata:/var/lib/postgresql/data restart: always volumes: pgdata: model-cache: tsdata: ``` ### Your .env content ```Shell # You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables # The location where your uploaded files are stored UPLOAD_LOCATION=/mnt/sWes1000/z_immich_msi_metadata # 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=REDACTED DB_PASSWORD=REDACTED # The values below this line do not need to be changed ################################################################################### DB_HOSTNAME=immich_postgres DB_USERNAME=postgres DB_DATABASE_NAME=immich REDIS_HOSTNAME=immich_redis ################################################################################### # Log message level - [simple|verbose] ################################################################################### LOG_LEVEL=verbose ``` ### Reproduction steps ```bash 1. Run immich normally and create a library with import path: `/mnt/SmallHarddrive01/MyPhotos` and where external path for user to `/mnt/SmallHarddrive01` 2. Change external path for user to `/mnt/BigHarddrive02` 3. Change the library import path to: `/mnt/BigHarddrive02/MyPhotos` (including in the docker-compose mounts (redacted above) 4. Run `rescan all library files` in library section of the user and run all background jobs for "missing" info. Even after doing all of this, the path for the files in that library remains as `/mnt/SmallHarddrive01/MyPhotos` in the database, and as a result, all files in the library get marked as "offline" and cannot be downloaded. ``` ### Additional information _No response_
Author
Owner

@maltokyo commented on GitHub (Jan 18, 2024):

I still have this issue, could anyone help please

@maltokyo commented on GitHub (Jan 18, 2024): I still have this issue, could anyone help please
Author
Owner

@opl- commented on GitHub (Jun 10, 2024):

Ran into the same problem today on immich v1.105.1.

Fix: I wrote some pgSQL which updates the file path of all the original assets (the ones which existed prior to the external library path being changed) to the latest known file path for a given filename within that library. I'm referring to photos and videos as "assets", as that's what they're known as in the code.

Please do not blindly run these. I left some comments to describe what each part does. If you don't know how to run these, you probably shouldn't be doing it at all.

Note: If the newer asset versions were added to any albums, had their description or location changed, or really had anything done to them, those changes will be lost. More queries can be written based on this to move that data over too, but I didn't bother because I don't need to.

First, a bit of housekeeping:

-- Start a transaction. Nothing will be saved until you COMMIT;
-- If you mess something up or something doesn't look right,
-- check the collapsed section at the end to ROLLBACK;
BEGIN;

-- List the names and uuids of all currently existing libraries.
SELECT * FROM "libraries";

-- Set these variables so we know which library we're moving:
SELECT
-- The path at which the library used to exist. Please note the slash at the end.
'/mnt/small-library/' AS "oldPath",
-- The path to which the library was moved. Please note the slash at the end.
'/mnt/big-library/' AS "newPath",
-- Id of the library from the previous query.
'a2209e2c-8aaa-4647-9de9-4b078af128e0'::uuid AS "libraryId"
INTO TEMPORARY TABLE "vars";

Next, we can figure out which assets have duplicates, and update the paths of the old assets:

-- maps the asset id to its path inside the library
CREATE TEMPORARY VIEW "extendedAssets"
AS SELECT
	regexp_replace("originalPath", '^(' || (SELECT "oldPath" FROM "vars" LIMIT 1) || '|' || (SELECT "newPath" FROM "vars" LIMIT 1) || ')', '') AS "originalLibraryPath",
	"assets".*
FROM "assets"
WHERE "libraryId" = (SELECT "libraryId" FROM "vars");

-- "versionedAssets" stores the ids of all files which have at least one duplicate according to their file name
-- can't use checksums as they don't match for whatever reason
DROP TABLE IF EXISTS "versionedAssets";
SELECT
	"assets"."id",
	"assets"."originalLibraryPath",
	"assets"."createdAt",
	"firstSeen",
	"lastSeen",
	"lastSeenSearch"."id" AS "lastSeenId"
INTO TEMPORARY TABLE "versionedAssets"
FROM "extendedAssets" AS "assets"
JOIN (
	SELECT "originalLibraryPath", min("createdAt") AS "firstSeen", max("createdAt") AS "lastSeen"
	FROM "extendedAssets" AS "assets"
	GROUP BY "originalLibraryPath"
) AS "otherVersions"
	ON "otherVersions"."originalLibraryPath" = "assets"."originalLibraryPath"
	AND "otherVersions"."firstSeen" != "otherVersions"."lastSeen"
JOIN "extendedAssets" AS "lastSeenSearch" ON "lastSeenSearch"."originalLibraryPath" = "assets"."originalLibraryPath" AND "lastSeenSearch"."createdAt" = "lastSeen";

-- SELECT * FROM "versionedAssets" ORDER BY "originalLibraryPath";

-- Update paths of old assets to point at the latest location known for that file path.
UPDATE "assets"
SET "originalPath" = (
	SELECT "originalPath"
	FROM "assets" AS "x"
	WHERE "x"."id" = (SELECT "lastSeenId" FROM "versionedAssets" AS "y" WHERE "y"."id" = "assets"."id")
)
WHERE "id" IN (SELECT "id" FROM "versionedAssets" WHERE "createdAt" = "firstSeen");

SELECT * FROM "assets" ORDER BY "originalFileName";

You can check that all paths were changed correctly by looking at the createdAt and originalPath columns.

The newer, duplicate versions of the asset can now be deleted. Again, this will also delete any information related to the newer version of the asset, such as which albums it was added to.

DELETE FROM "assets" WHERE "id" IN (SELECT "id" FROM "versionedAssets" WHERE "createdAt" != "firstSeen");

SELECT * FROM "assets" ORDER BY "originalFileName";

If everything looks ok, we can commit the changes:

COMMIT;
Or, if something went wrong, we can start again:
ROLLBACK;
BEGIN;

If you're running something again and getting an error saying one of the views or tables below already exists, you can safely delete them. These temporary tables will also get automatically deleted when you disconnect from the database.

DROP TABLE IF EXISTS "versionedAssets";
DROP VIEW IF EXISTS "extendedAssets";
DROP TABLE IF EXISTS "vars";

Finally, go to external libraries and run "Scan New Library Files" to make immich rediscover the previously offline assets.

@opl- commented on GitHub (Jun 10, 2024): Ran into the same problem today on immich v1.105.1. Fix: I wrote some pgSQL which updates the file path of all the original assets (the ones which existed prior to the external library path being changed) to the latest known file path for a given filename within that library. I'm referring to photos and videos as "assets", as that's what they're known as in the code. **Please do not blindly run these.** I left some comments to describe what each part does. If you don't know how to run these, you probably shouldn't be doing it at all. **Note**: If the newer asset versions were added to any albums, had their description or location changed, or really had anything done to them, those changes will be lost. More queries can be written based on this to move that data over too, but I didn't bother because I don't need to. First, a bit of housekeeping: ```pgsql -- Start a transaction. Nothing will be saved until you COMMIT; -- If you mess something up or something doesn't look right, -- check the collapsed section at the end to ROLLBACK; BEGIN; -- List the names and uuids of all currently existing libraries. SELECT * FROM "libraries"; -- Set these variables so we know which library we're moving: SELECT -- The path at which the library used to exist. Please note the slash at the end. '/mnt/small-library/' AS "oldPath", -- The path to which the library was moved. Please note the slash at the end. '/mnt/big-library/' AS "newPath", -- Id of the library from the previous query. 'a2209e2c-8aaa-4647-9de9-4b078af128e0'::uuid AS "libraryId" INTO TEMPORARY TABLE "vars"; ``` Next, we can figure out which assets have duplicates, and update the paths of the old assets: ```pgsql -- maps the asset id to its path inside the library CREATE TEMPORARY VIEW "extendedAssets" AS SELECT regexp_replace("originalPath", '^(' || (SELECT "oldPath" FROM "vars" LIMIT 1) || '|' || (SELECT "newPath" FROM "vars" LIMIT 1) || ')', '') AS "originalLibraryPath", "assets".* FROM "assets" WHERE "libraryId" = (SELECT "libraryId" FROM "vars"); -- "versionedAssets" stores the ids of all files which have at least one duplicate according to their file name -- can't use checksums as they don't match for whatever reason DROP TABLE IF EXISTS "versionedAssets"; SELECT "assets"."id", "assets"."originalLibraryPath", "assets"."createdAt", "firstSeen", "lastSeen", "lastSeenSearch"."id" AS "lastSeenId" INTO TEMPORARY TABLE "versionedAssets" FROM "extendedAssets" AS "assets" JOIN ( SELECT "originalLibraryPath", min("createdAt") AS "firstSeen", max("createdAt") AS "lastSeen" FROM "extendedAssets" AS "assets" GROUP BY "originalLibraryPath" ) AS "otherVersions" ON "otherVersions"."originalLibraryPath" = "assets"."originalLibraryPath" AND "otherVersions"."firstSeen" != "otherVersions"."lastSeen" JOIN "extendedAssets" AS "lastSeenSearch" ON "lastSeenSearch"."originalLibraryPath" = "assets"."originalLibraryPath" AND "lastSeenSearch"."createdAt" = "lastSeen"; -- SELECT * FROM "versionedAssets" ORDER BY "originalLibraryPath"; -- Update paths of old assets to point at the latest location known for that file path. UPDATE "assets" SET "originalPath" = ( SELECT "originalPath" FROM "assets" AS "x" WHERE "x"."id" = (SELECT "lastSeenId" FROM "versionedAssets" AS "y" WHERE "y"."id" = "assets"."id") ) WHERE "id" IN (SELECT "id" FROM "versionedAssets" WHERE "createdAt" = "firstSeen"); SELECT * FROM "assets" ORDER BY "originalFileName"; ``` You can check that all paths were changed correctly by looking at the `createdAt` and `originalPath` columns. The newer, duplicate versions of the asset can now be deleted. Again, this will also delete any information related to the newer version of the asset, such as which albums it was added to. ```pgsql DELETE FROM "assets" WHERE "id" IN (SELECT "id" FROM "versionedAssets" WHERE "createdAt" != "firstSeen"); SELECT * FROM "assets" ORDER BY "originalFileName"; ``` If everything looks ok, we can commit the changes: ```pgsql COMMIT; ``` <details> <summary> Or, if something went wrong, we can start again: </summary> ```pgsql ROLLBACK; BEGIN; ``` If you're running something again and getting an error saying one of the views or tables below already exists, you can safely delete them. These temporary tables will also get automatically deleted when you disconnect from the database. ```pgsql DROP TABLE IF EXISTS "versionedAssets"; DROP VIEW IF EXISTS "extendedAssets"; DROP TABLE IF EXISTS "vars"; ``` </details> Finally, go to external libraries and run "Scan New Library Files" to make immich rediscover the previously offline assets.
Author
Owner

@opl- commented on GitHub (Jun 10, 2024):

Uh, here's a more naive but simpler to understand approach which should work just as well if you don't intend to migrate other data like album memberships:

-- Start a transaction. Nothing will be saved until you COMMIT;
BEGIN;

-- List the names and uuids of all currently existing libraries.
SELECT * FROM "libraries";

-- Update all old assets to use the new path. Update all three paths, noting the trailing slashes and the ^ and % characters.
UPDATE "assets"
SET "originalPath" = regexp_replace("originalPath", '^/mnt/small-library/', '/mnt/large-library/')
WHERE "originalPath" LIKE '/mnt/small-library/%'
AND "libraryId" = 'a2209e2c-8aaa-4647-9de9-4b078af128e0'::uuid;

-- Delete duplicates of assets which have the same "originalPath" which were created after the first.
DELETE FROM "assets"
WHERE "id" IN (
	SELECT "id"
	FROM "assets"
	JOIN (
		SELECT "originalPath", min("createdAt") AS "firstSeen"
		FROM "assets"
		GROUP BY "originalPath"
	) AS "otherVersions"
	ON "otherVersions"."originalPath" = "assets"."originalPath"
	AND "assets"."createdAt" != "otherVersions"."firstSeen"
);

-- Check that everything looks in order.
SELECT * FROM "assets";

-- If it does, save:
COMMIT;

-- Otherwise, undo:
-- ROLLBACK;
@opl- commented on GitHub (Jun 10, 2024): Uh, here's a more naive but simpler to understand approach which should work just as well if you don't intend to migrate other data like album memberships: ```pgsql -- Start a transaction. Nothing will be saved until you COMMIT; BEGIN; -- List the names and uuids of all currently existing libraries. SELECT * FROM "libraries"; -- Update all old assets to use the new path. Update all three paths, noting the trailing slashes and the ^ and % characters. UPDATE "assets" SET "originalPath" = regexp_replace("originalPath", '^/mnt/small-library/', '/mnt/large-library/') WHERE "originalPath" LIKE '/mnt/small-library/%' AND "libraryId" = 'a2209e2c-8aaa-4647-9de9-4b078af128e0'::uuid; -- Delete duplicates of assets which have the same "originalPath" which were created after the first. DELETE FROM "assets" WHERE "id" IN ( SELECT "id" FROM "assets" JOIN ( SELECT "originalPath", min("createdAt") AS "firstSeen" FROM "assets" GROUP BY "originalPath" ) AS "otherVersions" ON "otherVersions"."originalPath" = "assets"."originalPath" AND "assets"."createdAt" != "otherVersions"."firstSeen" ); -- Check that everything looks in order. SELECT * FROM "assets"; -- If it does, save: COMMIT; -- Otherwise, undo: -- ROLLBACK; ```
Author
Owner

@etnoy commented on GitHub (Sep 3, 2024):

As of the current version, this is intended behavior. Moving an asset path means removing the old asset and importing a new one. We currently have no way of detecting moves.

@etnoy commented on GitHub (Sep 3, 2024): As of the current version, this is intended behavior. Moving an asset path means removing the old asset and importing a new one. We currently have no way of detecting moves.
Author
Owner

@wassupluke commented on GitHub (Sep 12, 2025):

@etnoy is this still expected behavior? If so, perhaps add a note in the install/setup instructions about not changing these paths after initial setup.

@wassupluke commented on GitHub (Sep 12, 2025): @etnoy is this still expected behavior? If so, perhaps add a note in the install/setup instructions about not changing these paths after initial setup.
Author
Owner

@etnoy commented on GitHub (Sep 12, 2025):

@etnoy is this still expected behavior? If so, perhaps add a note in the install/setup instructions about not changing these paths after initial setup.

Feel free to open a PR with the documentation changes

@etnoy commented on GitHub (Sep 12, 2025): > [@etnoy](https://github.com/etnoy) is this still expected behavior? If so, perhaps add a note in the install/setup instructions about not changing these paths after initial setup. Feel free to open a PR with the documentation changes
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: immich-app/immich#1765