mirror of
https://github.com/coder/code-server.git
synced 2025-07-31 14:04:06 +08:00
Configuration file bug fixes based on @code-asher's review
This commit is contained in:
148
src/node/cli.ts
148
src/node/cli.ts
@ -1,10 +1,10 @@
|
||||
import { field, Level, logger } from "@coder/logger"
|
||||
import * as fs from "fs-extra"
|
||||
import yaml from "js-yaml"
|
||||
import * as path from "path"
|
||||
import { field, logger, Level } from "@coder/logger"
|
||||
import { Args as VsArgs } from "../../lib/vscode/src/vs/server/ipc"
|
||||
import { AuthType } from "./http"
|
||||
import { paths, uxPath } from "./util"
|
||||
import { generatePassword, humanPath, paths } from "./util"
|
||||
|
||||
export class Optional<T> {
|
||||
public constructor(public readonly value?: T) {}
|
||||
@ -84,7 +84,10 @@ type Options<T> = {
|
||||
|
||||
const options: Options<Required<Args>> = {
|
||||
auth: { type: AuthType, description: "The type of authentication to use." },
|
||||
password: { type: "string", description: "The password for password authentication." },
|
||||
password: {
|
||||
type: "string",
|
||||
description: "The password for password authentication (can only be passed in via $PASSWORD or the config file).",
|
||||
},
|
||||
cert: {
|
||||
type: OptionalString,
|
||||
path: true,
|
||||
@ -96,11 +99,14 @@ const options: Options<Required<Args>> = {
|
||||
json: { type: "boolean" },
|
||||
open: { type: "boolean", description: "Open in browser on startup. Does not work remotely." },
|
||||
|
||||
"bind-addr": { type: "string", description: "Address to bind to in host:port." },
|
||||
"bind-addr": {
|
||||
type: "string",
|
||||
description: "Address to bind to in host:port. You can also use $PORT to override the port.",
|
||||
},
|
||||
|
||||
config: {
|
||||
type: "string",
|
||||
description: "Path to yaml config file. Every flag maps directory to a key in the config file.",
|
||||
description: "Path to yaml config file. Every flag maps directly to a key in the config file.",
|
||||
},
|
||||
|
||||
// These two have been deprecated by bindAddr.
|
||||
@ -145,7 +151,19 @@ export const optionDescriptions = (): string[] => {
|
||||
)
|
||||
}
|
||||
|
||||
export const parse = (argv: string[]): Args => {
|
||||
export const parse = (
|
||||
argv: string[],
|
||||
opts?: {
|
||||
configFile: string
|
||||
},
|
||||
): Args => {
|
||||
const error = (msg: string): Error => {
|
||||
if (opts?.configFile) {
|
||||
msg = `error reading ${opts.configFile}: ${msg}`
|
||||
}
|
||||
return new Error(msg)
|
||||
}
|
||||
|
||||
const args: Args = { _: [] }
|
||||
let ended = false
|
||||
|
||||
@ -175,7 +193,11 @@ export const parse = (argv: string[]): Args => {
|
||||
}
|
||||
|
||||
if (!key || !options[key]) {
|
||||
throw new Error(`Unknown option ${arg}`)
|
||||
throw error(`Unknown option ${arg}`)
|
||||
}
|
||||
|
||||
if (key === "password" && !opts?.configFile) {
|
||||
throw new Error("--password can only be set in the config file or passed in via $PASSWORD")
|
||||
}
|
||||
|
||||
const option = options[key]
|
||||
@ -194,7 +216,11 @@ export const parse = (argv: string[]): Args => {
|
||||
;(args[key] as OptionalString) = new OptionalString(value)
|
||||
continue
|
||||
} else if (!value) {
|
||||
throw new Error(`--${key} requires a value`)
|
||||
throw error(`--${key} requires a value`)
|
||||
}
|
||||
|
||||
if (option.type == OptionalString && value == "false") {
|
||||
continue
|
||||
}
|
||||
|
||||
if (option.path) {
|
||||
@ -214,7 +240,7 @@ export const parse = (argv: string[]): Args => {
|
||||
case "number":
|
||||
;(args[key] as number) = parseInt(value, 10)
|
||||
if (isNaN(args[key] as number)) {
|
||||
throw new Error(`--${key} must be a number`)
|
||||
throw error(`--${key} must be a number`)
|
||||
}
|
||||
break
|
||||
case OptionalString:
|
||||
@ -222,7 +248,7 @@ export const parse = (argv: string[]): Args => {
|
||||
break
|
||||
default: {
|
||||
if (!Object.values(option.type).includes(value)) {
|
||||
throw new Error(`--${key} valid values: [${Object.values(option.type).join(", ")}]`)
|
||||
throw error(`--${key} valid values: [${Object.values(option.type).join(", ")}]`)
|
||||
}
|
||||
;(args[key] as string) = value
|
||||
break
|
||||
@ -284,53 +310,93 @@ export const parse = (argv: string[]): Args => {
|
||||
return args
|
||||
}
|
||||
|
||||
const defaultConfigFile = `
|
||||
async function defaultConfigFile(): Promise<string> {
|
||||
return `bind-addr: 127.0.0.1:8080
|
||||
auth: password
|
||||
bind-addr: 127.0.0.1:8080
|
||||
`.trimLeft()
|
||||
password: ${await generatePassword()}
|
||||
cert: false
|
||||
`
|
||||
}
|
||||
|
||||
// readConfigFile reads the config file specified in the config flag
|
||||
// and loads it's configuration.
|
||||
//
|
||||
// Flags set on the CLI take priority.
|
||||
//
|
||||
// The config file can also be passed via $CODE_SERVER_CONFIG and defaults
|
||||
// to ~/.config/code-server/config.yaml.
|
||||
export async function readConfigFile(args: Args): Promise<Args> {
|
||||
const configPath = getConfigPath(args)
|
||||
|
||||
if (!(await fs.pathExists(configPath))) {
|
||||
await fs.outputFile(configPath, defaultConfigFile)
|
||||
logger.info(`Wrote default config file to ${uxPath(configPath)}`)
|
||||
/**
|
||||
* Reads the code-server yaml config file and returns it as Args.
|
||||
*
|
||||
* @param configPath Read the config from configPath instead of $CODE_SERVER_CONFIG or the default.
|
||||
*/
|
||||
export async function readConfigFile(configPath?: string): Promise<Args> {
|
||||
if (!configPath) {
|
||||
configPath = process.env.CODE_SERVER_CONFIG
|
||||
if (!configPath) {
|
||||
configPath = path.join(paths.config, "config.yaml")
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(`Using config file from ${uxPath(configPath)}`)
|
||||
if (!(await fs.pathExists(configPath))) {
|
||||
await fs.outputFile(configPath, await defaultConfigFile())
|
||||
logger.info(`Wrote default config file to ${humanPath(configPath)}`)
|
||||
}
|
||||
|
||||
logger.info(`Using config file from ${humanPath(configPath)}`)
|
||||
|
||||
const configFile = await fs.readFile(configPath)
|
||||
const config = yaml.safeLoad(configFile.toString(), {
|
||||
filename: args.config,
|
||||
filename: configPath,
|
||||
})
|
||||
|
||||
// We convert the config file into a set of flags.
|
||||
// This is a temporary measure until we add a proper CLI library.
|
||||
const configFileArgv = Object.entries(config).map(([optName, opt]) => {
|
||||
if (opt === null) {
|
||||
if (opt === true) {
|
||||
return `--${optName}`
|
||||
}
|
||||
return `--${optName}=${opt}`
|
||||
})
|
||||
const configFileArgs = parse(configFileArgv)
|
||||
|
||||
// This prioritizes the flags set in args over the ones in the config file.
|
||||
return Object.assign(configFileArgs, args)
|
||||
const args = parse(configFileArgv, {
|
||||
configFile: configPath,
|
||||
})
|
||||
return {
|
||||
...args,
|
||||
config: configPath,
|
||||
}
|
||||
}
|
||||
|
||||
function getConfigPath(args: Args): string {
|
||||
if (args.config !== undefined) {
|
||||
return args.config
|
||||
}
|
||||
if (process.env.CODE_SERVER_CONFIG !== undefined) {
|
||||
return process.env.CODE_SERVER_CONFIG
|
||||
}
|
||||
return path.join(paths.config, "config.yaml")
|
||||
function parseBindAddr(bindAddr: string): [string, number] {
|
||||
const u = new URL(`http://${bindAddr}`)
|
||||
return [u.hostname, parseInt(u.port, 10)]
|
||||
}
|
||||
|
||||
interface Addr {
|
||||
host: string
|
||||
port: number
|
||||
}
|
||||
|
||||
function bindAddrFromArgs(addr: Addr, args: Args): Addr {
|
||||
addr = { ...addr }
|
||||
if (args["bind-addr"]) {
|
||||
;[addr.host, addr.port] = parseBindAddr(args["bind-addr"])
|
||||
}
|
||||
if (args.host) {
|
||||
addr.host = args.host
|
||||
}
|
||||
if (args.port !== undefined) {
|
||||
addr.port = args.port
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
export function bindAddrFromAllSources(cliArgs: Args, configArgs: Args): [string, number] {
|
||||
let addr: Addr = {
|
||||
host: "localhost",
|
||||
port: 8080,
|
||||
}
|
||||
|
||||
addr = bindAddrFromArgs(addr, configArgs)
|
||||
|
||||
if (process.env.PORT) {
|
||||
addr.port = parseInt(process.env.PORT, 10)
|
||||
}
|
||||
|
||||
addr = bindAddrFromArgs(addr, cliArgs)
|
||||
|
||||
return [addr.host, addr.port]
|
||||
}
|
||||
|
Reference in New Issue
Block a user