feat: change default media location to /data (#20367)

* feat!: change default media location to /data

* feat: dynamically detect media location
This commit is contained in:
Jason Rasmussen
2025-07-29 16:58:50 -04:00
committed by GitHub
parent 4cae15f28d
commit 58521c9efb
39 changed files with 316 additions and 209 deletions
+59 -24
View File
@@ -1,9 +1,16 @@
import { Injectable } from '@nestjs/common';
import { join } from 'node:path';
import { APP_MEDIA_LOCATION } from 'src/constants';
import { StorageCore } from 'src/cores/storage.core';
import { OnEvent, OnJob } from 'src/decorators';
import { DatabaseLock, JobName, JobStatus, QueueName, StorageFolder, SystemMetadataKey } from 'src/enum';
import {
BootstrapEventPriority,
DatabaseLock,
JobName,
JobStatus,
QueueName,
StorageFolder,
SystemMetadataKey,
} from 'src/enum';
import { BaseService } from 'src/services/base.service';
import { JobOf, SystemFlags } from 'src/types';
import { ImmichStartupError } from 'src/utils/misc';
@@ -12,9 +19,32 @@ const docsMessage = `Please see https://immich.app/docs/administration/system-in
@Injectable()
export class StorageService extends BaseService {
@OnEvent({ name: 'AppBootstrap' })
async onBootstrap() {
private detectMediaLocation(): string {
const envData = this.configRepository.getEnv();
if (envData.storage.mediaLocation) {
return envData.storage.mediaLocation;
}
const targets: string[] = [];
const candidates = ['/data', '/usr/src/app/upload'];
for (const candidate of candidates) {
const exists = this.storageRepository.existsSync(candidate);
if (exists) {
targets.push(candidate);
}
}
if (targets.length === 1) {
return targets[0];
}
return '/usr/src/app/upload';
}
@OnEvent({ name: 'AppBootstrap', priority: BootstrapEventPriority.StorageService })
async onBootstrap() {
StorageCore.setMediaLocation(this.detectMediaLocation());
await this.databaseRepository.withLock(DatabaseLock.SystemFileMounts, async () => {
const flags =
@@ -53,6 +83,7 @@ export class StorageService extends BaseService {
this.logger.log('Successfully verified system mount folder checks');
} catch (error) {
const envData = this.configRepository.getEnv();
if (envData.storage.ignoreMountCheckErrors) {
this.logger.error(error as Error);
this.logger.warn('Ignoring mount folder errors');
@@ -63,30 +94,34 @@ export class StorageService extends BaseService {
});
await this.databaseRepository.withLock(DatabaseLock.MediaLocation, async () => {
const current = APP_MEDIA_LOCATION;
const savedValue = await this.systemMetadataRepository.get(SystemMetadataKey.MediaLocation);
let previous = savedValue?.location || '';
const current = StorageCore.getMediaLocation();
const samples = await this.assetRepository.getFileSamples();
if (samples.length > 0) {
const originalPath = samples[0].originalPath;
const savedValue = await this.systemMetadataRepository.get(SystemMetadataKey.MediaLocation);
let previous = savedValue?.location || '';
if (previous !== current) {
this.logger.log(`Media location changed (from=${previous}, to=${current})`);
const samples = await this.assetRepository.getFileSamples();
if (samples.length > 0) {
const originalPath = samples[0].originalPath;
if (!previous) {
previous = originalPath.startsWith('upload/') ? 'upload' : '/usr/src/app/upload';
}
if (previous && originalPath.startsWith(previous)) {
this.logger.warn(
`Detected a change to IMMICH_MEDIA_LOCATION, performing an automatic migration of file paths from ${previous} to ${current}, this may take awhile`,
);
await this.databaseRepository.migrateFilePaths(previous, current);
}
if (!previous) {
previous = originalPath.startsWith('upload/') ? 'upload' : '/usr/src/app/upload';
}
await this.systemMetadataRepository.set(SystemMetadataKey.MediaLocation, { location: current });
if (previous !== current) {
this.logger.log(`Media location changed (from=${previous}, to=${current})`);
if (!originalPath.startsWith(previous)) {
throw new Error(
'Detected an inconsistent media location. For more information, see https://immich.app/errors#inconsistent-media-location',
);
}
this.logger.warn(
`Detected a change to media location, performing an automatic migration of file paths from ${previous} to ${current}, this may take awhile`,
);
await this.databaseRepository.migrateFilePaths(previous, current);
}
}
await this.systemMetadataRepository.set(SystemMetadataKey.MediaLocation, { location: current });
});
}