2019-01-18 21:46:40 +00:00
import { SharedProcessInitMessage } from "@coder/protocol/src/proto" ;
2019-01-15 18:36:09 +00:00
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" ;
2019-01-18 21:46:40 +00:00
import { requireModule } from "./vscode/bootstrapFork" ;
import { createApp } from "./server" ;
import { SharedProcess } from './vscode/sharedProcess' ;
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-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-01-15 18:36:09 +00:00
const { args , flags } = this . parse ( Entry ) ;
2019-01-18 21:46:40 +00:00
if ( flags [ "bootstrap-fork" ] ) {
const module Path = flags [ "bootstrap-fork" ] ;
if ( ! module Path ) {
logger . error ( "No module path specified to fork!" ) ;
process . exit ( 1 ) ;
}
requireModule ( module Path ) ;
return ;
}
const dataDir = flags [ "data-dir" ] || path . join ( os . homedir ( ) , ".vscode-online" ) ;
2019-01-15 18:36:09 +00:00
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 ) ) ;
2019-01-18 21:46:40 +00:00
const sharedProcess = new SharedProcess ( dataDir ) ;
logger . info ( "Starting shared process..." , field ( "socket" , sharedProcess . socketPath ) ) ;
sharedProcess . onWillRestart ( ( ) = > {
logger . info ( "Restarting shared process..." ) ;
sharedProcess . ready . then ( ( ) = > {
logger . info ( "Shared process has restarted!" ) ;
} ) ;
} ) ;
sharedProcess . ready . then ( ( ) = > {
logger . info ( "Shared process has started up!" ) ;
} ) ;
2019-01-15 18:36:09 +00:00
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 ( ) ;
} ) ;
2019-01-18 21:46:40 +00:00
if ( process . env . CLI === "false" || ! process . env . CLI ) {
const webpackConfig = require ( path . join ( __dirname , ".." , ".." , "web" , "webpack.dev.config.js" ) ) ;
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-01-18 21:46:40 +00:00
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
const spm = ( < any > req ) . sharedProcessInit as SharedProcessInitMessage ;
if ( ! spm ) {
logger . warn ( "Received a socket without init data. Not sure how this happened." ) ;
return ;
}
logger . info ( ` WebSocket opened \ u001B[0m ${ req . url } ` , field ( "client" , id ) , field ( "ip" , req . socket . remoteAddress ) , field ( "window_id" , spm . getWindowId ( ) ) , field ( "log_directory" , spm . getLogDirectory ( ) ) ) ;
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 ( " " ) ;
}
2019-01-18 21:46:40 +00:00
2019-01-15 18:36:09 +00:00
}
Entry . run ( undefined , {
2019-01-18 21:46:40 +00:00
root : process.env.BUILD_DIR as string || __dirname ,
2019-01-15 18:36:09 +00:00
//@ts-ignore
} ) . catch ( require ( "@oclif/errors/handle" ) ) ;