Archived
1
0

Create initial server layout (#11)

* Create initial server layout

* Adjust command name to entry

* Add @oclif/config as dependency

* Implement build process for outputting single binary

* Add init message

* Remove unused import, add tsconfig.json to .gitignore

* Accidently pushed wacky change to output host FS files

* Add options to createApp
This commit is contained in:
Kyle Carberry
2019-01-15 12:36:09 -06:00
parent 2ff34bc5e2
commit 05899b5edf
25 changed files with 4646 additions and 222 deletions

6
packages/server/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
out
cli*
# This file is generated when the binary is created.
# We want to use the parent tsconfig so we can ignore it.
tsconfig.json

View File

@ -0,0 +1,28 @@
{
"name": "server",
"main": "./out/cli.js",
"bin": "./out/cli.js",
"files": [],
"scripts": {
"start": "ts-node -r tsconfig-paths/register src/cli.ts",
"build:webpack": "rm -rf ./out && ../../node_modules/.bin/webpack --config ./webpack.config.js",
"build:nexe": "node scripts/nexe.js",
"build": "npm run build:webpack && npm run build:nexe"
},
"dependencies": {
"@oclif/config": "^1.10.4",
"@oclif/errors": "^1.2.2",
"@oclif/plugin-help": "^2.1.4",
"express": "^4.16.4",
"nexe": "^2.0.0-rc.34",
"node-pty": "^0.8.0",
"ws": "^6.1.2"
},
"devDependencies": {
"@types/express": "^4.16.0",
"@types/ws": "^6.0.1",
"string-replace-webpack-plugin": "^0.1.3",
"ts-node": "^7.0.1",
"tsconfig-paths": "^3.7.0"
}
}

View File

@ -0,0 +1,46 @@
const fs = require("fs");
const os = require("os");
const path = require("path");
const nexe = require("nexe");
const nexeRoot = path.join(os.homedir(), ".nexe");
if (!fs.existsSync(nexeRoot)) {
throw new Error("run nexe manually on a binary to initialize it");
}
const listed = fs.readdirSync(nexeRoot);
listed.forEach((list) => {
if (list.startsWith("linux")) {
const stat = fs.statSync(path.join(nexeRoot, list));
if (stat.isFile()) {
if (stat.size > 20000000) {
throw new Error("must use upx to shrink node binary in ~/.nexe/" + list);
}
}
}
});
nexe.compile({
debugBundle: true,
input: path.join(__dirname, "../out/cli.js"),
output: 'cli',
native: {
"node-pty": {
additionalFiles: [
'./node_modules/node-pty/build/Release/pty',
],
}
},
targets: ["linux"],
/**
* To include native extensions, do NOT install node_modules for each one. They
* are not required as each extension is built using webpack.
*/
resources: [path.join(__dirname, "../package.json")],
});
/**
* Notes for tmrw
*
* `upx ~/.nexe/linux` <- node binary before compiling with nexe
* Use `testing.js` for bundling with nexe to build
*/

View File

@ -0,0 +1,81 @@
import { Command, flags } from "@oclif/command";
import { logger, field } from "@coder/logger";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { createApp } from './server';
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" }),
};
public static args = [{
name: "workdir",
description: "Specify working dir",
default: () => process.cwd(),
}];
public async run(): Promise<void> {
const { args, flags } = this.parse(Entry);
const dataDir = flags["data-dir"] || path.join(os.homedir(), `.vscode-online`);
const workingDir = args["workdir"];
logger.info("\u001B[1mvscode-remote v1.0.0");
// TODO: fill in appropriate doc url
logger.info("Additional documentation: https://coder.com/docs");
logger.info("Initializing", field("data-dir", dataDir), field("working-dir", workingDir));
const app = createApp((app) => {
app.use((req, res, next) => {
res.on("finish", () => {
logger.info(`\u001B[1m${req.method} ${res.statusCode} \u001B[0m${req.url}`, field("host", req.hostname), field("ip", req.ip));
});
next();
});
}, {
dataDirectory: dataDir,
workingDirectory: workingDir,
});
logger.info("Starting webserver...", field("host", flags.host), field("port", flags.port))
app.server.listen(flags.port, flags.host);
let clientId = 1;
app.wss.on("connection", (ws, req) => {
const id = clientId++;
logger.info(`WebSocket opened \u001B[0m${req.url}`, field("client", id), field("ip", req.socket.remoteAddress));
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, {
root: process.env.BUILD_DIR as string,
//@ts-ignore
}).catch(require("@oclif/errors/handle"));

View File

@ -0,0 +1,46 @@
import { ReadWriteConnection } from "@coder/protocol";
import { Server, ServerOptions } from "@coder/protocol/src/node/server";
import * as express from "express";
import * as http from "http";
import * as ws from "ws";
export const createApp = (registerMiddleware?: (app: express.Application) => void, options?: ServerOptions): {
readonly express: express.Application;
readonly server: http.Server;
readonly wss: ws.Server;
} => {
const app = express();
if (registerMiddleware) {
registerMiddleware(app);
}
const server = http.createServer(app);
const wss = new ws.Server({ server });
wss.on("connection", (ws: WebSocket) => {
const connection: ReadWriteConnection = {
onMessage: (cb) => {
ws.addEventListener("message", (event) => cb(event.data));
},
close: () => ws.close(),
send: (data) => ws.send(data),
onClose: (cb) => ws.addEventListener("close", () => cb()),
};
const server = new Server(connection, options);
});
/**
* We should static-serve the `web` package at this point
*/
app.get("/", (req, res, next) => {
res.write("Example! :)");
res.status(200);
res.end();
});
return {
express: app,
server,
wss,
};
};

View File

@ -0,0 +1,48 @@
const path = require("path");
const webpack = require("webpack");
const merge = require("webpack-merge");
const StringReplacePlugin = require("string-replace-webpack-plugin");
module.exports = merge({
devtool: 'none',
module: {
rules: [
{
test: /@oclif\/command\/lib\/index\.js/,
loader: StringReplacePlugin.replace({
replacements: [
{
// This is required otherwise it attempts to require("package.json")
pattern: /checkNodeVersion\(\)\;/,
replacement: () => / /,
}
]
}),
},
],
},
output: {
filename: "cli.js",
path: path.join(__dirname, "./out"),
libraryTarget: "commonjs",
},
node: {
console: false,
global: false,
process: false,
Buffer: false,
__filename: false,
__dirname: false,
setImmediate: false
},
externals: ["node-pty"],
entry: "./packages/server/src/cli.ts",
target: "node",
plugins: [
new webpack.DefinePlugin({
"process.env.BUILD_DIR": `"${__dirname}"`,
}),
],
}, require("../../scripts/webpack.general.config"), {
mode: "development",
});

3694
packages/server/yarn.lock Normal file

File diff suppressed because it is too large Load Diff