Skip to content

Commit 46face5

Browse files
authored
feat(main): fix and extend container engine check with rancher desktop dockerd (#634)
* feat(main): fix and extend container engine check * feat(ui): include Rancher Desktop in connection error message - Update error text to mention Docker, Podman, or Rancher Desktop - Add Rancher Desktop download link - Align UI messaging with backend detection capabilities - Improve user guidance for container engine options * feat: put link in a single row * feat: adjust and refactor for rancher dockerd * refactor: add dockerd on error message * refactor: remove timeout * refactor: remove rancher check that is not needed
1 parent 6e06133 commit 46face5

File tree

3 files changed

+93
-56
lines changed

3 files changed

+93
-56
lines changed

main/src/container-engine.ts

Lines changed: 77 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { exec } from 'node:child_process'
22
import { promisify } from 'node:util'
3+
import { platform } from 'node:os'
34
import log from './logger'
45

56
const execAsync = promisify(exec)
@@ -10,69 +11,96 @@ interface ContainerEngineStatus {
1011
available: boolean
1112
}
1213

13-
interface EngineCheckResult {
14-
name: string
15-
available: boolean
14+
// Common installation paths by platform
15+
const getCommonPaths = (): string[] => {
16+
const currentPlatform = platform()
17+
18+
switch (currentPlatform) {
19+
case 'darwin':
20+
return [
21+
'/Applications/Docker.app/Contents/Resources/bin',
22+
'/opt/homebrew/bin',
23+
'/usr/local/bin',
24+
'~/.rd/bin',
25+
]
26+
case 'linux':
27+
return ['/usr/local/bin', '/opt/homebrew/bin', '/snap/bin', '~/.rd/bin']
28+
case 'win32':
29+
return [
30+
'C:\\Program Files\\Docker\\Docker\\resources\\bin',
31+
'C:\\Program Files\\RedHat\\Podman',
32+
]
33+
default:
34+
return []
35+
}
1636
}
1737

18-
const createEngineResult = (
19-
name: string,
20-
available: boolean
21-
): EngineCheckResult => ({
22-
name,
23-
available,
24-
})
38+
const expandPath = (path: string): string =>
39+
path.startsWith('~')
40+
? path.replace('~', process.env.HOME || process.env.USERPROFILE || '')
41+
: path
2542

26-
const checkCommand = async (
27-
command: string,
28-
timeout = 5000
29-
): Promise<boolean> => {
43+
const createEnhancedPath = (): string => {
44+
const commonPaths = getCommonPaths().map(expandPath)
45+
const currentPath = process.env.PATH || ''
46+
const separator = platform() === 'win32' ? ';' : ':'
47+
48+
return [...commonPaths, ...currentPath.split(separator)]
49+
.filter(Boolean)
50+
.join(separator)
51+
}
52+
53+
const tryCommand = async (command: string): Promise<boolean> => {
3054
try {
31-
await execAsync(command, { timeout })
55+
await execAsync(command)
3256
return true
33-
} catch (error) {
34-
log.error(`container engine command failed: ${command}`, error)
35-
return false
57+
} catch {
58+
try {
59+
await execAsync(command, {
60+
env: { ...process.env, PATH: createEnhancedPath() },
61+
})
62+
return true
63+
} catch {
64+
return false
65+
}
3666
}
3767
}
3868

39-
// Individual engine checkers
40-
const checkDocker = async (): Promise<EngineCheckResult> => {
41-
// docker ps requires daemon to be running and accessible
42-
const available = await checkCommand('docker ps')
43-
return createEngineResult('docker', available)
44-
}
45-
46-
const checkPodman = async (): Promise<EngineCheckResult> => {
47-
// podman ps checks if podman can actually manage containers
48-
const available = await checkCommand('podman ps')
49-
return createEngineResult('podman', available)
50-
}
69+
const getCommandName = (base: string): string =>
70+
platform() === 'win32' ? `${base}.exe` : base
5171

52-
const combineEngineResults = (
53-
results: EngineCheckResult[]
54-
): ContainerEngineStatus => {
55-
const dockerResult = results.find((r) => r.name === 'docker')
56-
const podmanResult = results.find((r) => r.name === 'podman')
72+
const checkDocker = (): Promise<boolean> =>
73+
tryCommand(`${getCommandName('docker')} ps`)
5774

58-
const docker = dockerResult?.available ?? false
59-
const podman = podmanResult?.available ?? false
75+
const checkPodman = (): Promise<boolean> =>
76+
tryCommand(`${getCommandName('podman')} ps`)
6077

61-
return {
62-
docker,
63-
podman,
64-
available: docker || podman,
65-
}
66-
}
78+
const createStatus = (
79+
docker: boolean,
80+
podman: boolean
81+
): ContainerEngineStatus => ({
82+
docker,
83+
podman,
84+
available: docker || podman,
85+
})
6786

68-
// Main function - orchestrates the checks
6987
export const checkContainerEngine =
7088
async (): Promise<ContainerEngineStatus> => {
71-
const engineCheckers = [checkDocker, checkPodman]
72-
73-
const results = await Promise.all(
74-
engineCheckers.map((checker) => checker())
89+
const [docker, podman] = await Promise.allSettled([
90+
checkDocker(),
91+
checkPodman(),
92+
]).then((results) =>
93+
results.map((result) => {
94+
if (result.status === 'rejected') return false
95+
return result.value
96+
})
7597
)
7698

77-
return combineEngineResults(results)
99+
const status = createStatus(!!docker, !!podman)
100+
101+
if (!status.available) {
102+
log.warn('No container engines detected')
103+
}
104+
105+
return status
78106
}

preload/src/preload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export interface ElectronAPI {
118118
checkContainerEngine: () => Promise<{
119119
docker: boolean
120120
podman: boolean
121+
rancherDesktop: boolean
121122
available: boolean
122123
}>
123124
restartToolhive: () => Promise<{

renderer/src/common/components/error/connection-refused-error.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -125,27 +125,35 @@ export function ConnectionRefusedError() {
125125
icon={<IllustrationPackage className="size-20" />}
126126
>
127127
<p>
128-
ToolHive requires either <strong>Docker</strong> or{' '}
129-
<strong>Podman</strong> to be installed and running to manage
130-
containerized tools and services.
128+
ToolHive requires a container engine to be installed and running to
129+
manage containerized tools and services. We support{' '}
130+
<strong>Docker</strong>, <strong>Podman</strong> or{' '}
131+
<strong>Rancher Desktop</strong> (with dockerd - moby).
131132
</p>
132133

133134
<div className="bg-muted rounded-md p-3 text-sm">
134135
<p className="mb-2 font-medium">To get started:</p>
135136
<ol className="list-inside list-decimal space-y-1">
136-
<li>Install Docker Desktop or Podman Desktop</li>
137+
<li>
138+
Install Docker Desktop, Podman Desktop or Rancher Desktop (with
139+
dockerd - moby)
140+
</li>
137141
<li>Start the container engine</li>
138142
<li>Click "Try Again" to continue</li>
139143
</ol>
140144
</div>
141145

142-
<div className="space-y-2 pb-4">
146+
<div className="grid grid-cols-1 gap-2 pb-4 md:grid-cols-3">
143147
<ExternalLinkButton href="https://docs.docker.com/get-docker/">
144-
Install Docker Desktop
148+
Docker
145149
</ExternalLinkButton>
146150

147151
<ExternalLinkButton href="https://podman-desktop.io/downloads">
148-
Install Podman Desktop
152+
Podman
153+
</ExternalLinkButton>
154+
155+
<ExternalLinkButton href="https://rancherdesktop.io/">
156+
Rancher
149157
</ExternalLinkButton>
150158
</div>
151159
</BaseErrorScreen>

0 commit comments

Comments
 (0)