fix(server): add rate limit and deduplication to version check (#27747)

This commit is contained in:
Zack Pollard
2026-04-13 13:35:46 +01:00
committed by GitHub
parent bee49cef02
commit 352f6ecc28
4 changed files with 48 additions and 12 deletions
+23 -11
View File
@@ -4,7 +4,7 @@ import semver, { SemVer } from 'semver';
import { serverVersion } from 'src/constants';
import { OnEvent, OnJob } from 'src/decorators';
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
import { CronJob, DatabaseLock, JobName, JobStatus, QueueName, SystemMetadataKey } from 'src/enum';
import { CronJob, DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName, SystemMetadataKey } from 'src/enum';
import { ArgOf } from 'src/repositories/event.repository';
import { BaseService } from 'src/services/base.service';
import { VersionCheckMetadata } from 'src/types';
@@ -21,18 +21,21 @@ const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): Re
@Injectable()
export class VersionService extends BaseService {
@OnEvent({ name: 'AppBootstrap' })
@OnEvent({ name: 'AppBootstrap', workers: [ImmichWorker.Microservices] })
async onBootstrap(): Promise<void> {
await this.handleVersionCheck();
const hasLock = await this.databaseRepository.tryLock(DatabaseLock.VersionCheck);
if (hasLock) {
await this.handleVersionCheck();
const randomMinute = Math.floor(Math.random() * 60);
const expression = `${randomMinute} * * * *`;
this.logger.debug(`Scheduling version check for cron ${expression}`);
this.cronRepository.create({
name: CronJob.VersionCheck,
expression,
onTick: () => handlePromiseError(this.handleQueueVersionCheck(), this.logger),
});
const randomMinute = Math.floor(Math.random() * 60);
const expression = `${randomMinute} * * * *`;
this.logger.debug(`Scheduling version check for cron ${expression}`);
this.cronRepository.create({
name: CronJob.VersionCheck,
expression,
onTick: () => handlePromiseError(this.handleQueueVersionCheck(), this.logger),
});
}
await this.databaseRepository.withLock(DatabaseLock.VersionHistory, async () => {
const previous = await this.versionRepository.getLatest();
@@ -86,6 +89,15 @@ export class VersionService extends BaseService {
return JobStatus.Skipped;
}
const versionCheck = await this.systemMetadataRepository.get(SystemMetadataKey.VersionCheckState);
if (versionCheck?.checkedAt) {
const lastUpdate = DateTime.fromISO(versionCheck.checkedAt);
const elapsedTime = DateTime.now().diff(lastUpdate).as('seconds');
if (elapsedTime < 50) {
return JobStatus.Skipped;
}
}
const { version: releaseVersion, published_at: publishedAt } = await this.serverInfoRepository.getLatestRelease();
const metadata: VersionCheckMetadata = { checkedAt: DateTime.utc().toISO(), releaseVersion };