首页
/ Immich 外部库(External Libraries)完全实战指南:导入路径、排除模式与文件系统监控

Immich 外部库(External Libraries)完全实战指南:导入路径、排除模式与文件系统监控

2026-09-05 15:55:39作者:韦蓉瑛

Immich 的外部库功能让你把散落在 Immich 存储目录之外的照片和视频(NAS 目录、老照片文件夹、家庭视频等)纳入统一管理,它们会出现在时间线、地图和相册中,行为与普通资产一致。本文基于 外部库官方文档 展开,结合服务端源码逐层剖析导入路径校验、排除模式(glob)匹配、文件监控(watcher)与夜间扫描任务的完整实现,帮你不仅会用,还知道它为什么这样工作。

外部库是什么,边界在哪里

外部库追踪存储在 Immich 文件系统之外(外部)的资产。当外部库被扫描时,Immich 会从磁盘加载视频和照片并创建对应的资产记录,之后这些资产会显示在主时间线中,看起来、用起来和任何普通资产一样——包括在地图上查看、加入相册等。文件在 Immich 之外被修改后,需要重新扫描库才能让变更生效。

几个必须牢记的关键边界(均直接来自官方文档):

  • 单一属主:当前一个外部库只能属于一个用户,该用户在库创建时选定,此后不可更改;
  • 删除文件的去向:如果外部资产在磁盘上被删除,重新扫描时 Immich 会将其移入回收站。要恢复资产,需先恢复原始文件;30 天后文件会从回收站移除,Immich 内对该资产做过的所有元数据变更都会丢失(这与系统配置中回收站默认 days: 30 一致,见 config.dto.tstrash.days 的默认值);
  • 元数据不会写回外部文件:无论以何种方式给外部资产添加元数据(加入相册、编辑描述等),这些元数据只存储在 Immich 内部,不会持久化到外部资产文件。如果资产在库内被移动到另一个位置,重新扫描时所有此类元数据都会丢失——因为移动后该资产被视为新资产。这是已知问题,官方说明将在未来版本修复;
  • 缓存导致的延迟显示:由于激进的缓存机制,刷新后的资产可能无法立即在 Web 视图中正确显示,需要清理浏览器缓存。在 Chrome 中:按 F12 打开开发者工具,按 F5 重新加载,再右键点击重新加载按钮选择 “Empty Cache and Hard Reload”。这也是已知问题。

导入路径(Import Paths):扫描什么、如何校验

外部库使用导入路径来决定扫描哪些文件。每个库可以有多个导入路径,以便把不同位置的文件加入同一个库。导入路径会被递归扫描;如果同一文件出现在多个导入路径中,它只会被添加一次。每个导入路径必须是一个存在于文件系统上且可读的目录;导入路径对话框会提示任何不可访问的路径。

如果编辑导入路径导致某个外部文件不再位于任何导入路径中,它会被按“文件已删除”的方式从库中移除。如果文件被移回某个导入路径,则会像新文件一样被重新添加。

从源码看,导入路径的校验逻辑集中在 LibraryService.validateImportPath,其检查顺序为:

  1. 不能指向 Immich 的媒体上传目录(StorageCore.isImmichPath),否则报错 “Cannot use media upload folder for external libraries”;
  2. 必须是绝对路径,否则提示 “Import path must be absolute” 并给出解析后的建议路径;
  3. 路径必须存在且是目录(stat 失败时区分 ENOENT 等错误并给出可读消息);
  4. 必须具有读权限(R_OK 检查),否则报 “Lacking read permission for folder”。

这正是文档中“导入路径对话框会提示不可访问路径”的底层实现:库创建或更新时,所有导入路径都会先经过 validate 逐条验证。

排除模式(Exclusion Patterns):用 glob 过滤不想要的文件

默认情况下,导入路径中的所有文件都会被加入库。若某些文件不应被导入,可以使用排除模式。排除模式是与完整文件路径匹配的 glob 模式,匹配的文件不会被加入库。排除模式可以在每个库的扫描设置页面中添加。

