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:
6
packages/server/.gitignore
vendored
Normal file
6
packages/server/.gitignore
vendored
Normal 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
|
28
packages/server/package.json
Normal file
28
packages/server/package.json
Normal 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"
|
||||
}
|
||||
}
|
46
packages/server/scripts/nexe.js
Normal file
46
packages/server/scripts/nexe.js
Normal 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
|
||||
*/
|
81
packages/server/src/cli.ts
Normal file
81
packages/server/src/cli.ts
Normal 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"));
|
46
packages/server/src/server.ts
Normal file
46
packages/server/src/server.ts
Normal 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,
|
||||
};
|
||||
};
|
48
packages/server/webpack.config.js
Normal file
48
packages/server/webpack.config.js
Normal 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
3694
packages/server/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user