mirror of https://github.com/coder/code-server.git
parent
8e93e28162
commit
71dc5c7542
|
@ -0,0 +1,57 @@
|
||||||
|
import { logger } from "@coder/logger"
|
||||||
|
import express, { Express } from "express"
|
||||||
|
import { promises as fs } from "fs"
|
||||||
|
import http from "http"
|
||||||
|
import * as httpolyglot from "httpolyglot"
|
||||||
|
import { DefaultedArgs } from "./cli"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an Express app and an HTTP/S server to serve it.
|
||||||
|
*/
|
||||||
|
export const createApp = async (args: DefaultedArgs): Promise<[Express, http.Server]> => {
|
||||||
|
const app = express()
|
||||||
|
|
||||||
|
const server = args.cert
|
||||||
|
? httpolyglot.createServer(
|
||||||
|
{
|
||||||
|
cert: args.cert && (await fs.readFile(args.cert.value)),
|
||||||
|
key: args["cert-key"] && (await fs.readFile(args["cert-key"])),
|
||||||
|
},
|
||||||
|
app,
|
||||||
|
)
|
||||||
|
: http.createServer(app)
|
||||||
|
|
||||||
|
await new Promise<http.Server>(async (resolve, reject) => {
|
||||||
|
server.on("error", reject)
|
||||||
|
if (args.socket) {
|
||||||
|
try {
|
||||||
|
await fs.unlink(args.socket)
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code !== "ENOENT") {
|
||||||
|
logger.error(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server.listen(args.socket, resolve)
|
||||||
|
} else {
|
||||||
|
// [] is the correct format when using :: but Node errors with them.
|
||||||
|
server.listen(args.port, args.host.replace(/^\[|\]$/g, ""), resolve)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return [app, server]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the address of a server as a string (protocol not included) while
|
||||||
|
* ensuring there is one (will throw if there isn't).
|
||||||
|
*/
|
||||||
|
export const ensureAddress = (server: http.Server): string => {
|
||||||
|
const addr = server.address()
|
||||||
|
if (!addr) {
|
||||||
|
throw new Error("server has no address")
|
||||||
|
}
|
||||||
|
if (typeof addr !== "string") {
|
||||||
|
return `${addr.address}:${addr.port}`
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
|
@ -5,13 +5,9 @@ import http from "http"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import { CliMessage, OpenCommandPipeArgs } from "../../lib/vscode/src/vs/server/ipc"
|
import { CliMessage, OpenCommandPipeArgs } from "../../lib/vscode/src/vs/server/ipc"
|
||||||
import { plural } from "../common/util"
|
import { plural } from "../common/util"
|
||||||
import { HealthHttpProvider } from "./routes/health"
|
import { createApp, ensureAddress } from "./app"
|
||||||
import { LoginHttpProvider } from "./routes/login"
|
|
||||||
import { ProxyHttpProvider } from "./routes/proxy"
|
|
||||||
import { StaticHttpProvider } from "./routes/static"
|
|
||||||
import { UpdateHttpProvider } from "./routes/update"
|
|
||||||
import { VscodeHttpProvider } from "./routes/vscode"
|
|
||||||
import {
|
import {
|
||||||
|
AuthType,
|
||||||
DefaultedArgs,
|
DefaultedArgs,
|
||||||
optionDescriptions,
|
optionDescriptions,
|
||||||
parse,
|
parse,
|
||||||
|
@ -21,9 +17,8 @@ import {
|
||||||
shouldRunVsCodeCli,
|
shouldRunVsCodeCli,
|
||||||
} from "./cli"
|
} from "./cli"
|
||||||
import { coderCloudBind } from "./coder-cloud"
|
import { coderCloudBind } from "./coder-cloud"
|
||||||
import { AuthType, HttpServer, HttpServerOptions } from "./http"
|
|
||||||
import { loadPlugins } from "./plugin"
|
import { loadPlugins } from "./plugin"
|
||||||
import { hash, humanPath, open } from "./util"
|
import { humanPath, open } from "./util"
|
||||||
import { ipcMain, WrapperProcess } from "./wrapper"
|
import { ipcMain, WrapperProcess } from "./wrapper"
|
||||||
|
|
||||||
let pkg: { version?: string; commit?: string } = {}
|
let pkg: { version?: string; commit?: string } = {}
|
||||||
|
@ -117,65 +112,39 @@ export const openInExistingInstance = async (args: DefaultedArgs, socketPath: st
|
||||||
}
|
}
|
||||||
|
|
||||||
const main = async (args: DefaultedArgs): Promise<void> => {
|
const main = async (args: DefaultedArgs): Promise<void> => {
|
||||||
|
logger.info(`code-server ${version} ${commit}`)
|
||||||
|
|
||||||
logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`)
|
logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`)
|
||||||
logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`)
|
logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`)
|
||||||
|
|
||||||
if (args.auth === AuthType.Password && !args.password) {
|
if (args.auth === AuthType.Password && !args.password) {
|
||||||
throw new Error("Please pass in a password via the config file or $PASSWORD")
|
throw new Error("Please pass in a password via the config file or $PASSWORD")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn the main HTTP server.
|
|
||||||
const options: HttpServerOptions = {
|
|
||||||
auth: args.auth,
|
|
||||||
commit,
|
|
||||||
host: args.host,
|
|
||||||
// The hash does not add any actual security but we do it for obfuscation purposes.
|
|
||||||
password: args.password ? hash(args.password) : undefined,
|
|
||||||
port: args.port,
|
|
||||||
proxyDomains: args["proxy-domain"],
|
|
||||||
socket: args.socket,
|
|
||||||
cert: args.cert && args.cert.value,
|
|
||||||
certKey: args["cert-key"],
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.cert && !options.certKey) {
|
|
||||||
throw new Error("--cert-key is missing")
|
|
||||||
}
|
|
||||||
|
|
||||||
const httpServer = new HttpServer(options)
|
|
||||||
httpServer.registerHttpProvider(["/", "/vscode"], VscodeHttpProvider, args)
|
|
||||||
httpServer.registerHttpProvider("/update", UpdateHttpProvider, false)
|
|
||||||
httpServer.registerHttpProvider("/proxy", ProxyHttpProvider)
|
|
||||||
httpServer.registerHttpProvider("/login", LoginHttpProvider, args.config!, args.usingEnvPassword)
|
|
||||||
httpServer.registerHttpProvider("/static", StaticHttpProvider)
|
|
||||||
httpServer.registerHttpProvider("/healthz", HealthHttpProvider, httpServer.heart)
|
|
||||||
|
|
||||||
await loadPlugins(httpServer, args)
|
|
||||||
|
|
||||||
ipcMain.onDispose(() => {
|
ipcMain.onDispose(() => {
|
||||||
httpServer.dispose().then((errors) => {
|
// TODO: register disposables
|
||||||
errors.forEach((error) => logger.error(error.message))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
logger.info(`code-server ${version} ${commit}`)
|
const [app, server] = await createApp(args)
|
||||||
logger.info(`Using config file ${humanPath(args.config)}`)
|
const serverAddress = ensureAddress(server)
|
||||||
|
|
||||||
const serverAddress = await httpServer.listen()
|
// TODO: register routes
|
||||||
|
await loadPlugins(app, args)
|
||||||
|
|
||||||
|
logger.info(`Using config file ${humanPath(args.config)}`)
|
||||||
logger.info(`HTTP server listening on ${serverAddress} ${args.link ? "(randomized by --link)" : ""}`)
|
logger.info(`HTTP server listening on ${serverAddress} ${args.link ? "(randomized by --link)" : ""}`)
|
||||||
|
|
||||||
if (args.auth === AuthType.Password) {
|
if (args.auth === AuthType.Password) {
|
||||||
|
logger.info(" - Authentication is enabled")
|
||||||
if (args.usingEnvPassword) {
|
if (args.usingEnvPassword) {
|
||||||
logger.info(" - Using password from $PASSWORD")
|
logger.info(" - Using password from $PASSWORD")
|
||||||
} else {
|
} else {
|
||||||
logger.info(` - Using password from ${humanPath(args.config)}`)
|
logger.info(` - Using password from ${humanPath(args.config)}`)
|
||||||
}
|
}
|
||||||
logger.info(" - To disable use `--auth none`")
|
|
||||||
} else {
|
} else {
|
||||||
logger.info(` - No authentication ${args.link ? "(disabled by --link)" : ""}`)
|
logger.info(` - Authentication is disabled ${args.link ? "(disabled by --link)" : ""}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (httpServer.protocol === "https") {
|
if (args.cert) {
|
||||||
logger.info(
|
logger.info(
|
||||||
args.cert && args.cert.value
|
args.cert && args.cert.value
|
||||||
? ` - Using provided certificate and key for HTTPS`
|
? ` - Using provided certificate and key for HTTPS`
|
||||||
|
@ -192,7 +161,7 @@ const main = async (args: DefaultedArgs): Promise<void> => {
|
||||||
|
|
||||||
if (args.link) {
|
if (args.link) {
|
||||||
try {
|
try {
|
||||||
await coderCloudBind(serverAddress!, args.link.value)
|
await coderCloudBind(serverAddress, args.link.value)
|
||||||
logger.info(" - Connected to cloud agent")
|
logger.info(" - Connected to cloud agent")
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err.message)
|
logger.error(err.message)
|
||||||
|
@ -200,7 +169,7 @@ const main = async (args: DefaultedArgs): Promise<void> => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverAddress && !options.socket && args.open) {
|
if (serverAddress && !args.socket && args.open) {
|
||||||
// The web socket doesn't seem to work if browsing with 0.0.0.0.
|
// The web socket doesn't seem to work if browsing with 0.0.0.0.
|
||||||
const openAddress = serverAddress.replace(/:\/\/0.0.0.0/, "://localhost")
|
const openAddress = serverAddress.replace(/:\/\/0.0.0.0/, "://localhost")
|
||||||
await open(openAddress).catch((error: Error) => {
|
await open(openAddress).catch((error: Error) => {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { field, logger } from "@coder/logger"
|
import { field, logger } from "@coder/logger"
|
||||||
|
import { Express } from "express"
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import * as util from "util"
|
import * as util from "util"
|
||||||
import { Args } from "./cli"
|
import { Args } from "./cli"
|
||||||
import { HttpServer } from "./http"
|
|
||||||
import { paths } from "./util"
|
import { paths } from "./util"
|
||||||
|
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||||
|
|
||||||
export type Activate = (httpServer: HttpServer, args: Args) => void
|
export type Activate = (app: Express, args: Args) => void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugins must implement this interface.
|
* Plugins must implement this interface.
|
||||||
|
@ -30,10 +30,10 @@ require("module")._load = function (request: string, parent: object, isMain: boo
|
||||||
/**
|
/**
|
||||||
* Load a plugin and run its activation function.
|
* Load a plugin and run its activation function.
|
||||||
*/
|
*/
|
||||||
const loadPlugin = async (pluginPath: string, httpServer: HttpServer, args: Args): Promise<void> => {
|
const loadPlugin = async (pluginPath: string, app: Express, args: Args): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const plugin: Plugin = require(pluginPath)
|
const plugin: Plugin = require(pluginPath)
|
||||||
plugin.activate(httpServer, args)
|
plugin.activate(app, args)
|
||||||
|
|
||||||
const packageJson = require(path.join(pluginPath, "package.json"))
|
const packageJson = require(path.join(pluginPath, "package.json"))
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -50,12 +50,12 @@ const loadPlugin = async (pluginPath: string, httpServer: HttpServer, args: Args
|
||||||
/**
|
/**
|
||||||
* Load all plugins in the specified directory.
|
* Load all plugins in the specified directory.
|
||||||
*/
|
*/
|
||||||
const _loadPlugins = async (pluginDir: string, httpServer: HttpServer, args: Args): Promise<void> => {
|
const _loadPlugins = async (pluginDir: string, app: Express, args: Args): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const files = await util.promisify(fs.readdir)(pluginDir, {
|
const files = await util.promisify(fs.readdir)(pluginDir, {
|
||||||
withFileTypes: true,
|
withFileTypes: true,
|
||||||
})
|
})
|
||||||
await Promise.all(files.map((file) => loadPlugin(path.join(pluginDir, file.name), httpServer, args)))
|
await Promise.all(files.map((file) => loadPlugin(path.join(pluginDir, file.name), app, args)))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code !== "ENOENT") {
|
if (error.code !== "ENOENT") {
|
||||||
logger.warn(error.message)
|
logger.warn(error.message)
|
||||||
|
@ -68,17 +68,17 @@ const _loadPlugins = async (pluginDir: string, httpServer: HttpServer, args: Arg
|
||||||
* `CS_PLUGIN_PATH` (colon-separated), and individual plugins specified by
|
* `CS_PLUGIN_PATH` (colon-separated), and individual plugins specified by
|
||||||
* `CS_PLUGIN` (also colon-separated).
|
* `CS_PLUGIN` (also colon-separated).
|
||||||
*/
|
*/
|
||||||
export const loadPlugins = async (httpServer: HttpServer, args: Args): Promise<void> => {
|
export const loadPlugins = async (app: Express, args: Args): Promise<void> => {
|
||||||
const pluginPath = process.env.CS_PLUGIN_PATH || `${path.join(paths.data, "plugins")}:/usr/share/code-server/plugins`
|
const pluginPath = process.env.CS_PLUGIN_PATH || `${path.join(paths.data, "plugins")}:/usr/share/code-server/plugins`
|
||||||
const plugin = process.env.CS_PLUGIN || ""
|
const plugin = process.env.CS_PLUGIN || ""
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// Built-in plugins.
|
// Built-in plugins.
|
||||||
_loadPlugins(path.resolve(__dirname, "../../plugins"), httpServer, args),
|
_loadPlugins(path.resolve(__dirname, "../../plugins"), app, args),
|
||||||
// User-added plugins.
|
// User-added plugins.
|
||||||
...pluginPath
|
...pluginPath
|
||||||
.split(":")
|
.split(":")
|
||||||
.filter((p) => !!p)
|
.filter((p) => !!p)
|
||||||
.map((dir) => _loadPlugins(path.resolve(dir), httpServer, args)),
|
.map((dir) => _loadPlugins(path.resolve(dir), app, args)),
|
||||||
// Individual plugins so you don't have to symlink or move them into a
|
// Individual plugins so you don't have to symlink or move them into a
|
||||||
// directory specifically for plugins. This lets you load plugins that are
|
// directory specifically for plugins. This lets you load plugins that are
|
||||||
// on the same level as other directories that are not plugins (if you tried
|
// on the same level as other directories that are not plugins (if you tried
|
||||||
|
@ -87,6 +87,6 @@ export const loadPlugins = async (httpServer: HttpServer, args: Args): Promise<v
|
||||||
...plugin
|
...plugin
|
||||||
.split(":")
|
.split(":")
|
||||||
.filter((p) => !!p)
|
.filter((p) => !!p)
|
||||||
.map((dir) => loadPlugin(path.resolve(dir), httpServer, args)),
|
.map((dir) => loadPlugin(path.resolve(dir), app, args)),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue