import { flags } from '@oclif/command' ; import path from 'path' ; import fs from 'fs' ; import crypto from 'crypto' ; import boxen from 'boxen' ; import chalk from 'chalk' ; import os from 'os' ; import express from 'express' ; import cors from 'cors' ; import JBrowseCommand from '../base' ; function isValidPort ( port ) { return port > 0 && port < 65535 ; } function generateKey ( ) { return crypto . randomBytes ( 5 ) . toString ( 'hex' ) ; } export default class AdminServer extends JBrowseCommand { static description = 'Start up a small admin server for JBrowse configuration' ; static examples = [ '$ jbrowse admin-server' , '$ jbrowse admin-server -p 8888' ] ; static flags = { port : flags . string ( { char : 'p' , description : 'Specifified port to start the server on;\nDefault is 9090.' , } ) , target : flags . string ( { description : 'path to config file in JB2 installation directory to write out to.\nCreates ./config.json if nonexistent' , } ) , out : flags . string ( { description : 'synonym for target' , } ) , help : flags . help ( { char : 'h' } ) , } ; async run ( ) { const { flags : runFlags } = this . parse ( AdminServer ) ; const { target , out } = runFlags ; const output = target || out || '.' ; const isDir = fs . lstatSync ( output ) . isDirectory ( ) ; const outFile = isDir ? `${ output }/config.json` : output ; const baseDir = path . dirname ( outFile ) ; if ( fs . existsSync ( outFile ) ) { this . debug ( `Found existing config file ${ outFile }` ) ; } else { this . debug ( `Creating config file ${ outFile }` ) ; await this . writeJsonFile ( './config.json' , { assemblies : [ ] , configuration : { } , connections : [ ] , defaultSession : { name : 'New Session' , } , tracks : [ ] , } ) ; } let port = 9090 ; if ( runFlags . port ) { if ( ! isValidPort ( parseInt ( runFlags . port , 10 ) ) ) { this . error ( `${ runFlags . port } is not a valid port` ) ; } else { port = parseInt ( runFlags . port , 10 ) ; } } const app = express ( ) ; app . use ( express . static ( '.' ) ) ; app . use ( cors ( ) ) ; app . use ( express . json ( ) ) ; app . post ( '/updateConfig' , async ( req , res ) => { if ( adminKey === req . body . adminKey ) { this . debug ( 'Admin key matches' ) ; try { const filename = req . body . configPath ? path . join ( baseDir , req . body . configPath ) : outFile ; if ( filename . indexOf ( baseDir ) !== 0 ) { throw new Error ( `Cannot perform directory traversal outside of ${ baseDir }` ) ; } await this . writeJsonFile ( filename , req . body . config ) ; res . send ( 'Config written to disk' ) ; } catch ( e ) { res . status ( 500 ) . send ( `Could not write config file ${ e }` ) ; } } else { res . status ( 403 ) . send ( 'Admin key does not match' ) ; } } ) ; app . post ( '/shutdown' , async ( req , res ) => { this . debug ( 'Req body: ' , req . body ) ; if ( req . body . adminKey === adminKey ) { this . debug ( 'Admin key matches' ) ; res . send ( 'Exiting' ) ; server . close ( ) ; } else { res . status ( 403 ) . send ( 'Admin key does not match' ) ; } } ) ; const adminKey = generateKey ( ) ; const server = app . listen ( port ) ; const details = server . address ( ) ; let localAddress = '' ; let networkAddress = '' ; if ( typeof details === 'string' ) { localAddress = details ; } else if ( details && typeof details === 'object' ) { const address = details . address === '::' ? 'localhost' : details . address ; const ip = getNetworkAddress ( ) ; localAddress = `http://${ address }:${ details . port }?adminKey=${ adminKey }` ; if ( ip ) { networkAddress = `http://${ ip }:${ details . port }?adminKey=${ adminKey }` ; } } let message = chalk . green ( 'Now serving JBrowse\nNavigate to the below URL to configure' ) ; if ( localAddress ) { const prefix = networkAddress ? '- ' : '' ; const space = networkAddress ? '            ' : '  ' ; message += `\n\n${ chalk . bold ( `${ prefix }Local:` ) }${ space }${ localAddress }` ; } if ( networkAddress ) { message += `\n${ chalk . bold ( '- On Your Network:' ) }  ${ networkAddress }` ; } this . log ( boxen ( message , { padding : 1 , borderColor : 'blue' , margin : 1 } ) ) ; this . log ( `If you are running yarn start you can launch http://localhost:3000?adminKey=${ adminKey }&adminServer=http://localhost:${ port }/updateConfig` ) ; } } function getNetworkAddress ( ) { for ( const network of Object . values ( os . networkInterfaces ( ) ) ) { for ( const networkInterface of network || [ ] ) { const { address , family , internal } = networkInterface ; if ( family === 'IPv4' && ! internal ) { return address ; } } } return undefined ; }