diff --git a/apps/root.immich.app/src/lib/compose/build.test.ts b/apps/root.immich.app/src/lib/compose/build.test.ts index c648484c..290b4481 100644 --- a/apps/root.immich.app/src/lib/compose/build.test.ts +++ b/apps/root.immich.app/src/lib/compose/build.test.ts @@ -209,6 +209,23 @@ describe('buildCompose', () => { expect(spec.networks).toBeUndefined(); }); + it('keeps hardware acceleration mounts in rootless mode', () => { + const config = structuredClone(DEFAULT_CONFIG); + config.hwaccel.ml = 'openvino'; + config.rootless = { enabled: true, uid: 1000, gid: 1000 }; + const spec = parse(buildCompose(config, VERSION)); + + expect(spec.services['immich-machine-learning'].volumes).toEqual([ + './ml-model-cache:/cache', + './ml-dotcache:/.cache', + './ml-config:/.config', + '/dev/bus/usb:/dev/bus/usb', + ]); + expect(spec.services.redis.volumes).toEqual(['./redis:/data']); + // the named cache volume is no longer mounted, so it is not declared + expect(spec.volumes).toBeUndefined(); + }); + it('applies a custom uid and gid to every service', () => { const config = structuredClone(DEFAULT_CONFIG); config.rootless = { enabled: true, uid: 99, gid: 100 }; diff --git a/apps/root.immich.app/src/lib/compose/build.ts b/apps/root.immich.app/src/lib/compose/build.ts index 42f42e5c..5576e6ee 100644 --- a/apps/root.immich.app/src/lib/compose/build.ts +++ b/apps/root.immich.app/src/lib/compose/build.ts @@ -71,6 +71,8 @@ export const buildComposeSpec = (config: ImmichConfig, version: string): Compose .filter(({ path }) => path.trim()) .map(({ path, readOnly }) => `${path.trim()}:${path.trim()}${readOnly ? ':ro' : ''}`); + const rootlessVolumes = (name: string) => (rootless.enabled ? ROOTLESS_VOLUMES.get(name) : undefined); + const backingServices: Record = {}; if (!config.redis.external) { @@ -78,6 +80,7 @@ export const buildComposeSpec = (config: ImmichConfig, version: string): Compose container_name: containerName('immich_redis'), image: IMAGES.redis, healthcheck: { test: 'redis-cli ping || exit 1' }, + volumes: rootlessVolumes('redis') ?? [], restart: 'always', }; } @@ -132,7 +135,7 @@ export const buildComposeSpec = (config: ImmichConfig, version: string): Compose { container_name: containerName('immich_machine_learning'), image: IMAGES.machineLearning(version + ML_BACKENDS[config.hwaccel.ml].tag), - volumes: [`${NamedVolume.ModelCache}:/cache`], + volumes: rootlessVolumes('immich-machine-learning') ?? [`${NamedVolume.ModelCache}:/cache`], restart: 'always', healthcheck: { disable: false }, }, @@ -143,12 +146,7 @@ export const buildComposeSpec = (config: ImmichConfig, version: string): Compose if (rootless.enabled) { for (const [name, service] of Object.entries(services)) { - const hardened = deepmerge(service, rootlessHardening(rootless)); - const rootlessVolumes = ROOTLESS_VOLUMES.get(name); - if (rootlessVolumes) { - hardened.volumes = rootlessVolumes; - } - services[name] = hardened; + services[name] = deepmerge(service, rootlessHardening(rootless)); } }