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 { field, Level, logger } from "@coder/logger"
import * as crypto from "crypto"
import { promises as fs } from "fs" import { promises as fs } from "fs"
import { load } from "js-yaml" import { load } from "js-yaml"
import * as path from "path" import * as path from "path"
@@ -561,7 +562,7 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
} }
if (!args["session-socket"]) { 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"] 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. } 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[] { export function getResolvedPathsFromArgs(args: UserProvidedArgs): string[] {
return (args._ ?? []).map((p) => path.resolve(p)) return (args._ ?? []).map((p) => path.resolve(p))
} }

View File

@@ -5,6 +5,7 @@ import {
UserProvidedArgs, UserProvidedArgs,
bindAddrFromArgs, bindAddrFromArgs,
defaultConfigFile, defaultConfigFile,
defaultSessionSocket,
parse, parse,
parseConfigFile, parseConfigFile,
setDefaults, setDefaults,
@@ -37,7 +38,7 @@ const defaults = {
usingEnvHashedPassword: false, usingEnvHashedPassword: false,
"extensions-dir": path.join(paths.data, "extensions"), "extensions-dir": path.join(paths.data, "extensions"),
"user-data-dir": paths.data, "user-data-dir": paths.data,
"session-socket": path.join(paths.data, "code-server-ipc.sock"), "session-socket": defaultSessionSocket(paths.data),
"app-name": "code-server", "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", () => { describe("defaultConfigFile", () => {
it("should return the default config file as a string", async () => { it("should return the default config file as a string", async () => {
const password = await generatePassword() const password = await generatePassword()