Skip to content

Commit 4a32858

Browse files
committed
Add default config file and improve config/data directory detection
1 parent ce76dd3 commit 4a32858

File tree

9 files changed

+76
-37
lines changed

9 files changed

+76
-37
lines changed

doc/FAQ.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,8 @@ code-server crashes can be helpful.
168168
### Where is the data directory?
169169

170170
If the `XDG_DATA_HOME` environment variable is set the data directory will be
171-
`$XDG_DATA_HOME/code-server`. Otherwise the default is:
172-
173-
1. Linux: `~/.local/share/code-server`.
174-
2. Mac: `~/Library/Application\ Support/code-server`.
171+
`$XDG_DATA_HOME/code-server`. Otherwise the default is `~/.local/share/code-server`.
172+
On Windows, it will be `%APPDATA%\Local\code-server\Data`.
175173

176174
## Enterprise
177175

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
"dependencies": {
6666
"@coder/logger": "1.1.11",
6767
"adm-zip": "^0.4.14",
68+
"env-paths": "^2.2.0",
6869
"fs-extra": "^8.1.0",
6970
"http-proxy": "^1.18.0",
7071
"httpolyglot": "^0.1.2",

src/node/cli.ts

+19-13
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ import * as path from "path"
44
import { field, logger, Level } from "@coder/logger"
55
import { Args as VsArgs } from "../../lib/vscode/src/vs/server/ipc"
66
import { AuthType } from "./http"
7-
import { xdgLocalDir } from "./util"
8-
import xdgBasedir from "xdg-basedir"
7+
import { paths, uxPath } from "./util"
98

109
export class Optional<T> {
1110
public constructor(public readonly value?: T) {}
@@ -272,7 +271,7 @@ export const parse = (argv: string[]): Args => {
272271
}
273272

274273
if (!args["user-data-dir"]) {
275-
args["user-data-dir"] = xdgLocalDir
274+
args["user-data-dir"] = paths.data
276275
}
277276

278277
if (!args["extensions-dir"]) {
@@ -282,6 +281,11 @@ export const parse = (argv: string[]): Args => {
282281
return args
283282
}
284283

284+
const defaultConfigFile = `
285+
auth: password
286+
bind-addr: 127.0.0.1:8080
287+
`.trimLeft()
288+
285289
// readConfigFile reads the config file specified in the config flag
286290
// and loads it's configuration.
287291
//
@@ -291,37 +295,39 @@ export const parse = (argv: string[]): Args => {
291295
// to ~/.config/code-server/config.yaml.
292296
export async function readConfigFile(args: Args): Promise<Args> {
293297
const configPath = getConfigPath(args)
294-
if (configPath === undefined) {
295-
return args
296-
}
297298

298299
if (!(await fs.pathExists(configPath))) {
299-
await fs.outputFile(configPath, `default: hello`)
300+
await fs.outputFile(configPath, defaultConfigFile)
301+
logger.info(`Wrote default config file to ${uxPath(configPath)}`)
300302
}
301303

304+
logger.info(`Using config file from ${uxPath(configPath)}`)
305+
302306
const configFile = await fs.readFile(configPath)
303307
const config = yaml.safeLoad(configFile.toString(), {
304308
filename: args.config,
305309
})
306310

307311
// We convert the config file into a set of flags.
308312
// This is a temporary measure until we add a proper CLI library.
309-
const configFileArgv = Object.entries(config).map(([optName, opt]) => `--${optName}=${opt}`)
313+
const configFileArgv = Object.entries(config).map(([optName, opt]) => {
314+
if (opt === null) {
315+
return `--${optName}`
316+
}
317+
return `--${optName}=${opt}`
318+
})
310319
const configFileArgs = parse(configFileArgv)
311320

312321
// This prioritizes the flags set in args over the ones in the config file.
313322
return Object.assign(configFileArgs, args)
314323
}
315324

316-
function getConfigPath(args: Args): string | undefined {
325+
function getConfigPath(args: Args): string {
317326
if (args.config !== undefined) {
318327
return args.config
319328
}
320329
if (process.env.CODE_SERVER_CONFIG !== undefined) {
321330
return process.env.CODE_SERVER_CONFIG
322331
}
323-
if (xdgBasedir.config !== undefined) {
324-
return `${xdgBasedir.config}/code-server/config.yaml`
325-
}
326-
return undefined
332+
return path.join(paths.config, "config.yaml")
327333
}

src/node/entry.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { UpdateHttpProvider } from "./app/update"
1111
import { VscodeHttpProvider } from "./app/vscode"
1212
import { Args, optionDescriptions, parse, readConfigFile } from "./cli"
1313
import { AuthType, HttpServer, HttpServerOptions } from "./http"
14-
import { generateCertificate, generatePassword, hash, open } from "./util"
14+
import { generateCertificate, generatePassword, hash, open, uxPath } from "./util"
1515
import { ipcMain, wrap } from "./wrapper"
1616

1717
process.on("uncaughtException", (error) => {
@@ -34,6 +34,11 @@ const commit = pkg.commit || "development"
3434
const main = async (args: Args): Promise<void> => {
3535
args = await readConfigFile(args)
3636

37+
if (args.verbose === true) {
38+
logger.info(`Using extensions-dir at ${uxPath(args["extensions-dir"]!)}`)
39+
logger.info(`Using user-data-dir at ${uxPath(args["user-data-dir"]!)}`)
40+
}
41+
3742
const auth = args.auth || AuthType.Password
3843
const originalPassword = auth === AuthType.Password && (process.env.PASSWORD || (await generatePassword()))
3944

src/node/http.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import * as url from "url"
1414
import { HttpCode, HttpError } from "../common/http"
1515
import { normalize, Options, plural, split } from "../common/util"
1616
import { SocketProxyProvider } from "./socket"
17-
import { getMediaMime, xdgLocalDir } from "./util"
17+
import { getMediaMime, paths } from "./util"
1818

1919
export type Cookies = { [key: string]: string[] | undefined }
2020
export type PostData = { [key: string]: string | string[] | undefined }
@@ -473,7 +473,7 @@ export class HttpServer {
473473

474474
public constructor(private readonly options: HttpServerOptions) {
475475
this.proxyDomains = new Set((options.proxyDomains || []).map((d) => d.replace(/^\*\./, "")))
476-
this.heart = new Heart(path.join(xdgLocalDir, "heartbeat"), async () => {
476+
this.heart = new Heart(path.join(paths.data, "heartbeat"), async () => {
477477
const connections = await this.getConnections()
478478
logger.trace(`${connections} active connection${plural(connections)}`)
479479
return connections !== 0

src/node/settings.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as fs from "fs-extra"
22
import * as path from "path"
3-
import { extend, xdgLocalDir } from "./util"
3+
import { extend, paths } from "./util"
44
import { logger } from "@coder/logger"
55

66
export type Settings = { [key: string]: Settings | string | boolean | number }
@@ -60,4 +60,4 @@ export interface CoderSettings extends UpdateSettings {
6060
/**
6161
* Global code-server settings file.
6262
*/
63-
export const settings = new SettingsProvider<CoderSettings>(path.join(xdgLocalDir, "coder.json"))
63+
export const settings = new SettingsProvider<CoderSettings>(path.join(paths.data, "coder.json"))

src/node/util.ts

+36-12
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,48 @@ import * as fs from "fs-extra"
44
import * as os from "os"
55
import * as path from "path"
66
import * as util from "util"
7+
import envPaths from "env-paths"
8+
import xdgBasedir from "xdg-basedir"
79

810
export const tmpdir = path.join(os.tmpdir(), "code-server")
911

10-
const getXdgDataDir = (): string => {
11-
switch (process.platform) {
12-
case "win32":
13-
return path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), "AppData/Local"), "code-server/Data")
14-
case "darwin":
15-
return path.join(
16-
process.env.XDG_DATA_HOME || path.join(os.homedir(), "Library/Application Support"),
17-
"code-server",
18-
)
19-
default:
20-
return path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local/share"), "code-server")
12+
interface Paths {
13+
data: string
14+
config: string
15+
}
16+
17+
export const paths = getEnvPaths()
18+
19+
// getEnvPaths gets the config and data paths for the current platform/configuration.
20+
//
21+
// On MacOS this function gets the standard XDG directories instead of using the native macOS
22+
// ones. Most CLIs do this as in practice only GUI apps use the standard macOS directories.
23+
function getEnvPaths(): Paths {
24+
let paths: Paths
25+
if (process.platform === "win32") {
26+
paths = envPaths("code-server", {
27+
suffix: "",
28+
})
29+
} else {
30+
if (xdgBasedir.data === undefined) {
31+
throw new Error("Missing data directory?")
32+
}
33+
if (xdgBasedir.config === undefined) {
34+
throw new Error("Missing config directory?")
35+
}
36+
paths = {
37+
data: path.join(xdgBasedir.data, "code-server"),
38+
config: path.join(xdgBasedir.config, "code-server"),
39+
}
2140
}
41+
42+
return paths
2243
}
2344

24-
export const xdgLocalDir = getXdgDataDir()
45+
// uxPath replaces the home directory in p with ~.
46+
export function uxPath(p: string): string {
47+
return p.replace(os.homedir(), "~")
48+
}
2549

2650
export const generateCertificate = async (): Promise<{ cert: string; certKey: string }> => {
2751
const paths = {

test/cli.test.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { logger, Level } from "@coder/logger"
22
import * as assert from "assert"
33
import * as path from "path"
44
import { parse } from "../src/node/cli"
5-
import { xdgLocalDir } from "../src/node/util"
5+
import { paths } from "../src/node/util"
66

77
describe("cli", () => {
88
beforeEach(() => {
@@ -12,8 +12,8 @@ describe("cli", () => {
1212
// The parser will always fill these out.
1313
const defaults = {
1414
_: [],
15-
"extensions-dir": path.join(xdgLocalDir, "extensions"),
16-
"user-data-dir": xdgLocalDir,
15+
"extensions-dir": path.join(paths.data, "extensions"),
16+
"user-data-dir": paths.data,
1717
}
1818

1919
it("should set defaults", () => {

yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -2562,6 +2562,11 @@ entities@^2.0.0:
25622562
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
25632563
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
25642564

2565+
env-paths@^2.2.0:
2566+
version "2.2.0"
2567+
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
2568+
integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
2569+
25652570
envinfo@^7.3.1:
25662571
version "7.5.1"
25672572
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.1.tgz#93c26897225a00457c75e734d354ea9106a72236"

0 commit comments

Comments
 (0)