SiteAdminHandler.mjs 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import logger from '@overleaf/logger'
  2. import settings from '@overleaf/settings'
  3. import fs from 'node:fs'
  4. import {
  5. addOptionalCleanupHandlerAfterDrainingConnections,
  6. addRequiredCleanupHandlerBeforeDrainingConnections,
  7. } from './GracefulShutdown.mjs'
  8. import Features from './Features.mjs'
  9. import UserHandler from '../Features/User/UserHandler.mjs'
  10. import metrics from '@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. export default {
  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. }