mirror of
https://github.com/coder/code-server.git
synced 2026-05-10 14:27:26 +02:00
Compare commits
4 Commits
v4.112.0
...
ada3489acf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ada3489acf | ||
|
|
4d615f18a9 | ||
|
|
d544846caa | ||
|
|
44fc46316f |
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:experimental
|
# syntax=docker/dockerfile:experimental
|
||||||
|
|
||||||
ARG BASE=debian:12
|
ARG BASE=debian:13
|
||||||
FROM scratch AS packages
|
FROM scratch AS packages
|
||||||
COPY release-packages/code-server*.deb /tmp/
|
COPY release-packages/code-server*.deb /tmp/
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ variable "GITHUB_REGISTRY" {
|
|||||||
|
|
||||||
group "default" {
|
group "default" {
|
||||||
targets = [
|
targets = [
|
||||||
|
"code-server-debian-13",
|
||||||
"code-server-debian-12",
|
"code-server-debian-12",
|
||||||
"code-server-ubuntu-focal",
|
"code-server-ubuntu-focal",
|
||||||
"code-server-ubuntu-noble",
|
"code-server-ubuntu-noble",
|
||||||
@@ -48,13 +49,24 @@ function "gen_tags_for_docker_and_ghcr" {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
target "code-server-debian-12" {
|
target "code-server-debian-13" {
|
||||||
dockerfile = "ci/release-image/Dockerfile"
|
dockerfile = "ci/release-image/Dockerfile"
|
||||||
tags = concat(
|
tags = concat(
|
||||||
gen_tags_for_docker_and_ghcr(""),
|
gen_tags_for_docker_and_ghcr(""),
|
||||||
gen_tags_for_docker_and_ghcr("debian"),
|
gen_tags_for_docker_and_ghcr("debian"),
|
||||||
|
gen_tags_for_docker_and_ghcr("trixie"),
|
||||||
|
)
|
||||||
|
platforms = ["linux/amd64", "linux/arm64"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "code-server-debian-12" {
|
||||||
|
dockerfile = "ci/release-image/Dockerfile"
|
||||||
|
tags = concat(
|
||||||
gen_tags_for_docker_and_ghcr("bookworm"),
|
gen_tags_for_docker_and_ghcr("bookworm"),
|
||||||
)
|
)
|
||||||
|
args = {
|
||||||
|
BASE = "debian:12"
|
||||||
|
}
|
||||||
platforms = ["linux/amd64", "linux/arm64"]
|
platforms = ["linux/amd64", "linux/arm64"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
docs/FAQ.md
11
docs/FAQ.md
@@ -22,6 +22,7 @@
|
|||||||
- [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server)
|
- [How do I debug issues with code-server?](#how-do-i-debug-issues-with-code-server)
|
||||||
- [What is the healthz endpoint?](#what-is-the-healthz-endpoint)
|
- [What is the healthz endpoint?](#what-is-the-healthz-endpoint)
|
||||||
- [What is the heartbeat file?](#what-is-the-heartbeat-file)
|
- [What is the heartbeat file?](#what-is-the-heartbeat-file)
|
||||||
|
- [How do I change the reconnection grace time?](#how-do-i-change-the-reconnection-grace-time)
|
||||||
- [How do I change the password?](#how-do-i-change-the-password)
|
- [How do I change the password?](#how-do-i-change-the-password)
|
||||||
- [Can I store my password hashed?](#can-i-store-my-password-hashed)
|
- [Can I store my password hashed?](#can-i-store-my-password-hashed)
|
||||||
- [Is multi-tenancy possible?](#is-multi-tenancy-possible)
|
- [Is multi-tenancy possible?](#is-multi-tenancy-possible)
|
||||||
@@ -326,6 +327,16 @@ If you want to shutdown code-server if there hasn't been an active connection
|
|||||||
after a predetermined amount of time, you can use the --idle-timeout-seconds flag
|
after a predetermined amount of time, you can use the --idle-timeout-seconds flag
|
||||||
or set an `CODE_SERVER_IDLE_TIMEOUT_SECONDS` environment variable.
|
or set an `CODE_SERVER_IDLE_TIMEOUT_SECONDS` environment variable.
|
||||||
|
|
||||||
|
## How do I change the reconnection grace time?
|
||||||
|
|
||||||
|
Pass `--reconnection-grace-time <seconds>` to `code-server`, set
|
||||||
|
`CODE_SERVER_RECONNECTION_GRACE_TIME=<seconds>`, or add
|
||||||
|
`reconnection-grace-time: <seconds>` to
|
||||||
|
`~/.config/code-server/config.yaml`.
|
||||||
|
|
||||||
|
The default is `10800` (3 hours). If a client stays disconnected longer than
|
||||||
|
this, it must reload the window.
|
||||||
|
|
||||||
## How do I change the password?
|
## How do I change the password?
|
||||||
|
|
||||||
Edit the `password` field in the code-server config file at
|
Edit the `password` field in the code-server config file at
|
||||||
|
|||||||
8
package-lock.json
generated
8
package-lock.json
generated
@@ -18,7 +18,7 @@
|
|||||||
"express": "^5.0.1",
|
"express": "^5.0.1",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"httpolyglot": "^0.1.2",
|
"httpolyglot": "^0.1.2",
|
||||||
"i18next": "^25.3.0",
|
"i18next": "^25.8.3",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"limiter": "^2.1.0",
|
"limiter": "^2.1.0",
|
||||||
"pem": "^1.14.8",
|
"pem": "^1.14.8",
|
||||||
@@ -3095,9 +3095,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/flatted": {
|
"node_modules/flatted": {
|
||||||
"version": "3.3.4",
|
"version": "3.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||||
"integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==",
|
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export interface UserProvidedCodeArgs {
|
|||||||
"disable-workspace-trust"?: boolean
|
"disable-workspace-trust"?: boolean
|
||||||
"disable-getting-started-override"?: boolean
|
"disable-getting-started-override"?: boolean
|
||||||
"disable-proxy"?: boolean
|
"disable-proxy"?: boolean
|
||||||
|
"reconnection-grace-time"?: string
|
||||||
"session-socket"?: string
|
"session-socket"?: string
|
||||||
"cookie-suffix"?: string
|
"cookie-suffix"?: string
|
||||||
"link-protection-trusted-domains"?: string[]
|
"link-protection-trusted-domains"?: string[]
|
||||||
@@ -315,6 +316,12 @@ export const options: Options<Required<UserProvidedArgs>> = {
|
|||||||
type: "number",
|
type: "number",
|
||||||
description: "Timeout in seconds to wait before shutting down when idle.",
|
description: "Timeout in seconds to wait before shutting down when idle.",
|
||||||
},
|
},
|
||||||
|
"reconnection-grace-time": {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"Override the reconnection grace time in seconds. Clients who disconnect for longer than this duration will need to \n" +
|
||||||
|
"reload the window. Defaults to 10800 (3 hours).",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => {
|
export const optionDescriptions = (opts: Partial<Options<Required<UserProvidedArgs>>> = options): string[] => {
|
||||||
@@ -632,6 +639,10 @@ export async function setDefaults(cliArgs: UserProvidedArgs, configArgs?: Config
|
|||||||
args["github-auth"] = process.env.GITHUB_TOKEN
|
args["github-auth"] = process.env.GITHUB_TOKEN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.CODE_SERVER_RECONNECTION_GRACE_TIME) {
|
||||||
|
args["reconnection-grace-time"] = process.env.CODE_SERVER_RECONNECTION_GRACE_TIME
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.CODE_SERVER_IDLE_TIMEOUT_SECONDS) {
|
if (process.env.CODE_SERVER_IDLE_TIMEOUT_SECONDS) {
|
||||||
if (isNaN(Number(process.env.CODE_SERVER_IDLE_TIMEOUT_SECONDS))) {
|
if (isNaN(Number(process.env.CODE_SERVER_IDLE_TIMEOUT_SECONDS))) {
|
||||||
logger.info("CODE_SERVER_IDLE_TIMEOUT_SECONDS must be a number")
|
logger.info("CODE_SERVER_IDLE_TIMEOUT_SECONDS must be a number")
|
||||||
|
|||||||
@@ -351,6 +351,25 @@ export function ensureOrigin(req: express.Request, _?: express.Response, next?:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the origin matches any trusted origin. Entries are matched
|
||||||
|
* as exact strings, the special wildcard `"*"`, or `*.example.com`-style
|
||||||
|
* domain wildcards (same as --proxy-domain).
|
||||||
|
*/
|
||||||
|
export function isTrustedOrigin(origin: string, trustedOrigins: string[]): boolean {
|
||||||
|
return trustedOrigins.some((trusted) => {
|
||||||
|
if (trusted === "*" || trusted === origin) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// *.example.com style: match origin if it is the domain or a subdomain
|
||||||
|
if (trusted.startsWith("*.")) {
|
||||||
|
const domain = trusted.slice(2).toLowerCase()
|
||||||
|
return origin === domain || origin.endsWith("." + domain)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Authenticate the request origin against the host. Throw if invalid.
|
* Authenticate the request origin against the host. Throw if invalid.
|
||||||
*/
|
*/
|
||||||
@@ -370,7 +389,7 @@ export function authenticateOrigin(req: express.Request): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const trustedOrigins = req.args["trusted-origins"] || []
|
const trustedOrigins = req.args["trusted-origins"] || []
|
||||||
if (trustedOrigins.includes(origin) || trustedOrigins.includes("*")) {
|
if (isTrustedOrigin(origin, trustedOrigins)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ describe("parser", () => {
|
|||||||
delete process.env.PASSWORD
|
delete process.env.PASSWORD
|
||||||
delete process.env.CS_DISABLE_FILE_DOWNLOADS
|
delete process.env.CS_DISABLE_FILE_DOWNLOADS
|
||||||
delete process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE
|
delete process.env.CS_DISABLE_GETTING_STARTED_OVERRIDE
|
||||||
|
delete process.env.CODE_SERVER_RECONNECTION_GRACE_TIME
|
||||||
delete process.env.VSCODE_PROXY_URI
|
delete process.env.VSCODE_PROXY_URI
|
||||||
delete process.env.CS_DISABLE_PROXY
|
delete process.env.CS_DISABLE_PROXY
|
||||||
console.log = jest.fn()
|
console.log = jest.fn()
|
||||||
@@ -115,6 +116,8 @@ describe("parser", () => {
|
|||||||
|
|
||||||
["--session-socket", "/tmp/override-code-server-ipc-socket"],
|
["--session-socket", "/tmp/override-code-server-ipc-socket"],
|
||||||
|
|
||||||
|
["--reconnection-grace-time", "86400"],
|
||||||
|
|
||||||
["--host", "0.0.0.0"],
|
["--host", "0.0.0.0"],
|
||||||
"4",
|
"4",
|
||||||
"--",
|
"--",
|
||||||
@@ -151,6 +154,7 @@ describe("parser", () => {
|
|||||||
version: true,
|
version: true,
|
||||||
"bind-addr": "192.169.0.1:8080",
|
"bind-addr": "192.169.0.1:8080",
|
||||||
"session-socket": "/tmp/override-code-server-ipc-socket",
|
"session-socket": "/tmp/override-code-server-ipc-socket",
|
||||||
|
"reconnection-grace-time": "86400",
|
||||||
"abs-proxy-base-path": "/codeserver/app1",
|
"abs-proxy-base-path": "/codeserver/app1",
|
||||||
"skip-auth-preflight": true,
|
"skip-auth-preflight": true,
|
||||||
})
|
})
|
||||||
@@ -457,6 +461,19 @@ describe("parser", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should use env var CODE_SERVER_RECONNECTION_GRACE_TIME for reconnection grace time", async () => {
|
||||||
|
process.env.CODE_SERVER_RECONNECTION_GRACE_TIME = "86400"
|
||||||
|
const args = parse([])
|
||||||
|
expect(args).toEqual({})
|
||||||
|
|
||||||
|
const defaultArgs = await setDefaults(args)
|
||||||
|
expect(defaultArgs).toEqual({
|
||||||
|
...defaults,
|
||||||
|
"reconnection-grace-time": "86400",
|
||||||
|
})
|
||||||
|
delete process.env.CODE_SERVER_RECONNECTION_GRACE_TIME
|
||||||
|
})
|
||||||
|
|
||||||
it("should error if password passed in", () => {
|
it("should error if password passed in", () => {
|
||||||
expect(() => parse(["--password", "supersecret123"])).toThrowError(
|
expect(() => parse(["--password", "supersecret123"])).toThrowError(
|
||||||
"--password can only be set in the config file or passed in via $PASSWORD",
|
"--password can only be set in the config file or passed in via $PASSWORD",
|
||||||
|
|||||||
@@ -19,6 +19,30 @@ describe("http", () => {
|
|||||||
expect(http.relativeRoot("/foo/bar/")).toStrictEqual("./../..")
|
expect(http.relativeRoot("/foo/bar/")).toStrictEqual("./../..")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("isTrustedOrigin", () => {
|
||||||
|
it("should match exact origins", () => {
|
||||||
|
expect(http.isTrustedOrigin("localhost:8080", ["localhost:8080"])).toBe(true)
|
||||||
|
expect(http.isTrustedOrigin("example.com", ["example.com"])).toBe(true)
|
||||||
|
expect(http.isTrustedOrigin("example.com", ["other.com"])).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should match the wildcard *", () => {
|
||||||
|
expect(http.isTrustedOrigin("anything.example.com", ["*"])).toBe(true)
|
||||||
|
expect(http.isTrustedOrigin("localhost:8080", ["*"])).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should match *.example.com wildcard (same style as --proxy-domain)", () => {
|
||||||
|
expect(http.isTrustedOrigin("sub.example.com", ["*.example.com"])).toBe(true)
|
||||||
|
expect(http.isTrustedOrigin("example.com", ["*.example.com"])).toBe(true)
|
||||||
|
expect(http.isTrustedOrigin("evil.com", ["*.example.com"])).toBe(false)
|
||||||
|
expect(http.isTrustedOrigin("example.com.evil.com", ["*.example.com"])).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return false for an empty trusted origins list", () => {
|
||||||
|
expect(http.isTrustedOrigin("example.com", [])).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe("origin", () => {
|
describe("origin", () => {
|
||||||
;[
|
;[
|
||||||
{
|
{
|
||||||
@@ -54,6 +78,22 @@ describe("http", () => {
|
|||||||
host: "localhost:8080",
|
host: "localhost:8080",
|
||||||
expected: "malformed", // Parsing fails completely.
|
expected: "malformed", // Parsing fails completely.
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
origin: "http://sub.example.com",
|
||||||
|
host: "other.com",
|
||||||
|
trustedOrigins: ["*.example.com"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
origin: "http://evil.com",
|
||||||
|
host: "other.com",
|
||||||
|
trustedOrigins: ["*.example.com"],
|
||||||
|
expected: "does not match",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
origin: "http://sub.example.com",
|
||||||
|
host: "other.com",
|
||||||
|
trustedOrigins: ["*"],
|
||||||
|
},
|
||||||
].forEach((test) => {
|
].forEach((test) => {
|
||||||
;[
|
;[
|
||||||
["host", test.host],
|
["host", test.host],
|
||||||
@@ -70,7 +110,9 @@ describe("http", () => {
|
|||||||
origin: test.origin,
|
origin: test.origin,
|
||||||
[key]: value,
|
[key]: value,
|
||||||
},
|
},
|
||||||
args: {},
|
args: {
|
||||||
|
"trusted-origins": (test as { trustedOrigins?: string[] }).trustedOrigins,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if (typeof test.expected === "string") {
|
if (typeof test.expected === "string") {
|
||||||
expect(() => http.authenticateOrigin(req)).toThrow(test.expected)
|
expect(() => http.authenticateOrigin(req)).toThrow(test.expected)
|
||||||
|
|||||||
Reference in New Issue
Block a user