Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions scripts/setup/docker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { spawnSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import { SetupError } from './errors.ts'
import { waitFor } from './probes.ts'
import * as p from './prompter.ts'
Expand All @@ -9,6 +10,10 @@ const INSTALL_HINTS = [
`or OrbStack (lighter on macOS): ${theme.command('brew install orbstack')}`,
]

/** macOS GUI docker providers we know how to launch via `open -a`. */
const ORBSTACK_APP = { name: 'OrbStack', path: '/Applications/OrbStack.app' } as const
const DOCKER_DESKTOP_APP = { name: 'Docker', path: '/Applications/Docker.app' } as const

function daemonUp(): boolean {
return spawnSync('docker', ['info'], { stdio: 'ignore' }).status === 0
}
Expand All @@ -19,6 +24,26 @@ function installed(): boolean {
return Bun.which('docker') !== null
}

function currentDockerContext(): string | null {
const result = spawnSync('docker', ['context', 'show'], { encoding: 'utf8' })
return result.status === 0 ? result.stdout.trim() : null
}

/**
* Which GUI app owns the `docker` CLI on this Mac. Docker Desktop and OrbStack
* both install a `docker` binary, so presence of the CLI alone doesn't tell us
* which app to relaunch. Prefer the docker CLI's own active context — it's
* accurate regardless of where the app bundle lives, and authoritative when
* both apps are installed but only one is the active context. Only fall back
* to checking the well-known `.app` install path when the context command
* gives no answer at all.
*/
function macDockerApp(): typeof ORBSTACK_APP | typeof DOCKER_DESKTOP_APP {
const context = currentDockerContext()
if (context !== null) return context === 'orbstack' ? ORBSTACK_APP : DOCKER_DESKTOP_APP
return existsSync(ORBSTACK_APP.path) ? ORBSTACK_APP : DOCKER_DESKTOP_APP
}
Comment thread
cursor[bot] marked this conversation as resolved.

/**
* Returns whether the Docker daemon is available, offering to launch Docker
* Desktop (macOS) when it's installed but stopped. Never installs anything.
Expand All @@ -41,27 +66,31 @@ export async function ensureDocker(required: boolean): Promise<boolean> {
return false
}

const app = macDockerApp()

const launch = await p.confirm({
message: 'Docker is installed but not running — start Docker Desktop now?',
message: `Docker is installed but not running — start ${app.name} now?`,
initialValue: true,
})
if (!launch) {
if (required) {
throw new SetupError('Docker is required for this mode.', [
'start Docker Desktop, then re-run the wizard',
`start ${app.name}, then re-run the wizard`,
])
}
return false
}

spawnSync('open', ['-a', 'Docker'], { stdio: 'ignore' })
spawnSync('open', ['-a', app.name], { stdio: 'ignore' })
const spin = p.spinner()
spin.start('Waiting for the Docker daemon…')
spin.start(`Waiting for the Docker daemon (${app.name})…`)
const up = await waitFor(async () => daemonUp(), 90_000, 2000)
spin.stop(up ? 'Docker is running' : `${glyph.fail} daemon did not come up`)
if (!up) {
throw new SetupError('Docker Desktop did not start within 90s.', [
'first-ever launch needs a GUI license acceptance — open Docker Desktop manually once, then re-run',
throw new SetupError(`${app.name} did not start within 90s.`, [
app === ORBSTACK_APP
? 'open OrbStack manually once to finish its first-run setup, then re-run'
: 'first-ever launch needs a GUI license acceptance — open Docker Desktop manually once, then re-run',
])
}
return true
Expand Down