diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b4f2c35..6d75b299 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,6 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * **fleet:** fix false "Update available" on remote nodes whose `api_url` has a trailing slash, causing `fetchRemoteMeta` to construct a double-slash URL that fails silently * **fleet:** detect updates via GitHub Releases API instead of comparing against the gateway's own version. Previously, the local node could never appear outdated because it compared its version to itself. The Recheck button now invalidates the 30-minute version cache and fetches the actual latest release. +* **fleet:** add Docker Hub tags API as fallback for version detection when the GitHub repo is private. The GitHub Releases API returns 404 for private repos, causing version detection to silently fail and fall back to the gateway's own version. ## [0.41.1](https://github.com/AnsoCode/Sencho/compare/v0.41.0...v0.41.1) (2026-04-08) diff --git a/backend/src/index.ts b/backend/src/index.ts index 71be8117..ccc7310c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1241,17 +1241,44 @@ let latestVersionCache: { version: string; fetchedAt: number } | null = null; let latestVersionInflight: Promise | null = null; const LATEST_VERSION_CACHE_TTL = 30 * 60 * 1000; // 30 minutes +async function fetchFromGitHub(): Promise { + const res = await fetch('https://api.github.com/repos/AnsoCode/Sencho/releases/latest', { + headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'Sencho' }, + signal: AbortSignal.timeout(10000), + }); + if (!res.ok) return null; + const data = await res.json() as { tag_name?: string }; + const tag = data.tag_name?.replace(/^v/, '') ?? null; + return tag && semver.valid(tag) ? tag : null; +} + +async function fetchFromDockerHub(): Promise { + const res = await fetch( + 'https://hub.docker.com/v2/repositories/saelix/sencho/tags/?page_size=50&ordering=last_updated', + { headers: { 'User-Agent': 'Sencho' }, signal: AbortSignal.timeout(10000) }, + ); + if (!res.ok) return null; + const data = await res.json() as { results?: { name: string }[] }; + const tags = (data.results ?? []) + .map(t => t.name) + .filter(n => semver.valid(n)); + if (tags.length === 0) return null; + tags.sort(semver.rcompare); + return tags[0]; +} + async function fetchLatestSenchoVersion(): Promise { try { - const res = await fetch('https://api.github.com/repos/AnsoCode/Sencho/releases/latest', { - headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'Sencho' }, - signal: AbortSignal.timeout(10000), - }); - if (!res.ok) return null; - const data = await res.json() as { tag_name?: string }; - const tag = data.tag_name?.replace(/^v/, '') ?? null; - return tag && semver.valid(tag) ? tag : null; - } catch { + const gh = await fetchFromGitHub(); + if (gh) return gh; + } catch (err) { + // GitHub API fails for private repos or rate limits; try Docker Hub + console.warn('[VersionCheck] GitHub fetch failed:', (err as Error).message); + } + try { + return await fetchFromDockerHub(); + } catch (err) { + console.warn('[VersionCheck] Docker Hub fetch failed:', (err as Error).message); return null; } }