diff --git a/admin/adonisrc.ts b/admin/adonisrc.ts index 34586ca..741b160 100644 --- a/admin/adonisrc.ts +++ b/admin/adonisrc.ts @@ -56,6 +56,7 @@ export default defineConfig({ () => import('#providers/map_static_provider'), () => import('#providers/kiwix_migration_provider'), () => import('#providers/qdrant_restart_policy_provider'), + () => import('#providers/version_check_provider'), ], /* diff --git a/admin/providers/version_check_provider.ts b/admin/providers/version_check_provider.ts new file mode 100644 index 0000000..20add77 --- /dev/null +++ b/admin/providers/version_check_provider.ts @@ -0,0 +1,56 @@ +import logger from '@adonisjs/core/services/logger' +import type { ApplicationService } from '@adonisjs/core/types' + +/** + * Self-heals stale `system.updateAvailable` after a sidecar-driven update. + * + * When the admin container is recreated on a new image, the KVStore still + * carries pre-update values for `system.updateAvailable` and + * `system.latestVersion`. Without intervention the UI keeps showing the + * "update available" banner until the next scheduled CheckUpdateJob (could be up to ~12h). + * + * Synchronous self-heal (no network): if the cached "latest" is not newer + * than the version we are now running, clear `updateAvailable`. The next + * scheduled CheckUpdateJob refreshes the cache from GitHub — we deliberately + * do not hit the network from boot to avoid coupling container startup to + * a network request to Github (e.g. container restart loop = flooding GitHub with requests). + * + * Note: this provider does not set `updateAvailable` to true if the cached + * "latest" is newer than the current version. We rely on the next scheduled + * CheckUpdateJob to do that, to avoid false positives in case of a stale cache. + */ +export default class VersionCheckProvider { + constructor(protected app: ApplicationService) { } + + async boot() { + if (this.app.getEnvironment() !== 'web') return + + setImmediate(async () => { + try { + const KVStore = (await import('#models/kv_store')).default + const { SystemService } = await import('#services/system_service') + const { isNewerVersion } = await import('../app/utils/version.js') + + const current = SystemService.getAppVersion() + if (current === 'dev' || current === '0.0.0'){ + logger.info(`[VersionCheckProvider] Skipping self-heal for version ${current}. Appears to be a dev build without proper version set.`) + return + } + + logger.info(`[VersionCheckProvider] Checking for stale updateAvailable (current=${current})`) + + const cachedLatest = (await KVStore.getValue('system.latestVersion')) as string | null + const earlyAccess = ((await KVStore.getValue('system.earlyAccess')) ?? false) as boolean + + if (cachedLatest && !isNewerVersion(cachedLatest, current, earlyAccess)) { + await KVStore.setValue('system.updateAvailable', false) + logger.info( + `[VersionCheckProvider] Cleared stale updateAvailable (cached=${cachedLatest}, current=${current})` + ) + } + } catch (err: any) { + logger.warn(`[VersionCheckProvider] Self-heal skipped: ${err?.message ?? err}`) + } + }) + } +}