blob: caa2d3bdb2f187226a3631aa130d515a2447c2ec [file] [log] [blame]
var util = require('util')
var os = require('os')
var program = require('commander')
var Promise = require('bluebird')
var ip = require('my-local-ip')
var pkg = require('../package')
var cliutil = require('./util/cliutil')
var logger = require('./util/logger')
var path = require('path')
global.appRoot = path.resolve(__dirname);
Promise.longStackTraces()
program
.version(pkg.version)
program
.command('provider [serial...]')
.description('start provider')
.option('-s, --connect-sub <endpoint>'
, 'sub endpoint'
, cliutil.list)
.option('-p, --connect-push <endpoint>'
, 'push endpoint'
, cliutil.list)
.option('-n, --name <name>'
, 'name (or os.hostname())'
, String
, os.hostname())
.option('--min-port <port>'
, 'minimum port number for worker use'
, Number
, 7400)
.option('--max-port <port>'
, 'maximum port number for worker use'
, Number
, 7700)
.option('--public-ip <ip>'
, 'public ip for global access'
, String
, ip())
.option('-t, --group-timeout <seconds>'
, 'group timeout'
, Number
, 900)
.option('-r, --storage-url <url>'
, 'URL to storage client'
, String)
.option('--heartbeat-interval <ms>'
, 'heartbeat interval'
, Number
, 10000)
.option('--adb-host <host>'
, 'ADB host (defaults to 127.0.0.1)'
, String
, '127.0.0.1')
.option('--adb-port <port>'
, 'ADB port (defaults to 5037)'
, Number
, 5037)
.option('-R, --allow-remote'
, 'Whether to allow remote devices to be set up')
.option('--screen-ws-url-pattern <pattern>'
, 'screen WebSocket URL pattern'
, String
, 'ws://${publicIp}:${publicPort}')
.option('--connect-url-pattern <pattern>'
, 'adb connect URL pattern'
, String
, '${publicIp}:${publicPort}')
.option('--vnc-initial-size <size>'
, 'initial VNC size'
, cliutil.size
, [600, 800])
.option('--mute-master'
, 'whether to mute master volume when devices are being used')
.option('--lock-rotation'
, 'whether to lock rotation when devices are being used')
.action(function(serials, options) {
if (!options.connectSub) {
this.missingArgument('--connect-sub')
}
if (!options.connectPush) {
this.missingArgument('--connect-push')
}
if (!options.storageUrl) {
this.missingArgument('--storage-url')
}
require('./units/provider')({
name: options.name
, killTimeout: 10000
, ports: cliutil.range(options.minPort, options.maxPort)
, filter: function(device) {
return serials.length === 0 || serials.indexOf(device.id) !== -1
}
, allowRemote: options.allowRemote
, fork: function(device, ports) {
var fork = require('child_process').fork
return fork(__filename, [
'device', device.id
, '--provider', options.name
, '--connect-sub', options.connectSub.join(',')
, '--connect-push', options.connectPush.join(',')
, '--screen-port', ports.shift()
, '--connect-port', ports.shift()
, '--vnc-port', ports.shift()
, '--public-ip', options.publicIp
, '--group-timeout', options.groupTimeout
, '--storage-url', options.storageUrl
, '--adb-host', options.adbHost
, '--adb-port', options.adbPort
, '--screen-ws-url-pattern', options.screenWsUrlPattern
, '--connect-url-pattern', options.connectUrlPattern
, '--heartbeat-interval', options.heartbeatInterval
, '--vnc-initial-size', options.vncInitialSize.join('x')
]
.concat(options.muteMaster ? ['--mute-master'] : [])
.concat(options.lockRotation ? ['--lock-rotation'] : []))
}
, endpoints: {
sub: options.connectSub
, push: options.connectPush
}
, adbHost: options.adbHost
, adbPort: options.adbPort
})
})
program
.command('device <serial>')
.description('start device worker')
.option('-n, --provider <name>'
, 'provider name'
, String)
.option('-s, --connect-sub <endpoint>'
, 'sub endpoint'
, cliutil.list)
.option('-p, --connect-push <endpoint>'
, 'push endpoint'
, cliutil.list)
.option('--screen-port <port>'
, 'port allocated to the screen websocket'
, Number)
.option('--connect-port <port>'
, 'port allocated to adb connect'
, Number)
.option('--vnc-port <port>'
, 'port allocated to vnc'
, Number)
.option('--vnc-initial-size <size>'
, 'initial VNC size'
, cliutil.size
, [600, 800])
.option('--connect-url-pattern <pattern>'
, 'adb connect URL pattern'
, String
, '${publicIp}:${publicPort}')
.option('--public-ip <ip>'
, 'public ip for global access'
, String
, ip())
.option('-t, --group-timeout <seconds>'
, 'group timeout'
, Number
, 900)
.option('-r, --storage-url <url>'
, 'URL to storage client'
, String)
.option('--adb-host <host>'
, 'ADB host (defaults to 127.0.0.1)'
, String
, '127.0.0.1')
.option('--adb-port <port>'
, 'ADB port (defaults to 5037)'
, Number
, 5037)
.option('--screen-ws-url-pattern <pattern>'
, 'screen WebSocket URL pattern'
, String
, 'ws://${publicIp}:${publicPort}')
.option('--heartbeat-interval <ms>'
, 'heartbeat interval'
, Number
, 10000)
.option('--mute-master'
, 'whether to mute master volume when devices are being used')
.option('--lock-rotation'
, 'whether to lock rotation when devices are being used')
.action(function(serial, options) {
if (!options.connectSub) {
this.missingArgument('--connect-sub')
}
if (!options.connectPush) {
this.missingArgument('--connect-push')
}
if (!options.provider) {
this.missingArgument('--provider')
}
if (!options.screenPort) {
this.missingArgument('--screen-port')
}
if (!options.connectPort) {
this.missingArgument('--connect-port')
}
if (!options.vncPort) {
this.missingArgument('--vnc-port')
}
if (!options.storageUrl) {
this.missingArgument('--storage-url')
}
require('./units/device')({
serial: serial
, provider: options.provider
, publicIp: options.publicIp
, endpoints: {
sub: options.connectSub
, push: options.connectPush
}
, groupTimeout: options.groupTimeout * 1000 // change to ms
, storageUrl: options.storageUrl
, adbHost: options.adbHost
, adbPort: options.adbPort
, screenWsUrlPattern: options.screenWsUrlPattern
, screenPort: options.screenPort
, connectUrlPattern: options.connectUrlPattern
, connectPort: options.connectPort
, vncPort: options.vncPort
, vncInitialSize: options.vncInitialSize
, heartbeatInterval: options.heartbeatInterval
, muteMaster: options.muteMaster
, lockRotation: options.lockRotation
})
})
program
.command('processor <name>')
.description('start processor')
.option('-a, --connect-app-dealer <endpoint>'
, 'app dealer endpoint'
, cliutil.list)
.option('-d, --connect-dev-dealer <endpoint>'
, 'device dealer endpoint'
, cliutil.list)
.action(function(name, options) {
if (!options.connectAppDealer) {
this.missingArgument('--connect-app-dealer')
}
if (!options.connectDevDealer) {
this.missingArgument('--connect-dev-dealer')
}
require('./units/processor')({
name: name
, endpoints: {
appDealer: options.connectAppDealer
, devDealer: options.connectDevDealer
}
})
})
program
.command('reaper <name>')
.description('start reaper')
.option('-p, --connect-push <endpoint>'
, 'push endpoint'
, cliutil.list)
.option('-s, --connect-sub <endpoint>'
, 'sub endpoint'
, cliutil.list)
.option('-t, --heartbeat-timeout <ms>'
, 'consider devices with heartbeat older than this value dead'
, Number
, 30000)
.action(function(name, options) {
require('./units/reaper')({
name: name
, heartbeatTimeout: options.heartbeatTimeout
, endpoints: {
push: options.connectPush
, sub: options.connectSub
}
})
})
program
.command('triproxy <name>')
.description('start triproxy')
.option('-u, --bind-pub <endpoint>'
, 'pub endpoint'
, String
, 'tcp://*:7111')
.option('-d, --bind-dealer <endpoint>'
, 'dealer endpoint'
, String
, 'tcp://*:7112')
.option('-p, --bind-pull <endpoint>'
, 'pull endpoint'
, String
, 'tcp://*:7113')
.action(function(name, options) {
require('./units/triproxy')({
name: name
, endpoints: {
pub: options.bindPub
, dealer: options.bindDealer
, pull: options.bindPull
}
})
})
program
.command('auth-ldap')
.description('start LDAP auth client')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7120)
.option('-s, --secret <secret>'
, 'secret (or $SECRET)'
, String
, process.env.SECRET)
.option('-i, --ssid <ssid>'
, 'session SSID (or $SSID)'
, String
, process.env.SSID || 'ssid')
.option('-a, --app-url <url>'
, 'URL to app'
, String)
.option('-u, --ldap-url <url>'
, 'LDAP server URL (or $LDAP_URL)'
, String
, process.env.LDAP_URL)
.option('-t, --ldap-timeout <timeout>'
, 'LDAP timeout (or $LDAP_TIMEOUT)'
, Number
, process.env.LDAP_TIMEOUT || 1000)
.option('--ldap-bind-dn <dn>'
, 'LDAP bind DN (or $LDAP_BIND_DN)'
, String
, process.env.LDAP_BIND_DN)
.option('--ldap-bind-credentials <credentials>'
, 'LDAP bind credentials (or $LDAP_BIND_CREDENTIALS)'
, String
, process.env.LDAP_BIND_CREDENTIALS)
.option('--ldap-search-dn <dn>'
, 'LDAP search DN (or $LDAP_SEARCH_DN)'
, String
, process.env.LDAP_SEARCH_DN)
.option('--ldap-search-scope <scope>'
, 'LDAP search scope (or $LDAP_SEARCH_SCOPE)'
, String
, process.env.LDAP_SEARCH_SCOPE || 'sub')
.option('--ldap-search-class <class>'
, 'LDAP search objectClass (or $LDAP_SEARCH_CLASS)'
, String
, process.env.LDAP_SEARCH_CLASS || 'top')
.option('--ldap-search-field <name>'
, 'LDAP search field (or $LDAP_SEARCH_FIELD)'
, String
, process.env.LDAP_SEARCH_FIELD)
.action(function(options) {
if (!options.secret) {
this.missingArgument('--secret')
}
if (!options.appUrl) {
this.missingArgument('--app-url')
}
require('./units/auth/ldap')({
port: options.port
, secret: options.secret
, ssid: options.ssid
, appUrl: options.appUrl
, ldap: {
url: options.ldapUrl
, timeout: options.ldapTimeout
, bind: {
dn: options.ldapBindDn
, credentials: options.ldapBindCredentials
}
, search: {
dn: options.ldapSearchDn
, scope: options.ldapSearchScope
, objectClass: options.ldapSearchClass
, field: options.ldapSearchField
}
}
})
})
program
.command('auth-oauth2')
.description('start OAuth 2.0 auth client')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7120)
.option('-s, --secret <secret>'
, 'secret (or $SECRET)'
, String
, process.env.SECRET)
.option('-i, --ssid <ssid>'
, 'session SSID (or $SSID)'
, String
, process.env.SSID || 'ssid')
.option('-a, --app-url <url>'
, 'URL to app'
, String)
.option('--oauth-authorization-url <url>'
, 'OAuth 2.0 authorization URL (or $OAUTH_AUTHORIZATION_URL)'
, String
, process.env.OAUTH_AUTHORIZATION_URL)
.option('--oauth-token-url <url>'
, 'OAuth 2.0 token URL (or $OAUTH_TOKEN_URL)'
, String
, process.env.OAUTH_TOKEN_URL)
.option('--oauth-userinfo-url <url>'
, 'OAuth 2.0 token URL (or $OAUTH_USERINFO_URL)'
, String
, process.env.OAUTH_USERINFO_URL)
.option('--oauth-client-id <id>'
, 'OAuth 2.0 client ID (or $OAUTH_CLIENT_ID)'
, String
, process.env.OAUTH_CLIENT_ID)
.option('--oauth-client-secret <value>'
, 'OAuth 2.0 client secret (or $OAUTH_CLIENT_SECRET)'
, String
, process.env.OAUTH_CLIENT_SECRET)
.option('--oauth-callback-url <url>'
, 'OAuth 2.0 callback URL (or $OAUTH_CALLBACK_URL)'
, String
, process.env.OAUTH_CALLBACK_URL)
.option('--oauth-scope <scope>'
, 'Space-separated OAuth 2.0 scope (or $OAUTH_SCOPE)'
, String
, process.env.OAUTH_SCOPE)
.action(function(options) {
if (!options.secret) {
this.missingArgument('--secret')
}
if (!options.appUrl) {
this.missingArgument('--app-url')
}
if (!options.oauthAuthorizationUrl) {
this.missingArgument('--oauth-authorization-url')
}
if (!options.oauthTokenUrl) {
this.missingArgument('--oauth-token-url')
}
if (!options.oauthUserinfoUrl) {
this.missingArgument('--oauth-userinfo-url')
}
if (!options.oauthClientId) {
this.missingArgument('--oauth-client-id')
}
if (!options.oauthClientSecret) {
this.missingArgument('--oauth-client-secret')
}
if (!options.oauthCallbackUrl) {
this.missingArgument('--oauth-callback-url')
}
if (!options.oauthScope) {
this.missingArgument('--oauth-scope')
}
require('./units/auth/oauth2')({
port: options.port
, secret: options.secret
, ssid: options.ssid
, appUrl: options.appUrl
, oauth: {
authorizationURL: options.oauthAuthorizationUrl
, tokenURL: options.oauthTokenUrl
, userinfoURL: options.oauthUserinfoUrl
, clientID: options.oauthClientId
, clientSecret: options.oauthClientSecret
, callbackURL: options.oauthCallbackUrl
, scope: options.oauthScope.split(/\s+/)
}
})
})
program
.command('auth-saml2')
.description('start SAML 2.0 auth client')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7120)
.option('-s, --secret <secret>'
, 'secret (or $SECRET)'
, String
, process.env.SECRET)
.option('-i, --ssid <ssid>'
, 'session SSID (or $SSID)'
, String
, process.env.SSID || 'ssid')
.option('-a, --app-url <url>'
, 'URL to app'
, String)
.option('--saml-id-provider-entry-point-url <url>'
, 'SAML 2.0 identity provider URL (or $SAML_ID_PROVIDER_ENTRY_POINT_URL)'
, String
, process.env.SAML_ID_PROVIDER_ENTRY_POINT_URL)
.option('--saml-id-provider-issuer <issuer>'
, 'SAML 2.0 identity provider issuer (or $SAML_ID_PROVIDER_ISSUER)'
, String
, process.env.SAML_ID_PROVIDER_ISSUER)
.option('--saml-id-provider-cert-path <path>'
, 'SAML 2.0 identity provider certificate file path (or $SAML_ID_PROVIDER_CERT_PATH)'
, String
, process.env.SAML_ID_PROVIDER_CERT_PATH)
.action(function(options) {
if (!options.secret) {
this.missingArgument('--secret')
}
if (!options.appUrl) {
this.missingArgument('--app-url')
}
if (!options.samlIdProviderEntryPointUrl) {
this.missingArgument('--saml-id-provider-entry-point-url')
}
if (!options.samlIdProviderIssuer) {
this.missingArgument('--saml-id-provider-issuer')
}
require('./units/auth/saml2')({
port: options.port
, secret: options.secret
, ssid: options.ssid
, appUrl: options.appUrl
, saml: {
entryPoint: options.samlIdProviderEntryPointUrl
, issuer: options.samlIdProviderIssuer
, certPath: options.samlIdProviderCertPath
}
})
})
program
.command('auth-mock')
.description('start mock auth client')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7120)
.option('-s, --secret <secret>'
, 'secret (or $SECRET)'
, String
, process.env.SECRET)
.option('-i, --ssid <ssid>'
, 'session SSID (or $SSID)'
, String
, process.env.SSID || 'ssid')
.option('-a, --app-url <url>'
, 'URL to app'
, String)
.option('--use-basic-auth'
, 'Whether to use basic authentication for login or not')
.option('--basic-auth-username <username>'
, 'Basic Auth Username (or $BASIC_AUTH_USERNAME)'
, String
, process.env.BASIC_AUTH_USERNAME || 'username')
.option('--basic-auth-password <password>'
, 'Basic Auth Password (or $BASIC_AUTH_PASSWORD)'
, String
, process.env.BASIC_AUTH_PASSWORD || 'password')
.action(function(options) {
if (!options.secret) {
this.missingArgument('--secret')
}
if (!options.appUrl) {
this.missingArgument('--app-url')
}
require('./units/auth/mock')({
port: options.port
, secret: options.secret
, ssid: options.ssid
, appUrl: options.appUrl
, mock: {
useBasicAuth: options.useBasicAuth
, basicAuth: {
username: options.basicAuthUsername
, password: options.basicAuthPassword
}
}
})
})
// Start the service for token-based authentication.
// Hard coded values follow OpenSTF pattern above.
program
.command('auth-token')
.description('start token auth client')
.option('-p, --port <port>',
'port (or $PORT)',
Number,
process.env.PORT || 7120)
.option('-s, --secret <secret>', 'secret (or $SECRET)',
String, process.env.SECRET)
.option('-i, --ssid <ssid>', 'session SSID (or $SSID)', String,
process.env.SSID || 'ssid')
.option('-a, --app-url <url>', 'URL to app', String)
.option('--use-basic-auth',
'Whether to use basic authentication for login or not')
.option('--basic-auth-username <username>',
'Basic Auth Username (or $BASIC_AUTH_USERNAME)', String,
process.env.BASIC_AUTH_USERNAME || 'username')
.option('--basic-auth-password <password>',
'Basic Auth Password (or $BASIC_AUTH_PASSWORD)', String,
process.env.BASIC_AUTH_PASSWORD || 'password')
.action(function(options) {
if (!options.secret) {
this.missingArgument('--secret');
}
if (!options.appUrl) {
this.missingArgument('--app-url');
}
require('./units/auth/token')({
port: options.port,
secret: options.secret,
ssid: options.ssid,
appUrl: options.appUrl,
mock: {
useBasicAuth: options.useBasicAuth,
basicAuth: {
username: options.basicAuthUsername,
password: options.basicAuthPassword
}
}
});
});
program
.command('auth-openid')
.description('start openid auth client')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7120)
.option('-s, --secret <secret>'
, 'secret (or $SECRET)'
, String
, process.env.SECRET)
.option('-a, --app-url <url>'
, 'URL to app'
, String)
.option('--openid-identifier-url <openidIdentifierUrl>'
, 'openidIdentifierUrl'
, String
, process.env.OPENID_IDENTIFIER_URL)
.action(function(options) {
if (!options.openidIdentifierUrl) {
this.missingArgument('--openid-identifier-url')
}
if (!options.secret) {
this.missingArgument('--secret')
}
if (!options.appUrl) {
this.missingArgument('--app-url')
}
require('./units/auth/openid')({
port: options.port
, secret: options.secret
, appUrl: options.appUrl
, openid: {
identifierUrl: options.openidIdentifierUrl
}
})
})
program
.command('notify-hipchat')
.description('start HipChat notifier')
.option('-t, --token <token>'
, 'HipChat v2 API token (or $HIPCHAT_TOKEN)'
, String
, process.env.HIPCHAT_TOKEN)
.option('-r, --room <room>'
, 'HipChat room (or $HIPCHAT_ROOM)'
, String
, process.env.HIPCHAT_ROOM)
.option('-p, --priority <level>'
, 'minimum log level'
, Number
, logger.Level.IMPORTANT)
.option('-n, --notify-priority <level>'
, 'minimum log level to cause a notification'
, Number
, logger.Level.WARNING)
.option('-s, --connect-sub <endpoint>'
, 'sub endpoint'
, cliutil.list)
.action(function(options) {
if (!options.token) {
this.missingArgument('--token')
}
if (!options.room) {
this.missingArgument('--room')
}
if (!options.connectSub) {
this.missingArgument('--connect-sub')
}
require('./units/notify/hipchat')({
token: options.token
, room: options.room
, priority: options.priority
, notifyPriority: options.notifyPriority
, endpoints: {
sub: options.connectSub
}
})
})
program
.command('notify-slack')
.description('start Slack notifier')
.option('-t, --token <token>'
, 'Slack API token (or $SLACK_TOKEN)'
, String
, process.env.SLACK_TOKEN)
.option('-c, --channel #<channel>'
, 'Slack channel (or $SLACK_CHANNEL)'
, String
, process.env.SLACK_CHANNEL)
.option('-p, --priority <level>'
, 'minimum log level'
, Number
, logger.Level.IMPORTANT)
.option('-s, --connect-sub <endpoint>'
, 'sub endpoint'
, cliutil.list)
.action(function(options) {
if (!options.token) {
this.missingArgument('--token')
}
if (!options.channel) {
this.missingArgument('--channel')
}
if (!options.connectSub) {
this.missingArgument('--connect-sub')
}
require('./units/notify/slack')({
token: options.token
, channel: options.channel
, priority: options.priority
, endpoints: {
sub: options.connectSub
}
})
})
program
.command('log-rethinkdb')
.description('start a rethinkdb log recorder')
.option('-p, --priority <level>'
, 'minimum log level'
, Number
, logger.Level.DEBUG)
.option('-s, --connect-sub <endpoint>'
, 'sub endpoint'
, cliutil.list)
.action(function(options) {
if (!options.connectSub) {
this.missingArgument('--connect-sub')
}
require('./units/log/rethinkdb')({
priority: options.priority
, endpoints: {
sub: options.connectSub
}
})
})
program
.command('poorxy')
.description('start a poor reverse proxy for local development')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7100)
.option('-u, --app-url <url>'
, 'URL to app'
, String)
.option('-a, --auth-url <url>'
, 'URL to auth client'
, String)
.option('-w, --websocket-url <url>'
, 'URL to websocket client'
, String)
.option('-r, --storage-url <url>'
, 'URL to storage client'
, String)
.option('--storage-plugin-image-url <url>'
, 'URL to image storage plugin'
, String)
.option('--storage-plugin-apk-url <url>'
, 'URL to apk storage plugin'
, String)
.action(function(options) {
if (!options.appUrl) {
this.missingArgument('--app-url')
}
if (!options.authUrl) {
this.missingArgument('--auth-url')
}
if (!options.websocketUrl) {
this.missingArgument('--websocket-url')
}
if (!options.storageUrl) {
this.missingArgument('--storage-url')
}
if (!options.storagePluginImageUrl) {
this.missingArgument('--storage-plugin-image-url')
}
if (!options.storagePluginApkUrl) {
this.missingArgument('--storage-plugin-apk-url')
}
require('./units/poorxy')({
port: options.port
, appUrl: options.appUrl
, authUrl: options.authUrl
, websocketUrl: options.websocketUrl
, storageUrl: options.storageUrl
, storagePluginImageUrl: options.storagePluginImageUrl
, storagePluginApkUrl: options.storagePluginApkUrl
})
})
program
.command('app')
.description('start app')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7105)
.option('-s, --secret <secret>'
, 'secret (or $SECRET)'
, String
, process.env.SECRET)
.option('-i, --ssid <ssid>'
, 'session SSID (or $SSID)'
, String
, process.env.SSID || 'ssid')
.option('-a, --auth-url <url>'
, 'URL to auth client'
, String)
.option('-w, --websocket-url <url>'
, 'URL to websocket client'
, String)
.option('--user-profile-url <url>'
, 'URL to external user profile page'
, String)
.action(function(options) {
if (!options.secret) {
this.missingArgument('--secret')
}
if (!options.authUrl) {
this.missingArgument('--auth-url')
}
if (!options.websocketUrl) {
this.missingArgument('--websocket-url')
}
require('./units/app')({
port: options.port
, secret: options.secret
, ssid: options.ssid
, authUrl: options.authUrl
, websocketUrl: options.websocketUrl
, userProfileUrl: options.userProfileUrl
})
})
program
.command('websocket')
.description('start websocket')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7110)
.option('-s, --secret <secret>'
, 'secret (or $SECRET)'
, String
, process.env.SECRET)
.option('-i, --ssid <ssid>'
, 'session SSID (or $SSID)'
, String
, process.env.SSID || 'ssid')
.option('-r, --storage-url <url>'
, 'URL to storage client'
, String)
.option('-u, --connect-sub <endpoint>'
, 'sub endpoint'
, cliutil.list)
.option('-c, --connect-push <endpoint>'
, 'push endpoint'
, cliutil.list)
.action(function(options) {
if (!options.secret) {
this.missingArgument('--secret')
}
if (!options.storageUrl) {
this.missingArgument('--storage-url')
}
if (!options.connectSub) {
this.missingArgument('--connect-sub')
}
if (!options.connectPush) {
this.missingArgument('--connect-push')
}
require('./units/websocket')({
port: options.port
, secret: options.secret
, ssid: options.ssid
, storageUrl: options.storageUrl
, endpoints: {
sub: options.connectSub
, push: options.connectPush
}
})
})
program
.command('storage-temp')
.description('start temp storage')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7100)
.option('--save-dir <dir>'
, 'where to save files'
, String
, os.tmpdir())
.action(function(options) {
require('./units/storage/temp')({
port: options.port
, saveDir: options.saveDir
})
})
program
.command('storage-s3')
.description('start s3 storage')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7100)
.option('--bucket <bucketname>'
, 'your s3 bucket name'
, String)
.option('--profile <name>'
, 'your aws credentials profile name'
, String)
.option('--endpoint <endpoint>'
, 'your buckets endpoint'
, String)
.action(function(options) {
if (!options.profile) {
this.missingArgument('--profile')
}
if (!options.endpoint) {
this.missingArgument('--endpoint')
}
require('./units/storage/s3')({
port: options.port
, profile: options.profile
, bucket: options.bucket
, endpoint: options.endpoint
})
})
program
.command('storage-plugin-image')
.description('start storage image plugin')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7100)
.option('-r, --storage-url <url>'
, 'URL to storage client'
, String)
.option('-c, --concurrency <num>'
, 'maximum number of simultaneous transformations'
, Number)
.option('--cache-dir <dir>'
, 'where to cache images'
, String
, os.tmpdir())
.action(function(options) {
if (!options.storageUrl) {
this.missingArgument('--storage-url')
}
require('./units/storage/plugins/image')({
port: options.port
, storageUrl: options.storageUrl
, cacheDir: options.cacheDir
, concurrency: options.concurrency || os.cpus().length
})
})
program
.command('storage-plugin-apk')
.description('start storage apk plugin')
.option('-p, --port <port>'
, 'port (or $PORT)'
, Number
, process.env.PORT || 7100)
.option('-r, --storage-url <url>'
, 'URL to storage client'
, String)
.option('--cache-dir <dir>'
, 'where to cache images'
, String
, os.tmpdir())
.action(function(options) {
if (!options.storageUrl) {
this.missingArgument('--storage-url')
}
require('./units/storage/plugins/apk')({
port: options.port
, storageUrl: options.storageUrl
, cacheDir: options.cacheDir
})
})
program
.command('migrate')
.description('migrates the database to the latest version')
.action(function() {
var log = logger.createLogger('cli:migrate')
var db = require('./db')
db.setup()
.then(function() {
process.exit(0)
})
.catch(function(err) {
log.fatal('Migration had an error:', err.stack)
process.exit(1)
})
})
program
.command('generate-fake-device [model]')
.description('generates a fake device for testing')
.option('-n, --number <n>'
, 'how many devices to create (defaults to 1)'
, Number
, 1)
.action(function(model, options) {
var log = logger.createLogger('cli:generate-fake-device')
var fake = require('./util/fakedevice')
var n = options.number
function nextDevice() {
return fake.generate(model)
.then(function(serial) {
log.info('Created fake device "%s"', serial)
if (--n) {
return nextDevice()
}
})
}
nextDevice()
.then(function() {
process.exit(0)
})
.catch(function(err) {
log.fatal('Fake device creation had an error:', err.stack)
process.exit(1)
})
})
program
.command('local [serial...]')
.description('start everything locally')
.option('--bind-app-pub <endpoint>'
, 'app pub endpoint'
, String
, 'tcp://127.0.0.1:7111')
.option('--bind-app-dealer <endpoint>'
, 'app dealer endpoint'
, String
, 'tcp://127.0.0.1:7112')
.option('--bind-app-pull <endpoint>'
, 'app pull endpoint'
, String
, 'tcp://127.0.0.1:7113')
.option('--bind-dev-pub <endpoint>'
, 'device pub endpoint'
, String
, 'tcp://127.0.0.1:7114')
.option('--bind-dev-dealer <endpoint>'
, 'device dealer endpoint'
, String
, 'tcp://127.0.0.1:7115')
.option('--bind-dev-pull <endpoint>'
, 'device pull endpoint'
, String
, 'tcp://127.0.0.1:7116')
.option('--auth-type <mock|ldap|oauth2|saml2|openid|token>'
, 'auth type'
, String
, 'token')
.option('-a, --auth-url <url>'
, 'URL to auth client'
, String)
.option('--auth-port <port>'
, 'auth port'
, Number
, 7120)
.option('--auth-secret <secret>'
, 'auth secret'
, String
, 'kute kittykat')
.option('--auth-options <json>'
, 'array of options to pass to the auth implementation'
, String
, '[]')
.option('--poorxy-port <port>'
, 'poorxy port'
, Number
, 7100)
.option('--app-port <port>'
, 'app port'
, Number
, 7105)
.option('--websocket-port <port>'
, 'websocket port'
, Number
, 7110)
.option('--storage-type <temp|s3>'
, 'storage type'
, String
, 'temp')
.option('--storage-port <port>'
, 'storage port'
, Number
, 7102)
.option('--storage-options <json>'
, 'array of options to pass to the storage implementation'
, String
, '[]')
.option('--storage-plugin-image-port <port>'
, 'storage image plugin port'
, Number
, 7103)
.option('--storage-plugin-apk-port <port>'
, 'storage apk plugin port'
, Number
, 7104)
.option('--provider <name>'
, 'provider name (or os.hostname())'
, String
, os.hostname())
.option('--provider-min-port <port>'
, 'minimum port number for worker use'
, Number
, 7400)
.option('--provider-max-port <port>'
, 'maximum port number for worker use'
, Number
, 7700)
.option('-t, --group-timeout <seconds>'
, 'group timeout'
, Number
, 900)
.option('--public-ip <ip>'
, 'public ip for global access'
, String
, 'localhost')
.option('--adb-host <host>'
, 'ADB host (defaults to 127.0.0.1)'
, String
, '127.0.0.1')
.option('--adb-port <port>'
, 'ADB port (defaults to 5037)'
, Number
, 5037)
.option('-R, --allow-remote'
, 'Whether to allow remote devices to be set up')
.option('--user-profile-url <url>'
, 'URL to external user profile page'
, String)
.option('--vnc-initial-size <size>'
, 'initial VNC size'
, cliutil.size
, [600, 800])
.option('--mute-master'
, 'whether to mute master volume when devices are being used')
.option('--lock-rotation'
, 'whether to lock rotation when devices are being used')
.action(function(serials, options) {
var log = logger.createLogger('cli:local')
var procutil = require('./util/procutil')
// Each forked process waits for signals to stop, and so we run over the
// default limit of 10. So, it's not a leak, but a refactor wouldn't hurt.
process.setMaxListeners(20)
function run() {
var procs = [
// app triproxy
procutil.fork(__filename, [
'triproxy', 'app001'
, '--bind-pub', options.bindAppPub
, '--bind-dealer', options.bindAppDealer
, '--bind-pull', options.bindAppPull
])
// device triproxy
, procutil.fork(__filename, [
'triproxy', 'dev001'
, '--bind-pub', options.bindDevPub
, '--bind-dealer', options.bindDevDealer
, '--bind-pull', options.bindDevPull
])
// processor one
, procutil.fork(__filename, [
'processor', 'proc001'
, '--connect-app-dealer', options.bindAppDealer
, '--connect-dev-dealer', options.bindDevDealer
])
// processor two
, procutil.fork(__filename, [
'processor', 'proc002'
, '--connect-app-dealer', options.bindAppDealer
, '--connect-dev-dealer', options.bindDevDealer
])
// reaper one
, procutil.fork(__filename, [
'reaper', 'reaper001'
, '--connect-push', options.bindDevPull
, '--connect-sub', options.bindAppPub
])
// provider
, procutil.fork(__filename, [
'provider'
, '--name', options.provider
, '--min-port', options.providerMinPort
, '--max-port', options.providerMaxPort
, '--connect-sub', options.bindDevPub
, '--connect-push', options.bindDevPull
, '--group-timeout', options.groupTimeout
, '--public-ip', options.publicIp
, '--storage-url'
, util.format('http://localhost:%d/', options.poorxyPort)
, '--adb-host', options.adbHost
, '--adb-port', options.adbPort
, '--vnc-initial-size', options.vncInitialSize.join('x')
]
.concat(options.allowRemote ? ['--allow-remote'] : [])
.concat(options.muteMaster ? ['--mute-master'] : [])
.concat(options.lockRotation ? ['--lock-rotation'] : [])
.concat(serials))
// auth
, procutil.fork(__filename, [
util.format('auth-%s', options.authType)
, '--port', options.authPort
, '--secret', options.authSecret
, '--app-url', util.format(
'http://%s:%d/'
, options.publicIp
, options.poorxyPort
)
].concat(JSON.parse(options.authOptions)))
// app
, procutil.fork(__filename, [
'app'
, '--port', options.appPort
, '--secret', options.authSecret
, '--auth-url', options.authUrl || util.format(
'http://%s:%d/auth/%s/'
, options.publicIp
, options.poorxyPort
, {
oauth2: 'oauth'
, saml2: 'saml'
}[options.authType] || options.authType
)
, '--websocket-url', util.format(
'http://%s:%d/'
, options.publicIp
, options.websocketPort
)
].concat((function() {
var extra = []
if (options.userProfileUrl) {
extra.push('--user-profile-url', options.userProfileUrl)
}
return extra
})()))
// websocket
, procutil.fork(__filename, [
'websocket'
, '--port', options.websocketPort
, '--secret', options.authSecret
, '--storage-url'
, util.format('http://localhost:%d/', options.poorxyPort)
, '--connect-sub', options.bindAppPub
, '--connect-push', options.bindAppPull
])
// storage
, procutil.fork(__filename, [
util.format('storage-%s', options.storageType)
, '--port', options.storagePort
].concat(JSON.parse(options.storageOptions)))
// image processor
, procutil.fork(__filename, [
'storage-plugin-image'
, '--port', options.storagePluginImagePort
, '--storage-url'
, util.format('http://localhost:%d/', options.poorxyPort)
])
// apk processor
, procutil.fork(__filename, [
'storage-plugin-apk'
, '--port', options.storagePluginApkPort
, '--storage-url'
, util.format('http://localhost:%d/', options.poorxyPort)
])
// poorxy
, procutil.fork(__filename, [
'poorxy'
, '--port', options.poorxyPort
, '--app-url'
, util.format('http://localhost:%d/', options.appPort)
, '--auth-url'
, util.format('http://localhost:%d/', options.authPort)
, '--websocket-url'
, util.format('http://localhost:%d/', options.websocketPort)
, '--storage-url'
, util.format('http://localhost:%d/', options.storagePort)
, '--storage-plugin-image-url'
, util.format('http://localhost:%d/', options.storagePluginImagePort)
, '--storage-plugin-apk-url'
, util.format('http://localhost:%d/', options.storagePluginApkPort)
])
]
function shutdown() {
log.info('Shutting down all child processes')
procs.forEach(function(proc) {
proc.cancel()
})
return Promise.settle(procs)
}
process.on('SIGINT', function() {
log.info('Received SIGINT, waiting for processes to terminate')
})
process.on('SIGTERM', function() {
log.info('Received SIGTERM, waiting for processes to terminate')
})
return Promise.all(procs)
.then(function() {
process.exit(0)
})
.catch(function(err) {
log.fatal('Child process had an error', err.stack)
return shutdown()
.then(function() {
process.exit(1)
})
})
}
procutil.fork(__filename, ['migrate'])
.done(run)
})
program
.command('doctor')
.description('diagnose issues before starting')
.option('--devices'
, 'diagnose devices connected to stf')
.action(function(options) {
require('./util/doctor').run(options)
})
program.parse(process.argv)