SiteAdminHandler.js 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. const logger = require('@overleaf/logger')
  2. const settings = require('@overleaf/settings')
  3. const fs = require('fs')
  4. const {
  5. addOptionalCleanupHandlerAfterDrainingConnections,
  6. addRequiredCleanupHandlerBeforeDrainingConnections,
  7. } = require('./GracefulShutdown')
  8. const Features = require('./Features')
  9. const UserHandler = require('../Features/User/UserHandler')
  10. const metrics = require('@overleaf/metrics')
  11. // Monitor a site maintenance file (e.g. /etc/site_status) periodically and
  12. // close the site if the file contents contain the string "closed".
  13. const STATUS_FILE_CHECK_INTERVAL = 5000
  14. const statusFile = settings.siteMaintenanceFile
  15. function updateSiteMaintenanceStatus(fileContent) {
  16. const isClosed = !settings.siteIsOpen
  17. const shouldBeClosed = fileContent && fileContent.indexOf('closed') >= 0
  18. if (!isClosed && shouldBeClosed) {
  19. settings.siteIsOpen = false
  20. logger.warn({ fileContent }, 'putting site into maintenance mode')
  21. } else if (isClosed && !shouldBeClosed) {
  22. settings.siteIsOpen = true
  23. logger.warn({ fileContent }, 'taking site out of maintenance mode')
  24. }
  25. }
  26. function pollSiteMaintenanceFile() {
  27. fs.readFile(statusFile, { encoding: 'utf8' }, (err, fileContent) => {
  28. if (err) {
  29. logger.error(
  30. { file: statusFile, fsErr: err },
  31. 'error reading site maintenance file'
  32. )
  33. return
  34. }
  35. updateSiteMaintenanceStatus(fileContent)
  36. })
  37. }
  38. function checkSiteMaintenanceFileSync() {
  39. // crash on start up if file does not exist
  40. const content = fs.readFileSync(statusFile, { encoding: 'utf8' })
  41. updateSiteMaintenanceStatus(content)
  42. }
  43. const SERVER_PRO_ACTIVE_USERS_METRIC_INTERVAL =
  44. settings.activeUserMetricInterval || 1000 * 60 * 60
  45. function publishActiveUsersMetric() {
  46. UserHandler.promises
  47. .countActiveUsers()
  48. .then(activeUserCount => metrics.gauge('num_active_users', activeUserCount))
  49. .catch(error => logger.error({ error }, 'error counting active users'))
  50. }
  51. module.exports = {
  52. initialise() {
  53. if (settings.enabledServices.includes('web') && statusFile) {
  54. logger.debug(
  55. { statusFile, interval: STATUS_FILE_CHECK_INTERVAL },
  56. 'monitoring site maintenance file'
  57. )
  58. checkSiteMaintenanceFileSync() // perform an initial synchronous check at start up
  59. const intervalHandle = setInterval(
  60. pollSiteMaintenanceFile,
  61. STATUS_FILE_CHECK_INTERVAL
  62. ) // continue checking periodically
  63. addOptionalCleanupHandlerAfterDrainingConnections(
  64. 'poll site maintenance file',
  65. () => {
  66. clearInterval(intervalHandle)
  67. }
  68. )
  69. }
  70. if (!Features.hasFeature('saas')) {
  71. publishActiveUsersMetric()
  72. const intervalHandle = setInterval(
  73. publishActiveUsersMetric,
  74. SERVER_PRO_ACTIVE_USERS_METRIC_INTERVAL
  75. )
  76. addRequiredCleanupHandlerBeforeDrainingConnections(
  77. 'publish server pro usage metrics',
  78. () => {
  79. clearInterval(intervalHandle)
  80. }
  81. )
  82. }
  83. },
  84. }