2019-08-07 21:18:17 +00:00
import * as cp from "child_process" ;
2019-07-02 21:55:54 +00:00
import * as os from "os" ;
2019-08-21 22:02:31 +00:00
import * as path from "path" ;
2019-08-15 20:53:20 +00:00
import { setUnexpectedErrorHandler } from "vs/base/common/errors" ;
2019-08-07 21:18:17 +00:00
import { main as vsCli } from "vs/code/node/cliProcessMain" ;
2019-07-02 21:55:54 +00:00
import { validatePaths } from "vs/code/node/paths" ;
2019-08-07 21:18:17 +00:00
import { ParsedArgs } from "vs/platform/environment/common/environment" ;
2019-08-09 23:50:05 +00:00
import { buildHelpMessage , buildVersionMessage , Option as VsOption , options as vsOptions } from "vs/platform/environment/node/argv" ;
import { parseMainProcessArgv } from "vs/platform/environment/node/argvHelper" ;
2019-07-02 21:55:54 +00:00
import pkg from "vs/platform/product/node/package" ;
2019-07-16 19:57:02 +00:00
import product from "vs/platform/product/node/product" ;
2019-08-07 21:18:17 +00:00
import { ipcMain } from "vs/server/src/ipc" ;
2019-08-09 23:50:05 +00:00
import { enableCustomMarketplace } from "vs/server/src/marketplace" ;
2019-07-25 22:39:43 +00:00
import { MainServer } from "vs/server/src/server" ;
2019-09-04 21:57:23 +00:00
import { AuthType , buildAllowedMessage , enumToArray , FormatType , generateCertificate , generatePassword , localRequire , open , unpackExecutables } from "vs/server/src/util" ;
2019-07-31 20:22:05 +00:00
const { logger } = localRequire < typeof import ( " @ coder / logger / out / index " ) > ( "@coder/logger/out/index" ) ;
2019-08-15 20:53:20 +00:00
setUnexpectedErrorHandler ( ( error ) = > logger . warn ( error . message ) ) ;
2019-07-02 21:55:54 +00:00
2019-07-02 23:29:48 +00:00
interface Args extends ParsedArgs {
2019-07-23 20:38:00 +00:00
auth? : AuthType ;
2019-07-23 20:17:25 +00:00
"base-path" ? : string ;
2019-07-10 23:33:18 +00:00
cert? : string ;
"cert-key" ? : string ;
2019-09-04 21:57:23 +00:00
format? : string ;
2019-07-10 23:33:18 +00:00
host? : string ;
open? : string ;
2019-07-02 23:29:48 +00:00
port? : string ;
2019-07-10 23:33:18 +00:00
socket? : string ;
2019-07-02 23:29:48 +00:00
}
2019-08-09 23:50:05 +00:00
// @ts-ignore: Force `keyof Args` to work.
interface Option extends VsOption {
id : keyof Args ;
}
2019-08-07 21:18:17 +00:00
const getArgs = ( ) : Args = > {
2019-08-09 23:50:05 +00:00
const options = vsOptions as Option [ ] ;
2019-08-07 21:18:17 +00:00
// The last item is _ which is like -- so our options need to come before it.
const last = options . pop ( ) ! ;
// Remove options that won't work or don't make sense.
let i = options . length ;
while ( i -- ) {
switch ( options [ i ] . id ) {
case "add" :
case "diff" :
case "file-uri" :
case "folder-uri" :
case "goto" :
case "new-window" :
case "reuse-window" :
case "wait" :
case "disable-gpu" :
// TODO: pretty sure these don't work but not 100%.
case "max-memory" :
case "prof-startup" :
case "inspect-extensions" :
case "inspect-brk-extensions" :
options . splice ( i , 1 ) ;
break ;
}
2019-07-10 23:33:18 +00:00
}
2019-08-07 21:18:17 +00:00
options . push ( { id : "base-path" , type : "string" , cat : "o" , description : "Base path of the URL at which code-server is hosted (used for login redirects)." } ) ;
options . push ( { id : "cert" , type : "string" , cat : "o" , description : "Path to certificate. If the path is omitted, both this and --cert-key will be generated." } ) ;
options . push ( { id : "cert-key" , type : "string" , cat : "o" , description : "Path to the certificate's key if one was provided." } ) ;
options . push ( { id : "extra-builtin-extensions-dir" , type : "string" , cat : "o" , description : "Path to an extra builtin extension directory." } ) ;
options . push ( { id : "extra-extensions-dir" , type : "string" , cat : "o" , description : "Path to an extra user extension directory." } ) ;
2019-09-04 21:57:23 +00:00
options . push ( { id : "format" , type : "string" , cat : "o" , description : ` Format for the version. ${ buildAllowedMessage ( FormatType ) } . ` } ) ;
2019-08-07 21:18:17 +00:00
options . push ( { id : "host" , type : "string" , cat : "o" , description : "Host for the server." } ) ;
options . push ( { id : "auth" , type : "string" , cat : "o" , description : ` The type of authentication to use. ${ buildAllowedMessage ( AuthType ) } . ` } ) ;
options . push ( { id : "open" , type : "boolean" , cat : "o" , description : "Open in the browser on startup." } ) ;
options . push ( { id : "port" , type : "string" , cat : "o" , description : "Port for the main server." } ) ;
options . push ( { id : "socket" , type : "string" , cat : "o" , description : "Listen on a socket instead of host:port." } ) ;
2019-07-10 23:33:18 +00:00
2019-08-07 21:18:17 +00:00
options . push ( last ) ;
2019-07-02 23:29:48 +00:00
2019-08-21 22:02:31 +00:00
const args = parseMainProcessArgv ( process . argv ) ;
if ( ! args [ "user-data-dir" ] ) {
args [ "user-data-dir" ] = path . join ( process . env . XDG_DATA_HOME || path . join ( os . homedir ( ) , ".local/share" ) , "code-server" ) ;
}
if ( ! args [ "extensions-dir" ] ) {
args [ "extensions-dir" ] = path . join ( args [ "user-data-dir" ] , "extensions" ) ;
}
return validatePaths ( args ) ;
2019-08-07 21:18:17 +00:00
} ;
2019-07-02 21:55:54 +00:00
2019-08-07 21:18:17 +00:00
const startVscode = async ( ) : Promise < void | void [ ] > = > {
const args = getArgs ( ) ;
2019-07-19 22:43:54 +00:00
const extra = args [ "_" ] || [ ] ;
2019-07-11 22:12:52 +00:00
const options = {
2019-07-23 20:38:00 +00:00
auth : args.auth ,
2019-07-23 20:17:25 +00:00
basePath : args [ "base-path" ] ,
2019-07-12 20:21:00 +00:00
cert : args.cert ,
certKey : args [ "cert-key" ] ,
2019-07-19 22:43:54 +00:00
folderUri : extra.length > 1 ? extra [ extra . length - 1 ] : undefined ,
host : args.host ,
2019-07-12 20:21:00 +00:00
password : process.env.PASSWORD ,
2019-07-11 22:12:52 +00:00
} ;
2019-08-09 23:50:05 +00:00
if ( options . auth && enumToArray ( AuthType ) . filter ( ( t ) = > t === options . auth ) . length === 0 ) {
2019-07-23 20:38:00 +00:00
throw new Error ( ` ' ${ options . auth } ' is not a valid authentication type. ` ) ;
} else if ( options . auth && ! options . password ) {
options . password = await generatePassword ( ) ;
2019-07-12 20:21:00 +00:00
}
2019-07-23 20:38:00 +00:00
if ( ! options . certKey && typeof options . certKey !== "undefined" ) {
throw new Error ( ` --cert-key cannot be blank ` ) ;
} else if ( options . certKey && ! options . cert ) {
throw new Error ( ` --cert-key was provided but --cert was not ` ) ;
} if ( ! options . cert && typeof options . cert !== "undefined" ) {
2019-07-11 22:12:52 +00:00
const { cert , certKey } = await generateCertificate ( ) ;
options . cert = cert ;
options . certKey = certKey ;
}
2019-08-09 23:50:05 +00:00
enableCustomMarketplace ( ) ;
2019-08-07 21:18:17 +00:00
2019-07-11 22:12:52 +00:00
const server = new MainServer ( {
. . . options ,
2019-08-08 16:21:45 +00:00
port : typeof args . port !== "undefined" && parseInt ( args . port , 10 ) || 8080 ,
2019-07-11 22:12:52 +00:00
socket : args.socket ,
2019-07-24 00:06:40 +00:00
} , args ) ;
2019-07-11 22:12:52 +00:00
2019-07-24 00:06:40 +00:00
const [ serverAddress , /* ignore */ ] = await Promise . all ( [
2019-07-17 00:26:05 +00:00
server . listen ( ) ,
unpackExecutables ( ) ,
2019-07-05 15:54:44 +00:00
] ) ;
2019-07-31 20:22:05 +00:00
logger . info ( ` Server listening on ${ serverAddress } ` ) ;
2019-07-12 20:21:00 +00:00
2019-07-23 20:38:00 +00:00
if ( options . auth && ! process . env . PASSWORD ) {
2019-07-31 20:22:05 +00:00
logger . info ( ` - Password is ${ options . password } ` ) ;
logger . info ( " - To use your own password, set the PASSWORD environment variable" ) ;
2019-07-12 20:21:00 +00:00
} else if ( options . auth ) {
2019-07-31 20:22:05 +00:00
logger . info ( " - Using custom password for authentication" ) ;
2019-07-12 20:21:00 +00:00
} else {
2019-07-31 20:22:05 +00:00
logger . info ( " - No authentication" ) ;
2019-07-12 20:21:00 +00:00
}
2019-07-23 20:38:00 +00:00
if ( server . protocol === "https" ) {
2019-07-31 20:22:05 +00:00
logger . info (
2019-07-23 20:38:00 +00:00
args . cert
? ` - Using provided certificate ${ args [ "cert-key" ] ? " and key" : "" } for HTTPS `
: ` - Using generated certificate and key for HTTPS ` ,
2019-07-12 20:21:00 +00:00
) ;
} else {
2019-07-31 20:22:05 +00:00
logger . info ( " - Not serving HTTPS" ) ;
2019-07-12 20:21:00 +00:00
}
2019-07-15 18:31:05 +00:00
2019-07-23 20:38:00 +00:00
if ( ! server . options . socket && args . open ) {
2019-08-07 21:18:17 +00:00
// The web socket doesn't seem to work if browsing with 0.0.0.0.
2019-07-19 22:43:54 +00:00
const openAddress = ` http://localhost: ${ server . options . port } ` ;
2019-07-17 00:23:58 +00:00
await open ( openAddress ) . catch ( console . error ) ;
2019-07-31 20:22:05 +00:00
logger . info ( ` - Opened ${ openAddress } ` ) ;
2019-07-15 18:31:05 +00:00
}
2019-07-02 21:55:54 +00:00
} ;
2019-08-07 21:18:17 +00:00
const startCli = ( ) : boolean | Promise < void > = > {
const args = getArgs ( ) ;
if ( args . help ) {
const executable = ` ${ product . applicationName } ${ os . platform ( ) === "win32" ? ".exe" : "" } ` ;
console . log ( buildHelpMessage ( product . nameLong , executable , pkg . codeServerVersion , undefined , false ) ) ;
return true ;
}
if ( args . version ) {
2019-09-04 21:57:23 +00:00
if ( args . format === "json" ) {
console . log ( JSON . stringify ( {
codeServerVersion : pkg.codeServerVersion ,
commit : product.commit ,
vscodeVersion : pkg.version ,
} ) ) ;
} else {
buildVersionMessage ( pkg . codeServerVersion , product . commit ) . split ( "\n" ) . map ( ( line ) = > logger . info ( line ) ) ;
}
2019-08-07 21:18:17 +00:00
return true ;
}
const shouldSpawnCliProcess = ( ) : boolean = > {
return ! ! args [ "install-source" ]
|| ! ! args [ "list-extensions" ]
|| ! ! args [ "install-extension" ]
|| ! ! args [ "uninstall-extension" ]
|| ! ! args [ "locate-extension" ]
|| ! ! args [ "telemetry" ] ;
} ;
if ( shouldSpawnCliProcess ( ) ) {
2019-08-09 23:50:05 +00:00
enableCustomMarketplace ( ) ;
2019-08-07 21:18:17 +00:00
return vsCli ( args ) ;
}
return false ;
} ;
export class WrapperProcess {
private process? : cp.ChildProcess ;
private started? : Promise < void > ;
public constructor ( ) {
ipcMain . onMessage ( async ( message ) = > {
switch ( message ) {
case "relaunch" :
logger . info ( "Relaunching..." ) ;
this . started = undefined ;
if ( this . process ) {
this . process . kill ( ) ;
}
try {
await this . start ( ) ;
} catch ( error ) {
logger . error ( error . message ) ;
process . exit ( typeof error . code === "number" ? error.code : 1 ) ;
}
break ;
default :
logger . error ( ` Unrecognized message ${ message } ` ) ;
break ;
}
} ) ;
}
public start ( ) : Promise < void > {
if ( ! this . started ) {
const child = this . spawn ( ) ;
this . started = ipcMain . handshake ( child ) ;
this . process = child ;
}
return this . started ;
}
private spawn ( ) : cp . ChildProcess {
return cp . spawn ( process . argv [ 0 ] , process . argv . slice ( 1 ) , {
env : {
. . . process . env ,
LAUNCH_VSCODE : "true" ,
} ,
stdio : [ "inherit" , "inherit" , "inherit" , "ipc" ] ,
} ) ;
}
}
const main = async ( ) : Promise < boolean | void | void [ ] > = > {
if ( process . env . LAUNCH_VSCODE ) {
await ipcMain . handshake ( ) ;
return startVscode ( ) ;
}
return startCli ( ) || new WrapperProcess ( ) . start ( ) ;
} ;
2019-08-12 20:30:07 +00:00
// It's possible that the pipe has closed (for example if you run code-server
// --version | head -1). Assume that means we're done.
if ( ! process . stdout . isTTY ) {
process . stdout . on ( "error" , ( ) = > process . exit ( ) ) ;
}
2019-07-02 21:55:54 +00:00
main ( ) . catch ( ( error ) = > {
2019-08-07 21:18:17 +00:00
logger . error ( error . message ) ;
process . exit ( typeof error . code === "number" ? error.code : 1 ) ;
2019-07-02 21:55:54 +00:00
} ) ;