webhook_deploy_gogs.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /**
  2. * A server that listens on a port for Gogs's webhooks.
  3. * It will check for a push event on the master branch, then deploy the project on the machine.
  4. * The webhook's secret is check to ensure no malicious request from unknown sources.
  5. *
  6. * Usage :
  7. * Set the "WEBHOOK_SECRET" environment variable with the webhook's secret.
  8. *
  9. * @see https://gogs.io/docs/features/webhook
  10. *
  11. *
  12. * @author Antoine Sauvage <contact@asauvage.fr>
  13. * @license MIT 2019 - https://opensource.org/licenses/MIT
  14. * @see https://gist.github.com/rigwild/4238a13cb3501c6e85065b403a71b475
  15. */
  16. 'use strict'
  17. const fs = require('fs')
  18. const http = require('http')
  19. const path = require('path')
  20. const { promisify } = require('util')
  21. const exec = promisify(require('child_process').exec)
  22. // The port which this script will listen on
  23. const port = parseInt(process.env.WEBHOOK_PORT, 10)
  24. // The path to the project directory
  25. const projectPath = path.resolve('.')
  26. // The webhook secret to check the origin of the webhook event
  27. // Check the "WEBHOOK_SECRET" environment variable is set
  28. if (!process.env.WEBHOOK_SECRET && process.env.WEBHOOK_SECRET !== '') {
  29. console.error(`${new Date().toLocaleString()} - The "WEBHOOK_SECRET" environment variable is not set.`)
  30. process.exit(1)
  31. }
  32. const webhookSecret = process.env.WEBHOOK_SECRET
  33. // Check whether the project path exists and script has read access
  34. console.log(`${new Date().toLocaleString()} - Configured project path : ${projectPath}\n`)
  35. try {
  36. fs.accessSync(projectPath, fs.constants.W_OK)
  37. console.log(`${new Date().toLocaleString()} - The project's directory exists and script has write permission.`)
  38. }
  39. catch (err) {
  40. console.error(`${new Date().toLocaleString()} - The project's directory does not exist or script has not write permission.`, err)
  41. process.exit(1)
  42. }
  43. // Check the "PORT" environment variable is set to a valid integer
  44. if (!process.env.PORT || !parseInt(process.env.PORT, 10)) {
  45. console.error(`${new Date().toLocaleString()} - The "PORT" environment variable is not set or is not an integer.`)
  46. process.exit(1)
  47. }
  48. // Check the "SERVE_CLIENT" environment variable is set to 'true' or 'false'
  49. if (!process.env.SERVE_CLIENT || !['true', 'false'].some(x => x === process.env.SERVE_CLIENT)) {
  50. console.error(`${new Date().toLocaleString()} - The "SERVE_CLIENT" environment variable is not set or is not 'true' or 'false'`)
  51. process.exit(1)
  52. }
  53. let env = {
  54. PORT: parseInt(process.env.PORT, 10),
  55. SERVE_CLIENT: process.env.SERVE_CLIENT,
  56. IMAGES_PATH: process.env.IMAGES_PATH
  57. }
  58. env = Object.assign(process.env, env)
  59. // Recap used environment variables
  60. Object.keys(env).forEach(x => console.log(`${x}=${env[x]}`))
  61. // The script that will be executed by the machine
  62. const deployScript = `cd ${projectPath}` +
  63. ' && git reset --hard HEAD' +
  64. ' && git pull origin master' +
  65. ' && docker-compose down' +
  66. ' && docker-compose build' +
  67. ' && docker-compose up -d'
  68. console.log('\nConfiguration is valid. Starting the webhook-listener server ...')
  69. const deploy = async () => {
  70. try {
  71. console.log(`${new Date().toLocaleString()} - Deploying project ...`)
  72. const startTime = process.hrtime()
  73. const { stdout, stderr } = await exec(
  74. deployScript,
  75. {
  76. cwd: projectPath,
  77. env
  78. }
  79. )
  80. const endTime = process.hrtime(startTime)
  81. // Logs received from the deploy script are sent in stdout :
  82. // git fetch and docker-compose build/up are writing their success logs in stderr...
  83. // A deploy fail will be printed in stderr (in the catch)
  84. console.log('stdout :\n', stdout)
  85. console.log('stderr :\n', stderr)
  86. console.log(`\n${new Date().toLocaleString()} - Project successfully deployed with Docker.`)
  87. console.log(`Total deploy time : ${endTime[0]}s and ${endTime[1] / 1000000}ms.`)
  88. }
  89. catch (err) {
  90. console.error(`\n${new Date().toLocaleString()} - Error deploying project.\n`, err)
  91. }
  92. }
  93. // Configuration is fine, start the server
  94. http.createServer((req, res) => {
  95. // Check the method is POST
  96. if (req.method !== 'POST') {
  97. res.statusCode = 400
  98. res.write('Wrong HTTP method.')
  99. res.end()
  100. return
  101. }
  102. // Check the event is a push
  103. if (req.headers['x-gogs-event'] !== 'push') {
  104. res.statusCode = 200
  105. res.write('OK. Not a push event.')
  106. res.end()
  107. return
  108. }
  109. // Answer OK and close the connection
  110. res.statusCode = 200
  111. res.write('OK')
  112. res.end()
  113. let body = []
  114. req.on('data', chunk => body.push(chunk))
  115. req.on('end', () => {
  116. try {
  117. body = JSON.parse(Buffer.concat(body).toString())
  118. // Check if the event was on master
  119. if (!body.ref || body.ref !== 'refs/heads/master') return
  120. // Check if secret matches
  121. if (!body.secret || body.secret !== webhookSecret) return
  122. console.log(`${new Date()} - Valid webhook event. Push on master was sent. Deployement process starts.`)
  123. deploy()
  124. }
  125. catch (err) {
  126. console.error(`${new Date()} - Invalid JSON was received`)
  127. }
  128. })
  129. }).listen(port)
  130. console.log(`${new Date().toLocaleString()} - Server is listening on http://localhost:${port}/`)