fix(desktop): harden v2 daemon startup
This commit is contained in:
parent
e8326c70e6
commit
e96d0b3426
4 changed files with 45 additions and 37 deletions
|
|
@ -72,17 +72,23 @@ export function getCurrentCli(target = RUST_TARGET ?? nativeTarget()) {
|
||||||
export async function downloadCliToResources() {
|
export async function downloadCliToResources() {
|
||||||
const cli = getCurrentCli()
|
const cli = getCurrentCli()
|
||||||
const directory = await mkdtemp(join(tmpdir(), "opencode-cli-"))
|
const directory = await mkdtemp(join(tmpdir(), "opencode-cli-"))
|
||||||
await $`bun install --no-save --cwd ${directory} ${`${cli.package}@${CLI_VERSION}`} ${`--os=${cli.os}`} ${`--cpu=${cli.cpu}`}`
|
|
||||||
const source = join(directory, "node_modules", cli.package, "bin", cli.os === "win32" ? "opencode2.exe" : "opencode2")
|
|
||||||
const dest = windowsify("resources/opencode-cli")
|
const dest = windowsify("resources/opencode-cli")
|
||||||
await copyFile(source, dest).finally(() => rm(directory, { recursive: true, force: true }))
|
try {
|
||||||
|
await $`bun install --no-save --cwd ${directory} ${`${cli.package}@${CLI_VERSION}`} ${`--os=${cli.os}`} ${`--cpu=${cli.cpu}`}`
|
||||||
|
await copyFile(
|
||||||
|
join(directory, "node_modules", cli.package, "bin", cli.os === "win32" ? "opencode2.exe" : "opencode2"),
|
||||||
|
dest,
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
await rm(directory, { recursive: true, force: true })
|
||||||
|
}
|
||||||
if (process.platform !== "win32") await chmod(dest, 0o755)
|
if (process.platform !== "win32") await chmod(dest, 0o755)
|
||||||
if (process.platform === "win32" && process.env.GITHUB_ACTIONS === "true") {
|
if (process.platform === "win32" && process.env.GITHUB_ACTIONS === "true") {
|
||||||
await $`pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File ../../script/sign-windows.ps1 ${dest}`
|
await $`pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File ../../script/sign-windows.ps1 ${dest}`
|
||||||
}
|
}
|
||||||
if (process.platform === "darwin") await $`codesign --force --sign - ${dest}`
|
if (process.platform === "darwin") await $`codesign --force --sign - ${dest}`
|
||||||
|
|
||||||
console.log(`Copied ${source} to ${dest}`)
|
console.log(`Copied ${cli.package} to ${dest}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function windowsify(path: string) {
|
export function windowsify(path: string) {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ type Logger = {
|
||||||
error(message: string, meta?: Record<string, unknown>): void
|
error(message: string, meta?: Record<string, unknown>): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startBackgroundCli(logger: Logger) {
|
export async function startBackgroundCli(logger: Logger, shellStateHome?: string) {
|
||||||
const bundled = app.isPackaged
|
const bundled = app.isPackaged
|
||||||
? join(process.resourcesPath, executableName())
|
? join(process.resourcesPath, executableName())
|
||||||
: join(root, "../../resources", executableName())
|
: join(root, "../../resources", executableName())
|
||||||
|
|
@ -25,7 +25,7 @@ export async function startBackgroundCli(logger: Logger) {
|
||||||
const binary = app.isPackaged ? await installCli(bundled, version, logger) : bundled
|
const binary = app.isPackaged ? await installCli(bundled, version, logger) : bundled
|
||||||
|
|
||||||
const candidates = [
|
const candidates = [
|
||||||
...new Set([stateHome, ...desktopStateNames.map((name) => join(app.getPath("appData"), name))]),
|
...new Set([stateHome, shellStateHome, ...desktopStateNames.map((name) => join(app.getPath("appData"), name))]),
|
||||||
].filter((candidate) => candidate === undefined || existsSync(candidate))
|
].filter((candidate) => candidate === undefined || existsSync(candidate))
|
||||||
const discovered = await Promise.all(
|
const discovered = await Promise.all(
|
||||||
candidates.map(async (candidate) => ({
|
candidates.map(async (candidate) => ({
|
||||||
|
|
@ -40,7 +40,7 @@ export async function startBackgroundCli(logger: Logger) {
|
||||||
})
|
})
|
||||||
|
|
||||||
const daemonStateHome = found?.stateHome ?? stateHome
|
const daemonStateHome = found?.stateHome ?? stateHome
|
||||||
const url = found?.url ?? (await run(binary, ["service", "start"], logger, { stateHome: daemonStateHome }))
|
const url = await run(binary, ["service", "start"], logger, { stateHome: daemonStateHome })
|
||||||
const password = await run(binary, ["service", "get", "password"], logger, {
|
const password = await run(binary, ["service", "get", "password"], logger, {
|
||||||
redact: true,
|
redact: true,
|
||||||
stateHome: daemonStateHome,
|
stateHome: daemonStateHome,
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ const main = Effect.gen(function* () {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
preferAppEnv(app.getPath("userData"))
|
const shellEnv = preferAppEnv(app.getPath("userData"))
|
||||||
|
|
||||||
app.on("second-instance", (_event: Event, argv: string[]) => {
|
app.on("second-instance", (_event: Event, argv: string[]) => {
|
||||||
const urls = argv.filter((arg: string) => arg.startsWith("opencode://"))
|
const urls = argv.filter((arg: string) => arg.startsWith("opencode://"))
|
||||||
|
|
@ -315,33 +315,6 @@ const main = Effect.gen(function* () {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const port = yield* Effect.gen(function* () {
|
|
||||||
const fromEnv = process.env.OPENCODE_PORT
|
|
||||||
if (fromEnv) {
|
|
||||||
const parsed = Number.parseInt(fromEnv, 10)
|
|
||||||
if (!Number.isNaN(parsed)) return parsed
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = yield* Deferred.make<number, unknown>()
|
|
||||||
const server = createServer()
|
|
||||||
server.on("error", (e) => Deferred.failSync(res, () => e))
|
|
||||||
server.listen(0, "127.0.0.1", () => {
|
|
||||||
const address = server.address()
|
|
||||||
if (typeof address !== "object" || !address) {
|
|
||||||
server.close()
|
|
||||||
Deferred.failSync(res, () => new Error("Failed to get port"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const port = address.port
|
|
||||||
server.close(() => Effect.runSync(Deferred.succeed(res, port)))
|
|
||||||
})
|
|
||||||
|
|
||||||
return yield* Deferred.await(res)
|
|
||||||
})
|
|
||||||
const hostname = "127.0.0.1"
|
|
||||||
const url = `http://${hostname}:${port}`
|
|
||||||
const password = randomUUID()
|
|
||||||
|
|
||||||
const loadingTask = yield* Effect.gen(function* () {
|
const loadingTask = yield* Effect.gen(function* () {
|
||||||
logger.log("sidecar connection started", { version: SIDECAR_VERSION })
|
logger.log("sidecar connection started", { version: SIDECAR_VERSION })
|
||||||
|
|
||||||
|
|
@ -350,7 +323,7 @@ const main = Effect.gen(function* () {
|
||||||
|
|
||||||
if (SIDECAR_VERSION === "v2") {
|
if (SIDECAR_VERSION === "v2") {
|
||||||
logger.log("spawning v2 sidecar")
|
logger.log("spawning v2 sidecar")
|
||||||
const sidecar = yield* Effect.promise(() => startBackgroundCli(logger))
|
const sidecar = yield* Effect.promise(() => startBackgroundCli(logger, shellEnv?.XDG_STATE_HOME))
|
||||||
yield* Deferred.succeed(serverReady, {
|
yield* Deferred.succeed(serverReady, {
|
||||||
url: sidecar.url,
|
url: sidecar.url,
|
||||||
username: sidecar.username,
|
username: sidecar.username,
|
||||||
|
|
@ -365,6 +338,33 @@ const main = Effect.gen(function* () {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const port = yield* Effect.gen(function* () {
|
||||||
|
const fromEnv = process.env.OPENCODE_PORT
|
||||||
|
if (fromEnv) {
|
||||||
|
const parsed = Number.parseInt(fromEnv, 10)
|
||||||
|
if (!Number.isNaN(parsed)) return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = yield* Deferred.make<number, unknown>()
|
||||||
|
const socket = createServer()
|
||||||
|
socket.on("error", (e) => Deferred.failSync(res, () => e))
|
||||||
|
socket.listen(0, "127.0.0.1", () => {
|
||||||
|
const address = socket.address()
|
||||||
|
if (typeof address !== "object" || !address) {
|
||||||
|
socket.close()
|
||||||
|
Deferred.failSync(res, () => new Error("Failed to get port"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const port = address.port
|
||||||
|
socket.close(() => Effect.runSync(Deferred.succeed(res, port)))
|
||||||
|
})
|
||||||
|
|
||||||
|
return yield* Deferred.await(res)
|
||||||
|
})
|
||||||
|
const hostname = "127.0.0.1"
|
||||||
|
const url = `http://${hostname}:${port}`
|
||||||
|
const password = randomUUID()
|
||||||
|
|
||||||
logger.log("spawning sidecar", { url })
|
logger.log("spawning sidecar", { url })
|
||||||
const { listener, health } = yield* Effect.promise(() =>
|
const { listener, health } = yield* Effect.promise(() =>
|
||||||
spawnLocalServer(hostname, port, password, {
|
spawnLocalServer(hostname, port, password, {
|
||||||
|
|
|
||||||
|
|
@ -43,13 +43,15 @@ export function setDefaultServerUrl(url: string | null) {
|
||||||
|
|
||||||
export function preferAppEnv(userDataPath: string) {
|
export function preferAppEnv(userDataPath: string) {
|
||||||
const shell = process.platform === "win32" ? null : getUserShell()
|
const shell = process.platform === "win32" ? null : getUserShell()
|
||||||
|
const shellEnv = shell ? loadShellEnv(shell, getLogger()) : null
|
||||||
Object.assign(process.env, {
|
Object.assign(process.env, {
|
||||||
...(shell ? loadShellEnv(shell, getLogger()) : null),
|
...shellEnv,
|
||||||
OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "true",
|
OPENCODE_EXPERIMENTAL_ICON_DISCOVERY: "true",
|
||||||
OPENCODE_EXPERIMENTAL_FILEWATCHER: "true",
|
OPENCODE_EXPERIMENTAL_FILEWATCHER: "true",
|
||||||
OPENCODE_CLIENT: "desktop",
|
OPENCODE_CLIENT: "desktop",
|
||||||
XDG_STATE_HOME: process.env.XDG_STATE_HOME ?? userDataPath,
|
XDG_STATE_HOME: process.env.XDG_STATE_HOME ?? userDataPath,
|
||||||
})
|
})
|
||||||
|
return shellEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function spawnLocalServer(
|
export async function spawnLocalServer(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue