Immich 外部库(External Libraries)完全实战指南:导入路径、排除模式与文件系统监控
Immich 的外部库功能让你把散落在 Immich 存储目录之外的照片和视频(NAS 目录、老照片文件夹、家庭视频等)纳入统一管理,它们会出现在时间线、地图和相册中,行为与普通资产一致。本文基于 外部库官方文档 展开,结合服务端源码逐层剖析导入路径校验、排除模式(glob)匹配、文件监控(watcher)与夜间扫描任务的完整实现,帮你不仅会用,还知道它为什么这样工作。
外部库是什么,边界在哪里
外部库追踪存储在 Immich 文件系统之外(外部)的资产。当外部库被扫描时,Immich 会从磁盘加载视频和照片并创建对应的资产记录,之后这些资产会显示在主时间线中,看起来、用起来和任何普通资产一样——包括在地图上查看、加入相册等。文件在 Immich 之外被修改后,需要重新扫描库才能让变更生效。
几个必须牢记的关键边界(均直接来自官方文档):
- 单一属主:当前一个外部库只能属于一个用户,该用户在库创建时选定,此后不可更改;
- 删除文件的去向:如果外部资产在磁盘上被删除,重新扫描时 Immich 会将其移入回收站。要恢复资产,需先恢复原始文件;30 天后文件会从回收站移除,Immich 内对该资产做过的所有元数据变更都会丢失(这与系统配置中回收站默认
days: 30一致,见 config.dto.ts 中trash.days的默认值); - 元数据不会写回外部文件:无论以何种方式给外部资产添加元数据(加入相册、编辑描述等),这些元数据只存储在 Immich 内部,不会持久化到外部资产文件。如果资产在库内被移动到另一个位置,重新扫描时所有此类元数据都会丢失——因为移动后该资产被视为新资产。这是已知问题,官方说明将在未来版本修复;
- 缓存导致的延迟显示:由于激进的缓存机制,刷新后的资产可能无法立即在 Web 视图中正确显示,需要清理浏览器缓存。在 Chrome 中:按 F12 打开开发者工具,按 F5 重新加载,再右键点击重新加载按钮选择 “Empty Cache and Hard Reload”。这也是已知问题。
导入路径(Import Paths):扫描什么、如何校验
外部库使用导入路径来决定扫描哪些文件。每个库可以有多个导入路径,以便把不同位置的文件加入同一个库。导入路径会被递归扫描;如果同一文件出现在多个导入路径中,它只会被添加一次。每个导入路径必须是一个存在于文件系统上且可读的目录;导入路径对话框会提示任何不可访问的路径。
如果编辑导入路径导致某个外部文件不再位于任何导入路径中,它会被按“文件已删除”的方式从库中移除。如果文件被移回某个导入路径,则会像新文件一样被重新添加。
从源码看,导入路径的校验逻辑集中在 LibraryService.validateImportPath,其检查顺序为:
- 不能指向 Immich 的媒体上传目录(
StorageCore.isImmichPath),否则报错 “Cannot use media upload folder for external libraries”; - 必须是绝对路径,否则提示 “Import path must be absolute” 并给出解析后的建议路径;
- 路径必须存在且是目录(
stat失败时区分ENOENT等错误并给出可读消息); - 必须具有读权限(
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”库:
- 点击右上角头像;
- 点击
Administration -> External Libraries; - 点击
Create Library; - 选择拥有该库的用户(之后不可更改);
- 进入库管理页面后,点击
Folders区域的Add; - 输入
/mnt/media/christmas-trip,点击 Add; - 点击
EditLibrary,将其重命名为 “Christmas Trip”。
注意:这里必须使用容器内看到的路径 /mnt/media/christmas-trip,而不是宿主机路径 /mnt/nas/christmas-trip——所有路径都必须是 Docker 容器视角下的路径。
接着添加排除模式过滤 Raw 文件:
- 点击
Exclusion Patterns区域的Add; - 输入
**/Raw/**,点击 Add; - 点击
Scan。
此时圣诞旅行库将在后台开始扫描。趁此期间创建第二个库:
- 回到
Administration -> External Libraries; - 点击
Create Library,选择属主用户; - 在
Folders区域Add/mnt/media/old-pics; - 再次
Add/mnt/media/videos; - 点击
Scan; - 点击
EditLibrary,重命名为 “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把不在任何导入路径内、或被排除模式命中的资产标记为离线,再对剩余资产分批入队LibrarySyncAssets。checkExistingAsset 决定每个资产的命运:磁盘上找不到文件 → 标记离线(对应文档中“磁盘删除后移入回收站”);文件mtime变化 → 触发元数据重新提取;之前离线的资产重新回到导入路径且不被排除 → 恢复在线。
这也解释了文档中的两个行为细节:排除模式是“按完整路径 glob 匹配”的,且同一文件出现在多个导入路径只会被添加一次(数据库侧按 libraryId + originalPath 去重)。相关行为在 library.service.spec.ts 与 library.repository.ts 中有对应测试与实现可查。
自动监控(Automatic Watching,实验特性)
该功能面向高级用户,官方明确标注为实验性(EXPERIMENTAL):启用后 Immich 会自动监听文件系统,新资产无需重新扫描即可自动导入。如果你的照片在网络驱动器上,自动文件监听大概率不工作,此时需要依赖周期性库刷新(见下文“自定义扫描间隔”)来拉取变更。
源码印证:监控通过 StorageRepository.watch 封装的 chokidar 实现,library.service.ts 中配置了 usePolling: false、ignoreInitial: true 以及 awaitWriteFinish(stabilityThreshold: 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: true、cronExpression 为 EVERY_DAY_AT_MIDNIGHT(即每天午夜)、library.watch.enabled: false(监控默认关闭)。
该夜间任务同时会清理处于“删除中”卡住状态的库。在库管理页面点击 “Scan all libraries” 也可以手动触发这一清理。源码中,handleQueueScanAll 会先入队 LibraryDeleteCheck,再由 handleQueueCleanup 找出所有处于待删除状态的库并重新入队删除任务——这就是“服务器重启导致删除中断后由夜间任务兜底”的实现。
删除库:删除外部库时,库内所有资产会随库一起被立即删除。注意:库虽然可能在后台花较长时间才能真正删完,但会立即从库列表中移除。若删除过程被打断(例如服务器重启),会在下一次夜间 cron 任务中完成清理;也可以点击库列表中的 “Scan All Libraries” 按钮手动启动清理。源码层面,handleDeleteLibrary 对每个资产入队 AssetDelete 且 deleteOnDisk: false——Immich 只删除自己的资产记录,不会删除外部磁盘上的原始文件,删除库前无需担心源数据。
文件夹视图(Folder View)
文件夹视图是时间线之外的另一种浏览方式,类似文件资源管理器,允许你浏览库内的文件夹与文件。对于精心整理、高度自定义的外部库,或者配置得当的存储模板,这个功能非常实用。可以在 Account Settings > Features > Folders 中启用。更多细节参见 文件夹视图文档。
设置自定义扫描间隔
此操作仅管理员可执行。在 Administration -> Settings -> External Library 下可以定义触发外部库重新扫描的自定义间隔,支持预设选项或 cron 表达式格式(可参考 Crontab Guru 之类的工具学习 cron 语法)。对应的服务端配置结构为 library.scan.{enabled, cronExpression},见 config.dto.ts 中 AdminConfigLibraryScanDto 的定义与默认值。
外部库无法正确扫描时的排查清单
有时外部库不能正常扫描,通常是因为 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 及其 单元测试 中对照源码验证。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

