mirror of
https://github.com/anomalyco/opencode.git
synced 2026-05-30 20:44:31 +00:00
fix(desktop-wsl): skip sidecar spawn when distro first-run is incomplete
Reading HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Lxss tells us the DefaultUid for every registered distro without touching wsl.exe. On a freshly installed Ubuntu-24.04 the 'Create a default UNIX user account' prompt never ran and DefaultUid stays 0; every wsl.exe -d <distro> ... invocation in that state silently blocks on stdin forever, even with --user root. checkWslDistroFirstRun reads the registry via powershell, and spawnWslSidecar throws a human-readable error if the distro still needs setup, so the controller marks the server as failed with a clear message instead of hanging. Also emits 'wsl sidecar starting' to main.log for visibility.
This commit is contained in:
parent
6fc5f342dd
commit
16ada93dd4
3 changed files with 78 additions and 1 deletions
|
|
@ -5,7 +5,7 @@ import { app } from "electron"
|
|||
import { DEFAULT_SERVER_URL_KEY } from "./constants"
|
||||
import { getUserShell, loadShellEnv } from "./shell-env"
|
||||
import { getStore } from "./store"
|
||||
import { type WslCommandLine, resolveWslOpencode, wslArgs } from "./wsl"
|
||||
import { checkWslDistroFirstRun, type WslCommandLine, resolveWslOpencode, wslArgs } from "./wsl"
|
||||
|
||||
export type HealthCheck = { wait: Promise<void> }
|
||||
|
||||
|
|
@ -83,6 +83,23 @@ export async function spawnWslSidecar(
|
|||
distro: string,
|
||||
opts: { onLine?: (line: WslCommandLine) => void; healthTimeoutMs?: number } = {},
|
||||
): Promise<WslSidecar> {
|
||||
// Gate on the registry state BEFORE any wsl.exe invocation. If the
|
||||
// distro still has DefaultUid=0 it means the interactive first-run
|
||||
// "Create a default UNIX user account" prompt never completed, and
|
||||
// every wsl.exe -d <distro> ... call will silently block on stdin
|
||||
// forever (we verified: Ubuntu-24.04 hangs on both -- echo and
|
||||
// --user root -- echo in this state). Fail fast with a clear message
|
||||
// so the controller can surface it to the user.
|
||||
const firstRun = await checkWslDistroFirstRun(distro)
|
||||
if (firstRun.status === "not-installed") {
|
||||
throw new Error(`WSL distro ${distro} is not installed`)
|
||||
}
|
||||
if (firstRun.status === "needs-first-run") {
|
||||
throw new Error(
|
||||
`WSL distro ${distro} has not completed first-run setup. Open a terminal and run 'wsl -d ${distro}' to create a default UNIX user, then retry.`,
|
||||
)
|
||||
}
|
||||
|
||||
const opencode = await resolveWslOpencode(distro)
|
||||
if (!opencode) throw new Error(`OpenCode is not installed in ${distro}`)
|
||||
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ export function createWslServersController(appVersion: string, spawnSidecar: Spa
|
|||
if (!item) return
|
||||
await stopServerInternal(id)
|
||||
setRuntime(id, { kind: "starting" })
|
||||
mainLogger?.log("wsl sidecar starting", { id, distro: item.config.distro })
|
||||
try {
|
||||
const sidecar = await spawnSidecar(item.config.distro)
|
||||
sidecars.set(id, sidecar)
|
||||
|
|
|
|||
|
|
@ -159,6 +159,65 @@ export function runWslInDistro(args: string[], distro?: string | null, opts?: Ru
|
|||
return runWsl(wslArgs(args, distro), opts)
|
||||
}
|
||||
|
||||
export type WslRegistryDistro = {
|
||||
name: string
|
||||
defaultUid: number
|
||||
state: number
|
||||
version: number
|
||||
}
|
||||
|
||||
// Distros that are designed to run as root and don't have a user-level
|
||||
// first-run setup. Ubuntu/Debian/Kali/etc. all run a first-boot hook that
|
||||
// prompts for a UNIX username on first invocation; if that never runs,
|
||||
// wsl.exe -d <distro> hangs silently forever.
|
||||
const ALWAYS_ROOT_DISTROS = new Set(["docker-desktop", "docker-desktop-data"])
|
||||
|
||||
// Read LXSS metadata from the Windows registry. This never invokes
|
||||
// wsl.exe, so it is safe to call when wsl.exe itself is wedged.
|
||||
// DefaultUid === 0 on a user-oriented distro means the first-run
|
||||
// "Create a default UNIX user account" step never completed.
|
||||
export async function readWslDistrosFromRegistry(opts?: RunWslOptions): Promise<WslRegistryDistro[]> {
|
||||
const script = [
|
||||
"$ErrorActionPreference = 'Stop'",
|
||||
"$out = @()",
|
||||
"Get-ChildItem 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Lxss' -ErrorAction SilentlyContinue | ForEach-Object {",
|
||||
" $name = $_.GetValue('DistributionName')",
|
||||
" if (-not $name) { return }",
|
||||
" $out += [PSCustomObject]@{",
|
||||
" name = $name",
|
||||
" defaultUid = [int]$_.GetValue('DefaultUid', 0)",
|
||||
" state = [int]$_.GetValue('State', 0)",
|
||||
" version = [int]$_.GetValue('Version', 0)",
|
||||
" }",
|
||||
"}",
|
||||
"$out | ConvertTo-Json -Compress",
|
||||
].join("; ")
|
||||
const result = await runPowerShell(script, opts)
|
||||
if (result.code !== 0) return []
|
||||
const text = result.stdout.trim()
|
||||
if (!text) return []
|
||||
try {
|
||||
const parsed = JSON.parse(text) as WslRegistryDistro | WslRegistryDistro[]
|
||||
return Array.isArray(parsed) ? parsed : [parsed]
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
export type WslFirstRunCheck =
|
||||
| { status: "ok" }
|
||||
| { status: "needs-first-run"; defaultUid: number }
|
||||
| { status: "not-installed" }
|
||||
|
||||
export async function checkWslDistroFirstRun(distro: string, opts?: RunWslOptions): Promise<WslFirstRunCheck> {
|
||||
const distros = await readWslDistrosFromRegistry(opts)
|
||||
const entry = distros.find((d) => d.name === distro)
|
||||
if (!entry) return { status: "not-installed" }
|
||||
if (ALWAYS_ROOT_DISTROS.has(entry.name)) return { status: "ok" }
|
||||
if (entry.defaultUid === 0) return { status: "needs-first-run", defaultUid: entry.defaultUid }
|
||||
return { status: "ok" }
|
||||
}
|
||||
|
||||
export function runWslSh(script: string, distro?: string | null, opts?: RunWslOptions) {
|
||||
return runWslInDistro(["sh", "-lc", script], distro, opts)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue