Add default config file and improve config/data directory detection
This commit is contained in:
parent
c5179c2a06
commit
00d164b67f
@ -168,10 +168,8 @@ code-server crashes can be helpful.
|
|||||||
### Where is the data directory?
|
### Where is the data directory?
|
||||||
|
|
||||||
If the `XDG_DATA_HOME` environment variable is set the data directory will be
|
If the `XDG_DATA_HOME` environment variable is set the data directory will be
|
||||||
`$XDG_DATA_HOME/code-server`. Otherwise the default is:
|
`$XDG_DATA_HOME/code-server`. Otherwise the default is `~/.local/share/code-server`.
|
||||||
|
On Windows, it will be `%APPDATA%\Local\code-server\Data`.
|
||||||
1. Linux: `~/.local/share/code-server`.
|
|
||||||
2. Mac: `~/Library/Application\ Support/code-server`.
|
|
||||||
|
|
||||||
## Enterprise
|
## Enterprise
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coder/logger": "1.1.11",
|
"@coder/logger": "1.1.11",
|
||||||
"adm-zip": "^0.4.14",
|
"adm-zip": "^0.4.14",
|
||||||
|
"env-paths": "^2.2.0",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"http-proxy": "^1.18.0",
|
"http-proxy": "^1.18.0",
|
||||||
"httpolyglot": "^0.1.2",
|
"httpolyglot": "^0.1.2",
|
||||||
|
@ -4,8 +4,7 @@ import * as path from "path"
|
|||||||
import { field, logger, Level } from "@coder/logger"
|
import { field, logger, Level } from "@coder/logger"
|
||||||
import { Args as VsArgs } from "../../lib/vscode/src/vs/server/ipc"
|
import { Args as VsArgs } from "../../lib/vscode/src/vs/server/ipc"
|
||||||
import { AuthType } from "./http"
|
import { AuthType } from "./http"
|
||||||
import { xdgLocalDir } from "./util"
|
import { paths, uxPath } from "./util"
|
||||||
import xdgBasedir from "xdg-basedir"
|
|
||||||
|
|
||||||
export class Optional<T> {
|
export class Optional<T> {
|
||||||
public constructor(public readonly value?: T) {}
|
public constructor(public readonly value?: T) {}
|
||||||
@ -272,7 +271,7 @@ export const parse = (argv: string[]): Args => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!args["user-data-dir"]) {
|
if (!args["user-data-dir"]) {
|
||||||
args["user-data-dir"] = xdgLocalDir
|
args["user-data-dir"] = paths.data
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!args["extensions-dir"]) {
|
if (!args["extensions-dir"]) {
|
||||||
@ -282,6 +281,11 @@ export const parse = (argv: string[]): Args => {
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultConfigFile = `
|
||||||
|
auth: password
|
||||||
|
bind-addr: 127.0.0.1:8080
|
||||||
|
`.trimLeft()
|
||||||
|
|
||||||
// readConfigFile reads the config file specified in the config flag
|
// readConfigFile reads the config file specified in the config flag
|
||||||
// and loads it's configuration.
|
// and loads it's configuration.
|
||||||
//
|
//
|
||||||
@ -291,14 +295,14 @@ export const parse = (argv: string[]): Args => {
|
|||||||
// to ~/.config/code-server/config.yaml.
|
// to ~/.config/code-server/config.yaml.
|
||||||
export async function readConfigFile(args: Args): Promise<Args> {
|
export async function readConfigFile(args: Args): Promise<Args> {
|
||||||
const configPath = getConfigPath(args)
|
const configPath = getConfigPath(args)
|
||||||
if (configPath === undefined) {
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(await fs.pathExists(configPath))) {
|
if (!(await fs.pathExists(configPath))) {
|
||||||
await fs.outputFile(configPath, `default: hello`)
|
await fs.outputFile(configPath, defaultConfigFile)
|
||||||
|
logger.info(`Wrote default config file to ${uxPath(configPath)}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info(`Using config file from ${uxPath(configPath)}`)
|
||||||
|
|
||||||
const configFile = await fs.readFile(configPath)
|
const configFile = await fs.readFile(configPath)
|
||||||
const config = yaml.safeLoad(configFile.toString(), {
|
const config = yaml.safeLoad(configFile.toString(), {
|
||||||
filename: args.config,
|
filename: args.config,
|
||||||
@ -306,22 +310,24 @@ export async function readConfigFile(args: Args): Promise<Args> {
|
|||||||
|
|
||||||
// We convert the config file into a set of flags.
|
// We convert the config file into a set of flags.
|
||||||
// This is a temporary measure until we add a proper CLI library.
|
// This is a temporary measure until we add a proper CLI library.
|
||||||
const configFileArgv = Object.entries(config).map(([optName, opt]) => `--${optName}=${opt}`)
|
const configFileArgv = Object.entries(config).map(([optName, opt]) => {
|
||||||
|
if (opt === null) {
|
||||||
|
return `--${optName}`
|
||||||
|
}
|
||||||
|
return `--${optName}=${opt}`
|
||||||
|
})
|
||||||
const configFileArgs = parse(configFileArgv)
|
const configFileArgs = parse(configFileArgv)
|
||||||
|
|
||||||
// This prioritizes the flags set in args over the ones in the config file.
|
// This prioritizes the flags set in args over the ones in the config file.
|
||||||
return Object.assign(configFileArgs, args)
|
return Object.assign(configFileArgs, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfigPath(args: Args): string | undefined {
|
function getConfigPath(args: Args): string {
|
||||||
if (args.config !== undefined) {
|
if (args.config !== undefined) {
|
||||||
return args.config
|
return args.config
|
||||||
}
|
}
|
||||||
if (process.env.CODE_SERVER_CONFIG !== undefined) {
|
if (process.env.CODE_SERVER_CONFIG !== undefined) {
|
||||||
return process.env.CODE_SERVER_CONFIG
|
return process.env.CODE_SERVER_CONFIG
|
||||||
}
|
}
|
||||||
if (xdgBasedir.config !== undefined) {
|
return path.join(paths.config, "config.yaml")
|
||||||
return `${xdgBasedir.config}/code-server/config.yaml`
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import { UpdateHttpProvider } from "./app/update"
|
|||||||
import { VscodeHttpProvider } from "./app/vscode"
|
import { VscodeHttpProvider } from "./app/vscode"
|
||||||
import { Args, optionDescriptions, parse, readConfigFile } from "./cli"
|
import { Args, optionDescriptions, parse, readConfigFile } from "./cli"
|
||||||
import { AuthType, HttpServer, HttpServerOptions } from "./http"
|
import { AuthType, HttpServer, HttpServerOptions } from "./http"
|
||||||
import { generateCertificate, generatePassword, hash, open } from "./util"
|
import { generateCertificate, generatePassword, hash, open, uxPath } from "./util"
|
||||||
import { ipcMain, wrap } from "./wrapper"
|
import { ipcMain, wrap } from "./wrapper"
|
||||||
|
|
||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
@ -34,6 +34,11 @@ const commit = pkg.commit || "development"
|
|||||||
const main = async (args: Args): Promise<void> => {
|
const main = async (args: Args): Promise<void> => {
|
||||||
args = await readConfigFile(args)
|
args = await readConfigFile(args)
|
||||||
|
|
||||||
|
if (args.verbose === true) {
|
||||||
|
logger.info(`Using extensions-dir at ${uxPath(args["extensions-dir"]!)}`)
|
||||||
|
logger.info(`Using user-data-dir at ${uxPath(args["user-data-dir"]!)}`)
|
||||||
|
}
|
||||||
|
|
||||||
const auth = args.auth || AuthType.Password
|
const auth = args.auth || AuthType.Password
|
||||||
const originalPassword = auth === AuthType.Password && (process.env.PASSWORD || (await generatePassword()))
|
const originalPassword = auth === AuthType.Password && (process.env.PASSWORD || (await generatePassword()))
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ import * as url from "url"
|
|||||||
import { HttpCode, HttpError } from "../common/http"
|
import { HttpCode, HttpError } from "../common/http"
|
||||||
import { normalize, Options, plural, split } from "../common/util"
|
import { normalize, Options, plural, split } from "../common/util"
|
||||||
import { SocketProxyProvider } from "./socket"
|
import { SocketProxyProvider } from "./socket"
|
||||||
import { getMediaMime, xdgLocalDir } from "./util"
|
import { getMediaMime, paths } from "./util"
|
||||||
|
|
||||||
export type Cookies = { [key: string]: string[] | undefined }
|
export type Cookies = { [key: string]: string[] | undefined }
|
||||||
export type PostData = { [key: string]: string | string[] | undefined }
|
export type PostData = { [key: string]: string | string[] | undefined }
|
||||||
@ -473,7 +473,7 @@ export class HttpServer {
|
|||||||
|
|
||||||
public constructor(private readonly options: HttpServerOptions) {
|
public constructor(private readonly options: HttpServerOptions) {
|
||||||
this.proxyDomains = new Set((options.proxyDomains || []).map((d) => d.replace(/^\*\./, "")))
|
this.proxyDomains = new Set((options.proxyDomains || []).map((d) => d.replace(/^\*\./, "")))
|
||||||
this.heart = new Heart(path.join(xdgLocalDir, "heartbeat"), async () => {
|
this.heart = new Heart(path.join(paths.data, "heartbeat"), async () => {
|
||||||
const connections = await this.getConnections()
|
const connections = await this.getConnections()
|
||||||
logger.trace(`${connections} active connection${plural(connections)}`)
|
logger.trace(`${connections} active connection${plural(connections)}`)
|
||||||
return connections !== 0
|
return connections !== 0
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as fs from "fs-extra"
|
import * as fs from "fs-extra"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { extend, xdgLocalDir } from "./util"
|
import { extend, paths } from "./util"
|
||||||
import { logger } from "@coder/logger"
|
import { logger } from "@coder/logger"
|
||||||
|
|
||||||
export type Settings = { [key: string]: Settings | string | boolean | number }
|
export type Settings = { [key: string]: Settings | string | boolean | number }
|
||||||
@ -60,4 +60,4 @@ export interface CoderSettings extends UpdateSettings {
|
|||||||
/**
|
/**
|
||||||
* Global code-server settings file.
|
* Global code-server settings file.
|
||||||
*/
|
*/
|
||||||
export const settings = new SettingsProvider<CoderSettings>(path.join(xdgLocalDir, "coder.json"))
|
export const settings = new SettingsProvider<CoderSettings>(path.join(paths.data, "coder.json"))
|
||||||
|
@ -4,24 +4,48 @@ import * as fs from "fs-extra"
|
|||||||
import * as os from "os"
|
import * as os from "os"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import * as util from "util"
|
import * as util from "util"
|
||||||
|
import envPaths from "env-paths"
|
||||||
|
import xdgBasedir from "xdg-basedir"
|
||||||
|
|
||||||
export const tmpdir = path.join(os.tmpdir(), "code-server")
|
export const tmpdir = path.join(os.tmpdir(), "code-server")
|
||||||
|
|
||||||
const getXdgDataDir = (): string => {
|
interface Paths {
|
||||||
switch (process.platform) {
|
data: string
|
||||||
case "win32":
|
config: string
|
||||||
return path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), "AppData/Local"), "code-server/Data")
|
|
||||||
case "darwin":
|
|
||||||
return path.join(
|
|
||||||
process.env.XDG_DATA_HOME || path.join(os.homedir(), "Library/Application Support"),
|
|
||||||
"code-server",
|
|
||||||
)
|
|
||||||
default:
|
|
||||||
return path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), ".local/share"), "code-server")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const xdgLocalDir = getXdgDataDir()
|
export const paths = getEnvPaths()
|
||||||
|
|
||||||
|
// getEnvPaths gets the config and data paths for the current platform/configuration.
|
||||||
|
//
|
||||||
|
// On MacOS this function gets the standard XDG directories instead of using the native macOS
|
||||||
|
// ones. Most CLIs do this as in practice only GUI apps use the standard macOS directories.
|
||||||
|
function getEnvPaths(): Paths {
|
||||||
|
let paths: Paths
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
paths = envPaths("code-server", {
|
||||||
|
suffix: "",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (xdgBasedir.data === undefined) {
|
||||||
|
throw new Error("Missing data directory?")
|
||||||
|
}
|
||||||
|
if (xdgBasedir.config === undefined) {
|
||||||
|
throw new Error("Missing config directory?")
|
||||||
|
}
|
||||||
|
paths = {
|
||||||
|
data: path.join(xdgBasedir.data, "code-server"),
|
||||||
|
config: path.join(xdgBasedir.config, "code-server"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
// uxPath replaces the home directory in p with ~.
|
||||||
|
export function uxPath(p: string): string {
|
||||||
|
return p.replace(os.homedir(), "~")
|
||||||
|
}
|
||||||
|
|
||||||
export const generateCertificate = async (): Promise<{ cert: string; certKey: string }> => {
|
export const generateCertificate = async (): Promise<{ cert: string; certKey: string }> => {
|
||||||
const paths = {
|
const paths = {
|
||||||
|
@ -2,7 +2,7 @@ import { logger, Level } from "@coder/logger"
|
|||||||
import * as assert from "assert"
|
import * as assert from "assert"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { parse } from "../src/node/cli"
|
import { parse } from "../src/node/cli"
|
||||||
import { xdgLocalDir } from "../src/node/util"
|
import { paths } from "../src/node/util"
|
||||||
|
|
||||||
describe("cli", () => {
|
describe("cli", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@ -12,8 +12,8 @@ describe("cli", () => {
|
|||||||
// The parser will always fill these out.
|
// The parser will always fill these out.
|
||||||
const defaults = {
|
const defaults = {
|
||||||
_: [],
|
_: [],
|
||||||
"extensions-dir": path.join(xdgLocalDir, "extensions"),
|
"extensions-dir": path.join(paths.data, "extensions"),
|
||||||
"user-data-dir": xdgLocalDir,
|
"user-data-dir": paths.data,
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should set defaults", () => {
|
it("should set defaults", () => {
|
||||||
|
@ -2562,6 +2562,11 @@ entities@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
|
||||||
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
|
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
|
||||||
|
|
||||||
|
env-paths@^2.2.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
|
||||||
|
integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
|
||||||
|
|
||||||
envinfo@^7.3.1:
|
envinfo@^7.3.1:
|
||||||
version "7.5.1"
|
version "7.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.1.tgz#93c26897225a00457c75e734d354ea9106a72236"
|
resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.5.1.tgz#93c26897225a00457c75e734d354ea9106a72236"
|
||||||
|
Reference in New Issue
Block a user