main.ts 5.8 KB
Newer Older
A
Asher 已提交
1 2
import { field, logger } from "@coder/logger"
import http from "http"
3
import path from "path"
4
import { Disposable } from "../common/emitter"
5
import { plural } from "../common/util"
A
Asher 已提交
6
import { createApp, ensureAddress } from "./app"
T
Teffen 已提交
7
import { AuthType, DefaultedArgs, Feature, UserProvidedArgs } from "./cli"
A
Asher 已提交
8
import { coderCloudBind } from "./coder_cloud"
T
Teffen 已提交
9
import { commit, version } from "./constants"
A
Asher 已提交
10
import { register } from "./routes"
11
import { humanPath, isFile, loadAMDModule, open } from "./util"
A
Asher 已提交
12

T
Teffen 已提交
13 14 15 16 17 18 19 20 21
/**
 * Return true if the user passed an extension-related VS Code flag.
 */
export const shouldSpawnCliProcess = (args: UserProvidedArgs): boolean => {
  return (
    !!args["list-extensions"] ||
    !!args["install-extension"] ||
    !!args["uninstall-extension"] ||
    !!args["locate-extension"]
22 23 24
  )
}

25 26 27 28 29
/**
 * This is useful when an CLI arg should be passed to VS Code directly,
 * such as when managing extensions.
 * @deprecated This should be removed when code-server merges with lib/vscode.
 */
T
Teffen 已提交
30
export const runVsCodeCli = async (args: DefaultedArgs): Promise<void> => {
31 32
  logger.debug("Running VS Code CLI")

T
Teffen 已提交
33 34
  // See ../../vendor/modules/code-oss-dev/src/vs/server/main.js.
  const spawnCli = await loadAMDModule<CodeServerLib.SpawnCli>("vs/server/remoteExtensionHostAgent", "spawnCli")
35 36

  try {
T
Teffen 已提交
37 38 39 40 41
    await spawnCli({
      ...args,
      // For some reason VS Code takes the port as a string.
      port: typeof args.port !== "undefined" ? args.port.toString() : undefined,
    })
42 43
  } catch (error: any) {
    logger.error("Got error from VS Code", error)
44 45 46
  }

  process.exit(0)
A
Asher 已提交
47 48 49
}

export const openInExistingInstance = async (args: DefaultedArgs, socketPath: string): Promise<void> => {
50
  const pipeArgs: CodeServerLib.OpenCommandPipeArgs & { fileURIs: string[] } = {
A
Asher 已提交
51 52 53 54 55 56
    type: "open",
    folderURIs: [],
    fileURIs: [],
    forceReuseWindow: args["reuse-window"],
    forceNewWindow: args["new-window"],
  }
T
Teffen 已提交
57 58 59
  const paths = args._ || []
  for (let i = 0; i < paths.length; i++) {
    const fp = path.resolve(paths[i])
A
Asher 已提交
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    if (await isFile(fp)) {
      pipeArgs.fileURIs.push(fp)
    } else {
      pipeArgs.folderURIs.push(fp)
    }
  }
  if (pipeArgs.forceNewWindow && pipeArgs.fileURIs.length > 0) {
    logger.error("--new-window can only be used with folder paths")
    process.exit(1)
  }
  if (pipeArgs.folderURIs.length === 0 && pipeArgs.fileURIs.length === 0) {
    logger.error("Please specify at least one file or folder")
    process.exit(1)
  }
  const vscode = http.request(
    {
      path: "/",
      method: "POST",
      socketPath,
    },
    (response) => {
      response.on("data", (message) => {
        logger.debug("got message from VS Code", field("message", message.toString()))
      })
    },
  )
  vscode.on("error", (error: unknown) => {
    logger.error("got error from VS Code", field("error", error))
  })
  vscode.write(JSON.stringify(pipeArgs))
  vscode.end()
}

93 94 95
export const runCodeServer = async (
  args: DefaultedArgs,
): Promise<{ dispose: Disposable["dispose"]; server: http.Server }> => {
A
Asher 已提交
96 97 98 99 100 101 102 103 104 105 106
  logger.info(`code-server ${version} ${commit}`)

  logger.info(`Using user-data-dir ${humanPath(args["user-data-dir"])}`)
  logger.trace(`Using extensions-dir ${humanPath(args["extensions-dir"])}`)

  if (args.auth === AuthType.Password && !args.password && !args["hashed-password"]) {
    throw new Error(
      "Please pass in a password via the config file or environment variable ($PASSWORD or $HASHED_PASSWORD)",
    )
  }

107
  const app = await createApp(args)
T
Teffen 已提交
108 109
  const protocol = args.cert ? "https" : "http"
  const serverAddress = ensureAddress(app.server, protocol)
110
  const disposeRoutes = await register(app, args)
A
Asher 已提交
111 112

  logger.info(`Using config file ${humanPath(args.config)}`)
T
Teffen 已提交
113 114 115 116 117 118
  logger.info(
    `${protocol.toUpperCase()} server listening on ${serverAddress.toString()} ${
      args.link ? "(randomized by --link)" : ""
    }`,
  )

A
Asher 已提交
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
  if (args.auth === AuthType.Password) {
    logger.info("  - Authentication is enabled")
    if (args.usingEnvPassword) {
      logger.info("    - Using password from $PASSWORD")
    } else if (args.usingEnvHashedPassword) {
      logger.info("    - Using password from $HASHED_PASSWORD")
    } else {
      logger.info(`    - Using password from ${humanPath(args.config)}`)
    }
  } else {
    logger.info(`  - Authentication is disabled ${args.link ? "(disabled by --link)" : ""}`)
  }

  if (args.cert) {
    logger.info(`  - Using certificate for HTTPS: ${humanPath(args.cert.value)}`)
  } else {
    logger.info(`  - Not serving HTTPS ${args.link ? "(disabled by --link)" : ""}`)
  }

  if (args["proxy-domain"].length > 0) {
    logger.info(`  - ${plural(args["proxy-domain"].length, "Proxying the following domain")}:`)
    args["proxy-domain"].forEach((domain) => logger.info(`    - *.${domain}`))
  }

  if (args.link) {
144
    await coderCloudBind(serverAddress, args.link.value)
A
Asher 已提交
145
    logger.info("  - Connected to cloud agent")
A
Asher 已提交
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
  }

  if (args.enable && args.enable.length > 0) {
    logger.info("Enabling the following experimental features:")
    args.enable.forEach((feature) => {
      if (Object.values(Feature).includes(feature as Feature)) {
        logger.info(`  - "${feature}"`)
      } else {
        logger.error(`  X "${feature}" (unknown feature)`)
      }
    })
    // TODO: Could be nice to add wrapping to the logger?
    logger.info(
      "  The code-server project does not provide stability guarantees or commit to fixing bugs relating to these experimental features. When filing bug reports, please ensure that you can reproduce the bug with all experimental features turned off.",
    )
  }

163
  if (args.open) {
A
Asher 已提交
164
    try {
165 166
      await open(serverAddress)
      logger.info(`Opened ${serverAddress}`)
A
Asher 已提交
167
    } catch (error) {
168
      logger.error("Failed to open", field("address", serverAddress.toString()), field("error", error))
A
Asher 已提交
169 170
    }
  }
171

172 173 174 175 176 177 178
  return {
    server: app.server,
    dispose: async () => {
      disposeRoutes()
      await app.dispose()
    },
  }
A
Asher 已提交
179
}