helper.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. const fs = require('fs')
  2. const merge = require('lodash.merge')
  3. const stripIndent = require('common-tags/lib/stripIndent')
  4. const utils = require('../server/helpers/utils')
  5. const logger = require('../server/logger')
  6. const crypto = require('crypto')
  7. // @ts-ignore
  8. const { version } = require('../../package.json')
  9. /**
  10. * Reads all companion configuration set via environment variables
  11. * and via the config file path
  12. *
  13. * @returns {object}
  14. */
  15. exports.getUppyOptions = () => {
  16. return merge({}, getConfigFromEnv(), getConfigFromFile())
  17. }
  18. /**
  19. * Loads the config from environment variables
  20. *
  21. * @returns {object}
  22. */
  23. const getConfigFromEnv = () => {
  24. const uploadUrls = process.env.COMPANION_UPLOAD_URLS
  25. const domains = process.env.COMPANION_DOMAINS || process.env.COMPANION_DOMAIN || null
  26. const validHosts = domains ? domains.split(',') : []
  27. return {
  28. providerOptions: {
  29. google: {
  30. key: process.env.COMPANION_GOOGLE_KEY,
  31. secret: process.env.COMPANION_GOOGLE_SECRET
  32. },
  33. dropbox: {
  34. key: process.env.COMPANION_DROPBOX_KEY,
  35. secret: process.env.COMPANION_DROPBOX_SECRET
  36. },
  37. instagram: {
  38. key: process.env.COMPANION_INSTAGRAM_KEY,
  39. secret: process.env.COMPANION_INSTAGRAM_SECRET
  40. },
  41. s3: {
  42. key: process.env.COMPANION_AWS_KEY,
  43. secret: process.env.COMPANION_AWS_SECRET,
  44. bucket: process.env.COMPANION_AWS_BUCKET,
  45. endpoint: process.env.COMPANION_AWS_ENDPOINT,
  46. region: process.env.COMPANION_AWS_REGION
  47. }
  48. },
  49. server: {
  50. host: process.env.COMPANION_DOMAIN,
  51. protocol: process.env.COMPANION_PROTOCOL,
  52. path: process.env.COMPANION_PATH,
  53. implicitPath: process.env.COMPANION_IMPLICIT_PATH,
  54. oauthDomain: process.env.COMPANION_OAUTH_DOMAIN,
  55. validHosts: validHosts
  56. },
  57. filePath: process.env.COMPANION_DATADIR,
  58. redisUrl: process.env.COMPANION_REDIS_URL,
  59. sendSelfEndpoint: process.env.COMPANION_SELF_ENDPOINT,
  60. uploadUrls: uploadUrls ? uploadUrls.split(',') : null,
  61. secret: process.env.COMPANION_SECRET || generateSecret(),
  62. debug: process.env.NODE_ENV !== 'production',
  63. // TODO: this is a temporary hack to support distributed systems.
  64. // it is not documented, because it should be changed soon.
  65. cookieDomain: process.env.COMPANION_COOKIE_DOMAIN,
  66. multipleInstances: true
  67. }
  68. }
  69. /**
  70. * Auto-generates server secret
  71. *
  72. * @returns {string}
  73. */
  74. const generateSecret = () => {
  75. logger.warn('auto-generating server secret because none was specified', 'startup.secret')
  76. return crypto.randomBytes(64).toString('hex')
  77. }
  78. /**
  79. * Loads the config from a file and returns it as an object
  80. *
  81. * @returns {object}
  82. */
  83. const getConfigFromFile = () => {
  84. const path = getConfigPath()
  85. if (!path) return {}
  86. const rawdata = fs.readFileSync(getConfigPath())
  87. // TODO validate the json object fields to match the uppy config schema
  88. // @ts-ignore
  89. return JSON.parse(rawdata)
  90. }
  91. /**
  92. * Returns the config path specified via cli arguments
  93. *
  94. * @returns {string}
  95. */
  96. const getConfigPath = () => {
  97. let configPath
  98. for (let i = process.argv.length - 1; i >= 0; i--) {
  99. const isConfigFlag = process.argv[i] === '-c' || process.argv[i] === '--config'
  100. const flagHasValue = i + 1 <= process.argv.length
  101. if (isConfigFlag && flagHasValue) {
  102. configPath = process.argv[i + 1]
  103. break
  104. }
  105. }
  106. return configPath
  107. }
  108. /**
  109. * validates that the mandatory companion options are set.
  110. * If it is invalid, it will console an error of unset options and exits the process.
  111. * If it is valid, nothing happens.
  112. *
  113. * @param {object} config
  114. */
  115. exports.validateConfig = (config) => {
  116. const mandatoryOptions = ['secret', 'filePath', 'server.host']
  117. /** @type {string[]} */
  118. const unspecified = []
  119. mandatoryOptions.forEach((i) => {
  120. const value = i.split('.').reduce((prev, curr) => prev[curr], config)
  121. if (!value) unspecified.push(`"${i}"`)
  122. })
  123. // vaidate that all required config is specified
  124. if (unspecified.length) {
  125. console.error('\x1b[31m', 'Please specify the following options',
  126. 'to run companion as Standalone:\n', unspecified.join(',\n'), '\x1b[0m')
  127. process.exit(1)
  128. }
  129. // validate that specified filePath is writeable/readable.
  130. // TODO: consider moving this into the uppy module itself.
  131. try {
  132. // @ts-ignore
  133. fs.accessSync(`${config.filePath}`, fs.R_OK | fs.W_OK)
  134. } catch (err) {
  135. console.error('\x1b[31m', `No access to "${config.filePath}".`,
  136. 'Please ensure the directory exists and with read/write permissions.', '\x1b[0m')
  137. process.exit(1)
  138. }
  139. }
  140. /**
  141. *
  142. * @param {string} url
  143. */
  144. exports.hasProtocol = (url) => {
  145. return url.startsWith('http://') || url.startsWith('https://')
  146. }
  147. exports.buildHelpfulStartupMessage = (uppyOptions) => {
  148. const buildURL = utils.getURLBuilder(uppyOptions)
  149. const callbackURLs = []
  150. Object.keys(uppyOptions.providerOptions).forEach((providerName) => {
  151. // s3 does not need redirect_uris
  152. if (providerName === 's3') {
  153. return
  154. }
  155. if (providerName === 'google') {
  156. providerName = 'drive'
  157. }
  158. callbackURLs.push(buildURL(`/connect/${providerName}/callback`, true))
  159. })
  160. return stripIndent`
  161. Welcome to Companion v${version}
  162. ===================================
  163. Congratulations on setting up Companion! Thanks for joining our cause, you have taken
  164. the first step towards the future of file uploading! We
  165. hope you are as excited about this as we are!
  166. While you did an awesome job on getting Companion running, this is just the welcome
  167. message, so let's talk about the places that really matter:
  168. - Be sure to add ${callbackURLs.join(', ')} as your Oauth redirect uris on their corresponding developer interfaces.
  169. - The URL ${buildURL('/metrics', true)} is available for statistics to keep Companion running smoothly
  170. - https://github.com/transloadit/uppy/issues - report your bugs here
  171. So quit lollygagging, start uploading and experience the future!
  172. `
  173. }