2019-01-30 21:40:01 +00:00
|
|
|
import { field, logger } from "@coder/logger";
|
2019-01-18 23:08:44 +00:00
|
|
|
import { ServerMessage, SharedProcessActiveMessage } from "@coder/protocol/src/proto";
|
2019-01-15 18:36:09 +00:00
|
|
|
import { Command, flags } from "@oclif/command";
|
|
|
|
import * as fs from "fs";
|
|
|
|
import * as os from "os";
|
|
|
|
import * as path from "path";
|
2019-01-18 23:08:44 +00:00
|
|
|
import * as WebSocket from "ws";
|
2019-01-18 21:46:40 +00:00
|
|
|
import { createApp } from "./server";
|
2019-01-18 23:08:44 +00:00
|
|
|
import { requireModule } from "./vscode/bootstrapFork";
|
2019-01-22 18:27:59 +00:00
|
|
|
import { SharedProcess, SharedProcessState } from "./vscode/sharedProcess";
|
2019-02-06 00:08:48 +00:00
|
|
|
import { setup as setupNativeModules } from "./modules";
|
|
|
|
import { fillFs } from "./fill";
|
2019-02-07 17:47:00 +00:00
|
|
|
import { isCli, serveStatic, buildDir } from "./constants";
|
2019-01-15 18:36:09 +00:00
|
|
|
|
|
|
|
export class Entry extends Command {
|
|
|
|
public static description = "Start your own self-hosted browser-accessible VS Code";
|
|
|
|
public static flags = {
|
|
|
|
cert: flags.string(),
|
|
|
|
"cert-key": flags.string(),
|
|
|
|
"data-dir": flags.string({ char: "d" }),
|
|
|
|
help: flags.help(),
|
|
|
|
host: flags.string({ char: "h", default: "0.0.0.0" }),
|
|
|
|
open: flags.boolean({ char: "o", description: "Open in browser on startup" }),
|
|
|
|
port: flags.integer({ char: "p", default: 8080, description: "Port to bind on" }),
|
|
|
|
version: flags.version({ char: "v" }),
|
2019-01-18 21:46:40 +00:00
|
|
|
|
|
|
|
// Dev flags
|
|
|
|
"bootstrap-fork": flags.string({ hidden: true }),
|
2019-01-26 00:18:21 +00:00
|
|
|
env: flags.string({ hidden: true }),
|
2019-02-19 16:17:03 +00:00
|
|
|
args: flags.string({ hidden: true }),
|
2019-01-15 18:36:09 +00:00
|
|
|
};
|
|
|
|
public static args = [{
|
|
|
|
name: "workdir",
|
|
|
|
description: "Specify working dir",
|
2019-01-18 21:46:40 +00:00
|
|
|
default: (): string => process.cwd(),
|
2019-01-15 18:36:09 +00:00
|
|
|
}];
|
|
|
|
|
|
|
|
public async run(): Promise<void> {
|
2019-01-18 21:46:40 +00:00
|
|
|
try {
|
|
|
|
/**
|
|
|
|
* Suuuper janky
|
|
|
|
* Comes from - https://github.com/nexe/nexe/issues/524
|
|
|
|
* Seems to cleanup by removing this path immediately
|
|
|
|
* If any native module is added its assumed this pathname
|
|
|
|
* will change.
|
|
|
|
*/
|
|
|
|
require("spdlog");
|
|
|
|
const nodePath = path.join(process.cwd(), "e91a410b");
|
|
|
|
fs.unlinkSync(path.join(nodePath, "spdlog.node"));
|
|
|
|
fs.rmdirSync(nodePath);
|
|
|
|
} catch (ex) {
|
|
|
|
logger.warn("Failed to remove extracted dependency.", field("dependency", "spdlog"), field("error", ex.message));
|
|
|
|
}
|
|
|
|
|
2019-02-07 17:47:00 +00:00
|
|
|
if (isCli) {
|
2019-02-05 17:15:20 +00:00
|
|
|
fillFs();
|
|
|
|
}
|
|
|
|
|
2019-01-15 18:36:09 +00:00
|
|
|
const { args, flags } = this.parse(Entry);
|
|
|
|
|
2019-02-07 17:47:00 +00:00
|
|
|
const builtInExtensionsDir = path.join(buildDir || path.join(__dirname, ".."), "build/extensions");
|
2019-01-18 21:46:40 +00:00
|
|
|
if (flags["bootstrap-fork"]) {
|
|
|
|
const modulePath = flags["bootstrap-fork"];
|
|
|
|
if (!modulePath) {
|
|
|
|
logger.error("No module path specified to fork!");
|
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
2019-02-19 16:17:03 +00:00
|
|
|
Object.assign(process.env, flags.env ? JSON.parse(flags.env) : {});
|
|
|
|
((flags.args ? JSON.parse(flags.args) : []) as string[]).forEach((arg, i) => {
|
|
|
|
// [0] contains the binary running the script (`node` for example) and
|
|
|
|
// [1] contains the script name, so the arguments come after that.
|
|
|
|
process.argv[i + 2] = arg;
|
|
|
|
});
|
|
|
|
|
2019-02-05 17:15:20 +00:00
|
|
|
return requireModule(modulePath, builtInExtensionsDir);
|
2019-01-18 21:46:40 +00:00
|
|
|
}
|
|
|
|
|
2019-02-05 21:26:57 +00:00
|
|
|
const dataDir = flags["data-dir"] || path.join(os.homedir(), ".vscode-remote");
|
2019-01-15 18:36:09 +00:00
|
|
|
const workingDir = args["workdir"];
|
|
|
|
|
2019-02-07 17:47:00 +00:00
|
|
|
if (buildDir && buildDir.startsWith(workingDir)) {
|
|
|
|
logger.error("Cannot run binary inside of BUILD_DIR", field("build_dir", buildDir), field("cwd", process.cwd()));
|
2019-01-28 17:14:06 +00:00
|
|
|
process.exit(1);
|
|
|
|
}
|
|
|
|
|
2019-02-05 17:15:20 +00:00
|
|
|
if (!fs.existsSync(dataDir)) {
|
|
|
|
fs.mkdirSync(dataDir);
|
|
|
|
}
|
|
|
|
setupNativeModules(dataDir);
|
|
|
|
|
2019-01-28 17:14:06 +00:00
|
|
|
const logDir = path.join(dataDir, "logs", new Date().toISOString().replace(/[-:.TZ]/g, ""));
|
|
|
|
process.env.VSCODE_LOGS = logDir;
|
|
|
|
|
2019-01-15 18:36:09 +00:00
|
|
|
logger.info("\u001B[1mvscode-remote v1.0.0");
|
|
|
|
// TODO: fill in appropriate doc url
|
|
|
|
logger.info("Additional documentation: https://coder.com/docs");
|
2019-01-28 17:14:06 +00:00
|
|
|
logger.info("Initializing", field("data-dir", dataDir), field("working-dir", workingDir), field("log-dir", logDir));
|
2019-02-05 17:15:20 +00:00
|
|
|
const sharedProcess = new SharedProcess(dataDir, builtInExtensionsDir);
|
2019-01-22 18:44:03 +00:00
|
|
|
const sendSharedProcessReady = (socket: WebSocket): void => {
|
2019-01-18 23:08:44 +00:00
|
|
|
const active = new SharedProcessActiveMessage();
|
|
|
|
active.setSocketPath(sharedProcess.socketPath);
|
2019-01-28 17:14:06 +00:00
|
|
|
active.setLogPath(logDir);
|
2019-01-18 23:08:44 +00:00
|
|
|
const serverMessage = new ServerMessage();
|
|
|
|
serverMessage.setSharedProcessActive(active);
|
|
|
|
socket.send(serverMessage.serializeBinary());
|
|
|
|
};
|
|
|
|
sharedProcess.onState((event) => {
|
2019-02-07 00:11:31 +00:00
|
|
|
if (event.state === SharedProcessState.Ready) {
|
2019-01-18 23:08:44 +00:00
|
|
|
app.wss.clients.forEach((c) => sendSharedProcessReady(c));
|
|
|
|
}
|
2019-01-18 21:46:40 +00:00
|
|
|
});
|
2019-01-15 18:36:09 +00:00
|
|
|
|
|
|
|
const app = createApp((app) => {
|
|
|
|
app.use((req, res, next) => {
|
|
|
|
res.on("finish", () => {
|
2019-02-19 16:17:03 +00:00
|
|
|
logger.trace(`\u001B[1m${req.method} ${res.statusCode} \u001B[0m${req.url}`, field("host", req.hostname), field("ip", req.ip));
|
2019-01-15 18:36:09 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
next();
|
|
|
|
});
|
2019-02-07 19:50:17 +00:00
|
|
|
// If we're not running from the binary and we aren't serving the static
|
|
|
|
// pre-built version, use webpack to serve the web files.
|
2019-02-07 17:47:00 +00:00
|
|
|
if (!isCli && !serveStatic) {
|
2019-02-05 21:26:57 +00:00
|
|
|
const webpackConfig = require(path.join(__dirname, "..", "..", "web", "webpack.config.js"));
|
2019-01-18 21:46:40 +00:00
|
|
|
const compiler = require("webpack")(webpackConfig);
|
|
|
|
app.use(require("webpack-dev-middleware")(compiler, {
|
|
|
|
logger,
|
|
|
|
publicPath: webpackConfig.output.publicPath,
|
|
|
|
stats: webpackConfig.stats,
|
|
|
|
}));
|
|
|
|
app.use(require("webpack-hot-middleware")(compiler));
|
|
|
|
}
|
2019-01-15 18:36:09 +00:00
|
|
|
}, {
|
2019-02-06 18:00:01 +00:00
|
|
|
builtInExtensionsDirectory: builtInExtensionsDir,
|
|
|
|
dataDirectory: dataDir,
|
|
|
|
workingDirectory: workingDir,
|
|
|
|
});
|
2019-01-15 18:36:09 +00:00
|
|
|
|
2019-01-18 21:46:40 +00:00
|
|
|
logger.info("Starting webserver...", field("host", flags.host), field("port", flags.port));
|
2019-01-15 18:36:09 +00:00
|
|
|
app.server.listen(flags.port, flags.host);
|
|
|
|
let clientId = 1;
|
|
|
|
app.wss.on("connection", (ws, req) => {
|
|
|
|
const id = clientId++;
|
2019-01-18 21:46:40 +00:00
|
|
|
|
2019-01-22 17:48:01 +00:00
|
|
|
if (sharedProcess.state === SharedProcessState.Ready) {
|
|
|
|
sendSharedProcessReady(ws);
|
|
|
|
}
|
2019-01-18 23:08:44 +00:00
|
|
|
|
|
|
|
logger.info(`WebSocket opened \u001B[0m${req.url}`, field("client", id), field("ip", req.socket.remoteAddress));
|
2019-01-15 18:36:09 +00:00
|
|
|
|
|
|
|
ws.on("close", (code) => {
|
|
|
|
logger.info(`WebSocket closed \u001B[0m${req.url}`, field("client", id), field("code", code));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!flags["cert-key"] && !flags.cert) {
|
|
|
|
logger.warn("No certificate specified. \u001B[1mThis could be insecure.");
|
|
|
|
// TODO: fill in appropriate doc url
|
|
|
|
logger.warn("Documentation on securing your setup: https://coder.com/docs");
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info(" ");
|
|
|
|
logger.info("Password:\u001B[1m 023450wf09");
|
|
|
|
logger.info(" ");
|
|
|
|
logger.info("Started (click the link below to open):");
|
|
|
|
logger.info(`http://localhost:${flags.port}/`);
|
|
|
|
logger.info(" ");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Entry.run(undefined, {
|
2019-02-07 17:47:00 +00:00
|
|
|
root: buildDir || __dirname,
|
2019-01-15 18:36:09 +00:00
|
|
|
//@ts-ignore
|
|
|
|
}).catch(require("@oclif/errors/handle"));
|