cli.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. #!/usr/bin/env node
  2. 'use strict'
  3. // Node requires
  4. const path = require('path')
  5. // npm requires
  6. const meow = require('meow')
  7. const chalk = require('chalk')
  8. const figlet = require('figlet')
  9. const _ = require('lodash')
  10. const debug = require('debug')('joystream:colossus')
  11. // Project root
  12. const PROJECT_ROOT = path.resolve(__dirname, '..')
  13. // Parse CLI
  14. const FLAG_DEFINITIONS = {
  15. port: {
  16. type: 'number',
  17. alias: 'p',
  18. default: 3000
  19. },
  20. syncPeriod: {
  21. type: 'number',
  22. default: 300000
  23. },
  24. keyFile: {
  25. type: 'string'
  26. },
  27. publicUrl: {
  28. type: 'string',
  29. alias: 'u'
  30. },
  31. passphrase: {
  32. type: 'string'
  33. },
  34. wsProvider: {
  35. type: 'string',
  36. default: 'ws://localhost:9944'
  37. },
  38. providerId: {
  39. type: 'number',
  40. alias: 'i'
  41. }
  42. }
  43. const cli = meow(`
  44. Usage:
  45. $ colossus [command] [arguments]
  46. Commands:
  47. server [default] Run a server instance.
  48. discovery Run the discovery service only.
  49. dev-server Run a local development server for testing, listens on port 3001
  50. Arguments (required for server):
  51. --provider-id ID, -i ID StorageProviderId assigned to you in working group.
  52. --key-file FILE JSON key export file to use as the storage provider (role account).
  53. --public-url=URL, -u URL API Public URL to announce.
  54. Arguments (optional):
  55. --passphrase Optional passphrase to use to decrypt the key-file.
  56. --port=PORT, -p PORT Port number to listen on, defaults to 3000.
  57. --sync-period Number of milliseconds to wait between synchronization
  58. runs. Defaults to 300000ms (5min).
  59. --ws-provider WSURL Joystream Node websocket provider url, eg: 'ws://127.0.0.1:9944'
  60. `,
  61. { flags: FLAG_DEFINITIONS })
  62. // All-important banner!
  63. function banner () {
  64. console.log(chalk.blue(figlet.textSync('joystream', 'Speed')))
  65. }
  66. function start_express_app(app, port) {
  67. const http = require('http')
  68. const server = http.createServer(app)
  69. return new Promise((resolve, reject) => {
  70. server.on('error', reject)
  71. server.on('close', (...args) => {
  72. console.log('Server closed, shutting down...')
  73. resolve(...args)
  74. })
  75. server.on('listening', () => {
  76. console.log('API server started.', server.address())
  77. })
  78. server.listen(port, '::')
  79. console.log('Starting API server...')
  80. })
  81. }
  82. // Start app
  83. function start_all_services (store, api) {
  84. const app = require('../lib/app')(PROJECT_ROOT, store, api, cli.flags)
  85. const port = cli.flags.port
  86. return start_express_app(app, port)
  87. }
  88. // Start discovery service app
  89. function start_discovery_service (api) {
  90. const app = require('../lib/discovery')(PROJECT_ROOT, api, cli.flags)
  91. const port = cli.flags.port
  92. return start_express_app(app, port)
  93. }
  94. // Get an initialized storage instance
  95. function get_storage (runtime_api) {
  96. // TODO at some point, we can figure out what backend-specific connection
  97. // options make sense. For now, just don't use any configuration.
  98. const { Storage } = require('@joystream/storage')
  99. const options = {
  100. resolve_content_id: async (content_id) => {
  101. // Resolve via API
  102. const obj = await runtime_api.assets.getDataObject(content_id)
  103. if (!obj || obj.isNone) {
  104. return
  105. }
  106. return obj.unwrap().ipfs_content_id.toString()
  107. }
  108. }
  109. return Storage.create(options)
  110. }
  111. async function init_api_as_storage_provider () {
  112. // Load key information
  113. const { RuntimeApi } = require('@joystream/runtime-api')
  114. const keyFile = cli.flags.keyFile
  115. const providerId = cli.flags.providerId
  116. if (!keyFile) {
  117. throw new Error('Must specify a --key-file argument for running a storage node.')
  118. }
  119. if (providerId === undefined) {
  120. throw new Error('Must specify a --provider-id argument for running a storage node')
  121. }
  122. const wsProvider = cli.flags.wsProvider
  123. const api = await RuntimeApi.create({
  124. account_file: keyFile,
  125. passphrase: cli.flags.passphrase,
  126. provider_url: wsProvider,
  127. storageProviderId: providerId
  128. })
  129. if (!api.identities.key) {
  130. throw new Error('Failed to unlock storage provider account')
  131. }
  132. if (!await api.workers.isRoleAccountOfStorageProvider(api.storageProviderId, api.identities.key.address)) {
  133. throw new Error('storage provider role account and storageProviderId are not associated with a worker')
  134. }
  135. return api
  136. }
  137. async function init_api_as_development_storage_provider () {
  138. // Load key information
  139. const { RuntimeApi } = require('@joystream/runtime-api')
  140. const providerId = 0
  141. const wsProvider = 'ws://localhost:9944'
  142. const api = await RuntimeApi.create({
  143. provider_url: wsProvider,
  144. storageProviderId: providerId
  145. })
  146. const dev = require('../../cli/bin/dev')
  147. api.identities.useKeyPair(dev.roleKeyPair(api))
  148. console.log(`Using ${api.identities.key.address} as role account`)
  149. if (!await api.workers.isRoleAccountOfStorageProvider(api.storageProviderId, api.identities.key.address)) {
  150. throw new Error('Development chain not configured correctly')
  151. } else {
  152. console.log('== Running Development Server ==')
  153. }
  154. return api
  155. }
  156. function get_service_information (publicUrl) {
  157. // For now assume we run all services on the same endpoint
  158. return({
  159. asset: {
  160. version: 1, // spec version
  161. endpoint: publicUrl
  162. },
  163. discover: {
  164. version: 1, // spec version
  165. endpoint: publicUrl
  166. }
  167. })
  168. }
  169. async function announce_public_url (api, publicUrl) {
  170. // re-announce in future
  171. const reannounce = function (timeoutMs) {
  172. setTimeout(announce_public_url, timeoutMs, api, publicUrl)
  173. }
  174. debug('announcing public url')
  175. const { publish } = require('@joystream/discovery')
  176. try {
  177. const serviceInformation = get_service_information(publicUrl)
  178. let keyId = await publish.publish(serviceInformation)
  179. await api.discovery.setAccountInfo(keyId)
  180. debug('publishing complete, scheduling next update')
  181. // >> sometimes after tx is finalized.. we are not reaching here!
  182. // Reannounce before expiery
  183. reannounce(50 * 60 * 1000) // in 50 minutes
  184. } catch (err) {
  185. debug(`announcing public url failed: ${err.stack}`)
  186. // On failure retry sooner
  187. debug(`announcing failed, retrying in: 2 minutes`)
  188. reannounce(120 * 1000)
  189. }
  190. }
  191. function go_offline (api) {
  192. return api.discovery.unsetAccountInfo()
  193. }
  194. // Simple CLI commands
  195. var command = cli.input[0]
  196. if (!command) {
  197. command = 'server'
  198. }
  199. const commands = {
  200. 'server': async () => {
  201. // Load key information
  202. const api = await init_api_as_storage_provider()
  203. console.log('Storage Provider identity initialized, proceeding.')
  204. // A public URL is configured
  205. if (!cli.flags.publicUrl) {
  206. throw new Error('Must specify a --public-url argument')
  207. }
  208. // TODO: check valid url
  209. // Continue with server setup
  210. const store = get_storage(api)
  211. banner()
  212. const { start_syncing } = require('../lib/sync')
  213. start_syncing(api, cli.flags, store)
  214. announce_public_url(api, cli.flags.publicUrl)
  215. await start_all_services(store, api)
  216. },
  217. 'down': async () => {
  218. const api = await init_api_as_storage_provider()
  219. await go_offline(api)
  220. },
  221. 'discovery': async () => {
  222. debug('Starting Joystream Discovery Service')
  223. const { RuntimeApi } = require('@joystream/runtime-api')
  224. const wsProvider = cli.flags.wsProvider
  225. const api = await RuntimeApi.create({ provider_url: wsProvider })
  226. await start_discovery_service(api)
  227. },
  228. 'dev-server': async () => {
  229. // Load key information
  230. const api = await init_api_as_development_storage_provider()
  231. console.log('Development Storage Provider identity initialized, proceeding.')
  232. // Continue with server setup
  233. const store = get_storage(api)
  234. banner()
  235. const { start_syncing } = require('../lib/sync')
  236. start_syncing(api, cli.flags, store)
  237. // force listening on port 3001, assuming pioneer dev server is running on 3000
  238. cli.flags.port = 3001
  239. announce_public_url(api, `http://localhost:${cli.flags.port}/`)
  240. await start_all_services(store, api)
  241. }
  242. }
  243. async function main () {
  244. // Simple CLI commands
  245. var command = cli.input[0]
  246. if (!command) {
  247. command = 'server'
  248. }
  249. if (commands.hasOwnProperty(command)) {
  250. // Command recognized
  251. const args = _.clone(cli.input).slice(1)
  252. await commands[command](...args)
  253. } else {
  254. throw new Error(`Command '${command}' not recognized, aborting!`)
  255. }
  256. }
  257. main()
  258. .then(() => {
  259. console.log('Process exiting gracefully.')
  260. process.exit(0)
  261. })
  262. .catch((err) => {
  263. console.error(chalk.red(err.stack))
  264. process.exit(-1)
  265. })