helper.js 6.8 KB

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