Default the session socket to a named pipe on windows (#7985)

This commit is contained in:
denusklo
2026-09-09 04:37:51 +08:00
committed by GitHub
parent 8c8476eb76
commit f1dde9b093
2 changed files with 43 additions and 2 deletions

View File

@@ -1,4 +1,5 @@
import { field, Level, logger } from "@coder/logger"
import * as crypto from "crypto"
import { promises as fs } from "fs"
import { load } from "js-yaml"
import * as path from "path"
@@ -561,7 +562,7 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
}
if (!args["session-socket"]) {
args["session-socket"] = path.join(args["user-data-dir"], "code-server-ipc.sock")
args["session-socket"] = defaultSessionSocket(args["user-data-dir"])
}
process.env.CODE_SERVER_SESSION_SOCKET = args["session-socket"]
@@ -710,6 +711,25 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
} as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
}
/**
* The session socket to use when one was not given.
*
* Windows has no Unix sockets, so there it is a named pipe, which lives in its
* own namespace rather than on disk and so cannot be placed inside the user
* data directory. The name is derived from that directory anyway, so that two
* instances with separate data directories do not collide and a later
* invocation with the same one finds the first. Windows paths are compared
* without regard to case, so the name is folded before it is hashed; otherwise
* the same directory typed two ways would produce two pipes.
*/
export function defaultSessionSocket(userDataDir: string, platform: NodeJS.Platform = process.platform): string {
if (platform !== "win32") {
return path.join(userDataDir, "code-server-ipc.sock")
}
const name = crypto.createHash("sha256").update(path.resolve(userDataDir).toLowerCase()).digest("hex").slice(0, 16)
return String.raw`\\.\pipe\code-server-ipc-${name}`
}
export function getResolvedPathsFromArgs(args: UserProvidedArgs): string[] {
return (args._ ?? []).map((p) => path.resolve(p))
}

View File

@@ -5,6 +5,7 @@ import {
UserProvidedArgs,
bindAddrFromArgs,
defaultConfigFile,
defaultSessionSocket,
parse,
parseConfigFile,
setDefaults,
@@ -37,7 +38,7 @@ const defaults = {
usingEnvHashedPassword: false,
"extensions-dir": path.join(paths.data, "extensions"),
"user-data-dir": paths.data,
"session-socket": path.join(paths.data, "code-server-ipc.sock"),
"session-socket": defaultSessionSocket(paths.data),
"app-name": "code-server",
_: [],
}
@@ -977,6 +978,26 @@ describe("bindAddrFromArgs", () => {
})
})
describe("defaultSessionSocket", () => {
const dataDir = path.join("/home/coder/.local/share", "code-server")
it("should put the socket in the user data directory", () => {
expect(defaultSessionSocket(dataDir, "linux")).toBe(path.join(dataDir, "code-server-ipc.sock"))
})
it("should use a named pipe on windows", () => {
expect(defaultSessionSocket(dataDir, "win32")).toMatch(/^\\\\\.\\pipe\\code-server-ipc-[0-9a-f]{16}$/)
})
it("should give separate data directories separate pipes", () => {
expect(defaultSessionSocket(dataDir, "win32")).not.toBe(defaultSessionSocket(dataDir + "-other", "win32"))
})
it("should give one data directory one pipe however it is spelled", () => {
expect(defaultSessionSocket(dataDir.toUpperCase(), "win32")).toBe(defaultSessionSocket(dataDir, "win32"))
})
})
describe("defaultConfigFile", () => {
it("should return the default config file as a string", async () => {
const password = await generatePassword()