文档给出的基本示例(可直接照抄使用):

  • **/*.tif 排除所有 .tif 扩展名的文件
  • **/hidden.jpg 排除所有名为 hidden.jpg 的文件
  • **/Raw/** 排除任何名为 Raw 的目录中的所有文件
  • **/*.{tif,jpg} 排除 .tif.jpg 扩展名的所有文件

通配符语义需要注意:* 匹配零个或多个字符(仅限文件名或单个目录名内);** 递归匹配零个或多个子目录,且当 ** 出现在模式末尾时,它包含子目录中的任何/所有文件。例如 **/exclude_me/** 会排除任何名为 exclude_me 的目录及其所有子目录中的全部文件。

特殊字符需要转义,例如:

  • **/\@eaDir/** 排除任何名为 @eaDir 的目录中的所有文件

官方说明还指出:Immich 内部用 glob 包处理排除模式,某些场景下模式会被翻译成 Postgres LIKE 模式。设计意图是支持基础的目录排除,不建议高级用法,因为复杂 glob 无法可靠地翻译成 Postgres 语法。

源码层面有两条补充证据,值得知道:

  • 新建库时系统会自动注入一组默认排除模式,覆盖 Synology(#recycle#snapshot)、群晖/威联通(@eaDir)、macOS(._*.stversions.stfolder)等 NAS 系统文件,见 LibraryService.create
  • 文件监控器构建匹配器时,用 picomatch 以库的 exclusionPatterns 作为 ignore 列表,只对支持的文件扩展名做匹配,见 library.service.ts

实战:把现有图库接入 Immich

下面完整复现文档中的示例场景。假设你有以下目录要接入:

  • /home/user/old-pics:童年照片文件夹;
  • /mnt/nas/christmas-trip:圣诞旅行照片,其中子目录 /mnt/nas/christmas-trip/Raw 是单反相机原始文件,不希望导入;
  • /mnt/media/videos:同一次旅行的视频。

规划思路:圣诞照片因为要排除 Raw 文件,应独立成库;视频和老照片可以放在同一个库里(因为没有匹配排除模式的文件);若其他文件夹中没有需要排除的文件,也可以把三个目录都放进同一个库。

第一步:挂载 Docker 卷

immich-server 容器需要能访问这些目录。修改 docker compose 文件(可对照仓库中的 docker/docker-compose.yml):

  immich-server:
    volumes:
      - ${UPLOAD_LOCATION}:/data
+     - /mnt/nas/christmas-trip:/mnt/media/christmas-trip:ro
+     - /home/user/old-pics:/mnt/media/old-pics:ro
+     - /mnt/media/videos:/mnt/media/videos:ro
+     - /mnt/media/videos2:/mnt/media/videos2 # WARNING: Immich will be able to delete the files in this folder, as it does not end with :ro
+     - "C:/Users/user_name/Desktop/my media:/mnt/media/my-media:ro" # import path in Windows system.

要点:

  • 末尾的 ro 标志只授予卷只读访问,禁止在 Web UI 中删除这些图片、向库写入元数据(如 XMP sidecars);
  • 修改后必须运行 docker compose up -d 使变更生效,并确认容器内能看到挂载路径;
  • 注意 Windows 宿主机的导入路径写法(带引号的 C:/... 形式)。

第二步:创建库并配置排除模式

以下操作必须由 Immich 管理员执行。

创建“Christmas Trip”库:

  1. 点击右上角头像;
  2. 点击 Administration -> External Libraries
  3. 点击 Create Library
  4. 选择拥有该库的用户(之后不可更改);
  5. 进入库管理页面后,点击 Folders 区域的 Add
  6. 输入 /mnt/media/christmas-trip,点击 Add;
  7. 点击 Edit Library,将其重命名为 “Christmas Trip”。

注意:这里必须使用容器内看到的路径 /mnt/media/christmas-trip,而不是宿主机路径 /mnt/nas/christmas-trip——所有路径都必须是 Docker 容器视角下的路径。

接着添加排除模式过滤 Raw 文件:

  1. 点击 Exclusion Patterns 区域的 Add
  2. 输入 **/Raw/**,点击 Add;
  3. 点击 Scan

此时圣诞旅行库将在后台开始扫描。趁此期间创建第二个库:

  1. 回到 Administration -> External Libraries
  2. 点击 Create Library,选择属主用户;
  3. Folders 区域 Add /mnt/media/old-pics
  4. 再次 Add /mnt/media/videos
  5. 点击 Scan
  6. 点击 Edit Library,重命名为 “Old videos and photos”。

几秒钟内,old-pics 和 videos 文件夹中的资产就会出现在主时间线中。

扫描的底层机制:从磁盘遍历到资产同步

理解扫描的内部流程,能解释很多“为什么”。从 library.service.ts 的源码结构看,一次库扫描(queueScan)会依次入队两类任务:

  • LibrarySyncFilesQueueAll(新文件导入):先逐条验证导入路径(见上文的 validateImportPath),再用 storage.repository.ts 中的 walk 递归遍历磁盘、套用排除模式,按分页批次把“尚未入库的外部资产路径”筛选出来(filterNewExternalAssetPaths),为每批新文件入队 LibrarySyncFiles 任务。每个新文件经 processEntity 生成资产记录:originalPath 记录绝对路径、isExternal: true、文件修改时间取自磁盘 mtime、类型按扩展名判断视频/图片;随后批量建库记录并触发 SidecarCheck(XMP 侧车发现)进而做元数据提取;
  • LibrarySyncAssetsQueueAll(存量资产对账):先用 detectOfflineExternalAssets 把不在任何导入路径内、或被排除模式命中的资产标记为离线,再对剩余资产分批入队 LibrarySyncAssetscheckExistingAsset 决定每个资产的命运:磁盘上找不到文件 → 标记离线(对应文档中“磁盘删除后移入回收站”);文件 mtime 变化 → 触发元数据重新提取;之前离线的资产重新回到导入路径且不被排除 → 恢复在线。

这也解释了文档中的两个行为细节:排除模式是“按完整路径 glob 匹配”的,且同一文件出现在多个导入路径只会被添加一次(数据库侧按 libraryId + originalPath 去重)。相关行为在 library.service.spec.tslibrary.repository.ts 中有对应测试与实现可查。

自动监控(Automatic Watching,实验特性)

该功能面向高级用户,官方明确标注为实验性(EXPERIMENTAL):启用后 Immich 会自动监听文件系统,新资产无需重新扫描即可自动导入。如果你的照片在网络驱动器上,自动文件监听大概率不工作,此时需要依赖周期性库刷新(见下文“自定义扫描间隔”)来拉取变更。

源码印证:监控通过 StorageRepository.watch 封装的 chokidar 实现,library.service.ts 中配置了 usePolling: falseignoreInitial: true 以及 awaitWriteFinishstabilityThreshold: 5000 毫秒、pollInterval: 1000 毫秒)——即文件写完并稳定 5 秒后才触发导入任务,避免读到写了一半的文件。另外,只有抢到 DatabaseLock.Library 数据库锁的那一个微服务实例才会启动监控,保证多副本部署下监听只发生一次(见 onConfigInit)。文件 add/change 事件入队 LibrarySyncFiles,unlink 事件入队 LibraryRemoveAsset

监控功能排障

  • ENOSPC 错误:需要提高文件监听器上限。对应 sysctl 键为 fs.inotify.max_user_watches,默认值 8192,应调大到大于你要监听的文件数。注意 Immich 必须监听导入路径中的所有文件(包括被忽略的文件),例如:
ERROR [LibraryService] Library watcher for library c69faf55-f96d-4aa0-b83b-2d80cbc27d98 encountered error: Error: ENOSPC: System limit for number of file watchers reached, watch '/media/photo.jpg'
  • 监听器挂起:罕见情况下库监听器可能挂起,导致 Immich 无法启动。此时需在配置文件中禁用库监听器。如果监听是在 Immich 界面内启用的,就必须在不启动微服务的情况下启动应用:在 docker compose 文件中禁用 microservices,启动 Immich,在管理设置中禁用库监听器,关闭 Immich,重新启用 microservices,之后 Immich 即可正常启动。

夜间任务(Nightly Job)与删除库

系统内置一个每日执行的自动扫描任务,其调度可配置(见下文“自定义扫描间隔”)。从 config.dto.ts 看,默认配置为 library.scan.enabled: truecronExpressionEVERY_DAY_AT_MIDNIGHT(即每天午夜)、library.watch.enabled: false(监控默认关闭)。

该夜间任务同时会清理处于“删除中”卡住状态的库。在库管理页面点击 “Scan all libraries” 也可以手动触发这一清理。源码中,handleQueueScanAll 会先入队 LibraryDeleteCheck,再由 handleQueueCleanup 找出所有处于待删除状态的库并重新入队删除任务——这就是“服务器重启导致删除中断后由夜间任务兜底”的实现。

删除库:删除外部库时,库内所有资产会随库一起被立即删除。注意:库虽然可能在后台花较长时间才能真正删完,但会立即从库列表中移除。若删除过程被打断(例如服务器重启),会在下一次夜间 cron 任务中完成清理;也可以点击库列表中的 “Scan All Libraries” 按钮手动启动清理。源码层面,handleDeleteLibrary 对每个资产入队 AssetDeletedeleteOnDisk: false——Immich 只删除自己的资产记录,不会删除外部磁盘上的原始文件,删除库前无需担心源数据。

文件夹视图(Folder View)

文件夹视图是时间线之外的另一种浏览方式,类似文件资源管理器,允许你浏览库内的文件夹与文件。对于精心整理、高度自定义的外部库,或者配置得当的存储模板,这个功能非常实用。可以在 Account Settings > Features > Folders 中启用。更多细节参见 文件夹视图文档

Immich 外部库的文件夹视图界面

设置自定义扫描间隔

此操作仅管理员可执行。在 Administration -> Settings -> External Library 下可以定义触发外部库重新扫描的自定义间隔,支持预设选项或 cron 表达式格式(可参考 Crontab Guru 之类的工具学习 cron 语法)。对应的服务端配置结构为 library.scan.{enabled, cronExpression},见 config.dto.tsAdminConfigLibraryScanDto 的定义与默认值。

为外部库设置自定义扫描间隔

外部库无法正确扫描时的排查清单

有时外部库不能正常扫描,通常是因为 Immich 无法访问文件。文档给出的排查清单逐项核对:

  • docker-compose 文件中卷是否挂载正确?
  • 卷是否同时挂载到了所有 worker 容器?
  • 导入路径是否设置正确,并且与 docker-compose 文件中设置的路径一致?
  • 导入路径中不要使用符号链接,也不要跨 Docker 挂载点做链接;
  • 文件权限是否正确?
  • 确认路径使用正斜杠(/)而非反斜杠。

验证 Immich 能否触达外部库的实操方法:进入容器 shell 执行 docker exec -it immich_server bash,如果你的导入路径是 /mnt/photos,用 ls /mnt/photos 检查。如果你使用了独立的 microservices 容器,务必为它配置相同的挂载点,并在该容器内同样确认可访问性(因为监控与扫描任务实际运行在 microservices 中,见前文源码分析)。

小结

外部库让 Immich 从“上传式图库”扩展为“索引式图库”:原始文件留在原处,Immich 只维护资产索引与元数据。掌握导入路径的容器视角、**/xxx/** 这类排除模式、ro 只读挂载、监控的 inotify 限制与夜间扫描的 cron 配置,就足以稳定运营大规模外部媒体库。所有行为均可在 server/src/services/library.service.ts 及其 单元测试 中对照源码验证。

登录后查看全文
热门项目推荐
相关项目推荐