From a89479ed6fe913bf4c18a00d93d3704bb6a33a03 Mon Sep 17 00:00:00 2001 From: Thomas Camlong Date: Wed, 27 May 2026 16:42:14 +0200 Subject: [PATCH] =?UTF-8?q?fix(onboarding):=20strict=20Docker=20discovery?= =?UTF-8?q?=20=E2=80=94=20only=20running=20containers=20with=20matched=20i?= =?UTF-8?q?cons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change listContainers to all:false so only running containers are discovered - Replace loose bestMatch with strictIconMatch (exact or startsWith) to avoid false positives from short image names matching unrelated icons - Gate discovered apps behind DB icon match; unmatched containers are hidden - Add CDN icon URL fallback for matched integrations display --- .../api/src/router/onboard/onboard-router.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/api/src/router/onboard/onboard-router.ts b/packages/api/src/router/onboard/onboard-router.ts index 380c9361c..8c83ee980 100644 --- a/packages/api/src/router/onboard/onboard-router.ts +++ b/packages/api/src/router/onboard/onboard-router.ts @@ -158,7 +158,6 @@ export const onboardRouter = createTRPCRouter({ try { const { DockerSingleton, dockerLabels } = await import("@homarr/docker"); - const { bestMatch } = await import("@homarr/common"); const dockerInstances = DockerSingleton.getInstances(); if (dockerInstances.length === 0) { @@ -167,7 +166,7 @@ export const onboardRouter = createTRPCRouter({ const results = await Promise.allSettled( dockerInstances.map(async ({ instance, host }) => { - const containerList = await instance.listContainers({ all: true }); + const containerList = await instance.listContainers({ all: false }); return containerList.filter((c) => !(dockerLabels.hide in c.Labels)).map((c) => ({ ...c, host })); }), ); @@ -188,11 +187,19 @@ export const onboardRouter = createTRPCRouter({ const cdnIconUrl = (slug: string) => `https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/${slug}.svg`; + const strictIconMatch = (imageName: string) => { + const normalized = imageName.toLowerCase().trim(); + if (normalized.length < 3) return null; + const exact = dbIcons.find((icon) => icon.name.toLowerCase() === normalized); + if (exact) return exact.url; + return dbIcons.find((icon) => icon.name.toLowerCase().startsWith(normalized))?.url ?? null; + }; + for (const container of containers) { const imageName = extractContainerImageName(container.Image); const containerName = container.Names[0]?.split("/")[1] ?? "Unknown"; - const iconUrl = - bestMatch(imageName, dbIcons, (icon) => icon.name)?.url ?? cdnIconUrl(imageName.toLowerCase()); + const dbIcon = strictIconMatch(imageName); + const iconUrl = dbIcon ?? cdnIconUrl(imageName.toLowerCase()); const { url: suggestedUrl, publishedPort } = buildSuggestedUrl(container.Ports, container.host); @@ -208,7 +215,7 @@ export const onboardRouter = createTRPCRouter({ publishedPort, iconUrl, }); - } else if (!kind) { + } else if (!kind && dbIcon) { discoveredApps.push({ containerId: container.Id, containerName,