| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -41,6 +41,7 @@ | |||
| 41 | 41 | - [How do I disable the proxy?](#how-do-i-disable-the-proxy) | |
| 42 | 42 | - [How do I disable file download?](#how-do-i-disable-file-download) | |
| 43 | 43 | - [Why do web views not work?](#why-do-web-views-not-work) | |
| 44 | + - [Can I run code-server with systemd socket activation?](#can-i-run-code-server-with-systemd-socket-activation) | ||
| 44 | 45 | ||
| 45 | 46 | <!-- END doctoc generated TOC please keep comment here to allow auto update --> | |
| 46 | 47 | <!-- prettier-ignore-end --> | |
@@ -560,3 +561,41 @@ To fix this, you must either: | |||
| 560 | 561 | create and trust a certificate manually). | |
| 561 | 562 | - Disable security if your browser allows it. For example, in Chromium see | |
| 562 | 563 | `chrome://flags/#unsafely-treat-insecure-origin-as-secure` | |
| 564 | + | ||
| 565 | + ## Can I run code-server with systemd socket activation? | ||
| 566 | + | ||
| 567 | + Yes. Pass the inherited socket to code-server with `--socket-fd`. systemd | ||
| 568 | + passes the first listening socket as file descriptor `3`. | ||
| 569 | + | ||
| 570 | + Create a socket unit, `~/.config/systemd/user/code-server.socket`: | ||
| 571 | + | ||
| 572 | + ```ini | ||
| 573 | + [Socket] | ||
| 574 | + ListenStream=8080 | ||
| 575 | + | ||
| 576 | + [Install] | ||
| 577 | + WantedBy=sockets.target | ||
| 578 | + ``` | ||
| 579 | + | ||
| 580 | + And a matching service unit, `~/.config/systemd/user/code-server.service`: | ||
| 581 | + | ||
| 582 | + ```ini | ||
| 583 | + [Service] | ||
| 584 | + ExecStart=/usr/bin/code-server --socket-fd 3 | ||
| 585 | + ``` | ||
| 586 | + | ||
| 587 | + Then enable and start the socket: | ||
| 588 | + | ||
| 589 | + ```bash | ||
| 590 | + systemctl --user enable --now code-server.socket | ||
| 591 | + ``` | ||
| 592 | + | ||
| 593 | + code-server will start on the first connection and listen on the socket | ||
| 594 | + systemd created. `--socket-fd` takes precedence over `--socket` and | ||
| 595 | + `--bind-addr`/`--port`/`--host`, and `--socket-mode` is ignored because | ||
| 596 | + systemd owns the socket's permissions. | ||
| 597 | + | ||
| 598 | + Socket activation only changes how code-server binds; your usual | ||
| 599 | + authentication still applies (it keeps prompting for the configured password | ||
| 600 | + unless you set `--auth none`), so keep authentication enabled when exposing the | ||
| 601 | + server. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,7 +13,8 @@ import { EditorSessionManager, makeEditorSessionManagerServer } from "./vscodeSo | |||
| 13 | 13 | import { handleUpgrade } from "./wsRouter" | |
| 14 | 14 | ||
| 15 | 15 | type SocketOptions = { socket: string; "socket-mode"?: string } | |
| 16 | - type ListenOptions = DefaultedArgs | SocketOptions | ||
| 16 | + type FdOptions = { "socket-fd": number } | ||
| 17 | + type ListenOptions = DefaultedArgs | SocketOptions | FdOptions | ||
| 17 | 18 | ||
| 18 | 19 | export interface App extends Disposable { | |
| 19 | 20 | /** Handles regular HTTP requests. */ | |
@@ -30,8 +31,12 @@ const isSocketOpts = (opts: ListenOptions): opts is SocketOptions => { | |||
| 30 | 31 | return !!(opts as SocketOptions).socket || !(opts as DefaultedArgs).host | |
| 31 | 32 | } | |
| 32 | 33 | ||
| 34 | + export const isFdOpts = (opts: ListenOptions): opts is FdOptions => { | ||
| 35 | + return typeof (opts as FdOptions)["socket-fd"] === "number" | ||
| 36 | + } | ||
| 37 | + | ||
| 33 | 38 | export const listen = async (server: http.Server, opts: ListenOptions) => { | |
| 34 | - if (isSocketOpts(opts)) { | ||
| 39 | + if (!isFdOpts(opts) && isSocketOpts(opts)) { | ||
| 35 | 40 | try { | |
| 36 | 41 | await fs.unlink(opts.socket) | |
| 37 | 42 | } catch (error: any) { | |
@@ -46,7 +51,9 @@ export const listen = async (server: http.Server, opts: ListenOptions) => { | |||
| 46 | 51 | server.on("error", (err) => util.logError(logger, "http server error", err)) | |
| 47 | 52 | resolve() | |
| 48 | 53 | } | |
| 49 | - if (isSocketOpts(opts)) { | ||
| 54 | + if (isFdOpts(opts)) { | ||
| 55 | + server.listen({ fd: opts["socket-fd"] }, onListen) | ||
| 56 | + } else if (isSocketOpts(opts)) { | ||
| 50 | 57 | server.listen(opts.socket, onListen) | |
| 51 | 58 | } else { | |
| 52 | 59 | // [] is the correct format when using :: but Node errors with them. | |
@@ -56,7 +63,7 @@ export const listen = async (server: http.Server, opts: ListenOptions) => { | |||
| 56 | 63 | ||
| 57 | 64 | // NOTE@jsjoeio: we need to chmod after the server is finished | |
| 58 | 65 | // listening. Otherwise, the socket may not have been created yet. | |
| 59 | - if (isSocketOpts(opts)) { | ||
| 66 | + if (!isFdOpts(opts) && isSocketOpts(opts)) { | ||
| 60 | 67 | if (opts["socket-mode"]) { | |
| 61 | 68 | await fs.chmod(opts.socket, opts["socket-mode"]) | |
| 62 | 69 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -83,6 +83,7 @@ export interface UserProvidedArgs extends UserProvidedCodeArgs { | |||
| 83 | 83 | open?: boolean | |
| 84 | 84 | "bind-addr"?: string | |
| 85 | 85 | socket?: string | |
| 86 | + "socket-fd"?: number | ||
| 86 | 87 | "socket-mode"?: string | |
| 87 | 88 | "trusted-origins"?: string[] | |
| 88 | 89 | version?: boolean | |
@@ -236,6 +237,10 @@ export const options: Options<Required<UserProvidedArgs>> = { | |||
| 236 | 237 | port: { type: "number", description: "" }, | |
| 237 | 238 | ||
| 238 | 239 | socket: { type: "string", path: true, description: "Path to a socket (bind-addr will be ignored)." }, | |
| 240 | + "socket-fd": { | ||
| 241 | + type: "number", | ||
| 242 | + description: "File descriptor of a pre-bound, listening socket to use (for systemd socket activation).", | ||
| 243 | + }, | ||
| 239 | 244 | "socket-mode": { type: "string", description: "File mode of the socket." }, | |
| 240 | 245 | "trusted-origins": { | |
| 241 | 246 | type: "string[]", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -3,7 +3,7 @@ import { promises } from "fs" | |||
| 3 | 3 | import * as http from "http" | |
| 4 | 4 | import * as https from "https" | |
| 5 | 5 | import * as path from "path" | |
| 6 | - import { createApp, ensureAddress, handleArgsSocketCatchError, listen } from "../../../src/node/app" | ||
| 6 | + import { createApp, ensureAddress, handleArgsSocketCatchError, isFdOpts, listen } from "../../../src/node/app" | ||
| 7 | 7 | import { OptionalString, setDefaults } from "../../../src/node/cli" | |
| 8 | 8 | import { generateCertificate } from "../../../src/node/util" | |
| 9 | 9 | import { clean, mockLogger, getAvailablePort, tmpdir } from "../../utils/helpers" | |
@@ -261,3 +261,47 @@ describe("listen", () => { | |||
| 261 | 261 | } | |
| 262 | 262 | }) | |
| 263 | 263 | }) | |
| 264 | + | ||
| 265 | + describe("listen (socket-fd)", () => { | ||
| 266 | + // Wrap a bound-but-not-yet-listening TCP socket so we get a real file | ||
| 267 | + // descriptor that listen({ fd }) can adopt, mirroring the systemd socket | ||
| 268 | + // activation case where the process inherits an fd and calls listen(2) on it. | ||
| 269 | + // Using a live net.Server's fd instead fails with EEXIST because the socket | ||
| 270 | + // is already listening in-process. | ||
| 271 | + // eslint-disable-next-line @typescript-eslint/no-var-requires | ||
| 272 | + const { TCP, constants: TCPConstants } = (process as any).binding("tcp_wrap") | ||
| 273 | + | ||
| 274 | + let inherited: any | ||
| 275 | + let httpServer: http.Server | ||
| 276 | + let unlinkSpy: jest.SpyInstance | ||
| 277 | + | ||
| 278 | + beforeEach(async () => { | ||
| 279 | + mockLogger() | ||
| 280 | + unlinkSpy = jest.spyOn(promises, "unlink") | ||
| 281 | + inherited = new TCP(TCPConstants.SERVER) | ||
| 282 | + inherited.bind("127.0.0.1", 0) | ||
| 283 | + httpServer = http.createServer() | ||
| 284 | + }) | ||
| 285 | + | ||
| 286 | + afterEach(() => { | ||
| 287 | + httpServer.close() | ||
| 288 | + try { | ||
| 289 | + inherited.close() | ||
| 290 | + } catch { | ||
| 291 | + // The fd is adopted by httpServer.close() above; ignore double-close. | ||
| 292 | + } | ||
| 293 | + jest.clearAllMocks() | ||
| 294 | + }) | ||
| 295 | + | ||
| 296 | + it("isFdOpts detects a numeric socket-fd", () => { | ||
| 297 | + expect(isFdOpts({ "socket-fd": 3 })).toBe(true) | ||
| 298 | + expect(isFdOpts({ socket: "/tmp/x.sock" } as any)).toBe(false) | ||
| 299 | + }) | ||
| 300 | + | ||
| 301 | + it("listens on an inherited fd without unlinking", async () => { | ||
| 302 | + const fd = inherited.fd as number | ||
| 303 | + await listen(httpServer, { "socket-fd": fd }) | ||
| 304 | + expect(httpServer.address()).not.toBeNull() | ||
| 305 | + expect(unlinkSpy).not.toHaveBeenCalled() | ||
| 306 | + }) | ||
| 307 | + }) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments