diff --git a/certbot.ini b/certbot.ini index 891ebf6..dab450e 100644 --- a/certbot.ini +++ b/certbot.ini @@ -1,7 +1,7 @@ config-dir = /home/ubuntu/.certbot/config work-dir = /home/ubuntu/.certbot/work logs-dir = /home/ubuntu/.certbot/logs -email = jason.park@gatech.edu +email = parkjs814@gmail.com authenticator = webroot webroot-path = /home/ubuntu/server/public/frontend-built domains = algorithm-visualizer.org diff --git a/package-lock.json b/package-lock.json index 646e43c..80b8362 100644 --- a/package-lock.json +++ b/package-lock.json @@ -112,6 +112,12 @@ "integrity": "sha512-1YKeT4JitGgE4SOzyB9eMwO0nGVNkNEsm9qlIt1Lqm/tG2QEiSMTD4kS3aO6L+w5SClLVxALmIBESK6Mk5wX0A==", "dev": true }, + "@types/node-cron": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.1.tgz", + "integrity": "sha512-BkMHHonDT8NJUE/pQ3kr5v2GLDKm5or9btLBoBx4F2MB2cuqYC748LYMDC55VlrLI5qZZv+Qgc3m4P3dBPcmeg==", + "dev": true + }, "@types/range-parser": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", @@ -199,13 +205,13 @@ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "aws-sdk": { - "version": "2.480.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.480.0.tgz", - "integrity": "sha512-X6xOgBeg8ZZJPnL4/wHXFIP6QF2SBABqw/F5l5zT/vEPUfmrIg24LVSUwPXmV6ZMh4hwsuuOjO910MbjNoHXXg==", + "version": "2.814.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.814.0.tgz", + "integrity": "sha512-empd1m/J/MAkL6d9OeRpmg9thobULu0wk4v8W3JToaxGi2TD7PIdvE6yliZKyOVAdJINhBWEBhxR4OUIHhcGbQ==", "requires": { - "buffer": "4.9.1", + "buffer": "4.9.2", "events": "1.1.1", - "ieee754": "1.1.8", + "ieee754": "1.1.13", "jmespath": "0.15.0", "querystring": "0.2.0", "sax": "1.2.1", @@ -215,12 +221,11 @@ } }, "axios": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", - "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.2.tgz", + "integrity": "sha512-87otirqUw3e8CzHTMO+/9kh/FSgXt/eVDvipijwDtEuwbkySWZ9SBm6VEubmJ/kLKEoLQV/POhxXFb66bfekfg==", "requires": { - "follow-redirects": "1.5.10", - "is-buffer": "^2.0.2" + "follow-redirects": "^1.14.0" } }, "balanced-match": { @@ -230,9 +235,9 @@ "dev": true }, "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "basic-auth": { "version": "2.0.1", @@ -280,9 +285,9 @@ } }, "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "requires": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", @@ -449,14 +454,6 @@ "integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg==", "dev": true }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -651,12 +648,9 @@ } }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - } + "version": "1.14.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.5.tgz", + "integrity": "sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==" }, "forwarded": { "version": "0.1.2", @@ -722,9 +716,9 @@ "dev": true }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "http-errors": { @@ -748,9 +742,9 @@ } }, "ieee754": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", - "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "indent-string": { "version": "2.1.0", @@ -787,11 +781,6 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" - }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", @@ -970,6 +959,19 @@ } } }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "moment-timezone": { + "version": "0.5.34", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", + "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==", + "requires": { + "moment": ">= 2.9.0" + } + }, "morgan": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", @@ -1002,6 +1004,14 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-cron": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.0.tgz", + "integrity": "sha512-DDwIvvuCwrNiaU7HEivFDULcaQualDv7KoNlB/UU1wPW0n1tDEmBJKhEIE6DlF2FuoOHcNbLJ8ITL2Iv/3AWmA==", + "requires": { + "moment-timezone": "^0.5.31" + } + }, "node-notifier": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", @@ -1091,9 +1101,9 @@ "dev": true }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, "path-to-regexp": { @@ -1423,9 +1433,9 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tree-kill": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", - "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, "trim-newlines": { diff --git a/package.json b/package.json index 5c7f111..d33045a 100644 --- a/package.json +++ b/package.json @@ -20,14 +20,15 @@ "@types/fs-extra": "^7.0.0", "@types/morgan": "^1.7.35", "@types/node": "^12.0.0", + "@types/node-cron": "^3.0.1", "@types/remove-markdown": "^0.1.1", "@types/uuid": "^3.4.4", "ts-node-dev": "^1.0.0-pre.39", "tslint": "^5.16.0" }, "dependencies": { - "aws-sdk": "^2.480.0", - "axios": "^0.19.0", + "aws-sdk": "^2.814.0", + "axios": "^0.21.2", "body-parser": "^1.18.2", "compression": "^1.7.3", "dotenv": "^8.0.0", @@ -36,6 +37,7 @@ "express-github-webhook": "^1.0.6", "fs-extra": "^6.0.1", "morgan": "^1.9.1", + "node-cron": "^3.0.0", "remove-markdown": "^0.3.0", "ts-httpexceptions": "^4.1.0", "ts-node": "^8.1.0", diff --git a/src/Server.ts b/src/Server.ts index 3130322..637b2df 100644 --- a/src/Server.ts +++ b/src/Server.ts @@ -7,18 +7,19 @@ import compression from 'compression'; import { __PROD__, credentials, httpPort, httpsPort, webhookOptions } from 'config/environments'; import http from 'http'; import https from 'https'; +import cron from 'node-cron'; import { Hierarchy } from 'models'; import * as Tracers from 'tracers'; import { errorHandlerMiddleware, frontendMiddleware, redirectMiddleware } from 'middlewares'; -import { execute, pull } from 'utils/misc'; +import { execute, issueHttpsCertificate, pull } from 'utils/misc'; import { frontendBuildDir, frontendBuiltDir, frontendDir, rootDir } from 'config/paths'; const Webhook = require('express-github-webhook'); export default class Server { - private readonly app = express(); readonly hierarchy = new Hierarchy(); readonly tracers = Object.values(Tracers).map(Tracer => new Tracer()); + private readonly app = express(); private readonly webhook = webhookOptions && Webhook(webhookOptions); constructor() { @@ -27,7 +28,7 @@ export default class Server { .use(morgan(__PROD__ ? 'tiny' : 'dev')) .use(redirectMiddleware()) .use(bodyParser.json()) - .use(bodyParser.urlencoded({extended: true})) + .use(bodyParser.urlencoded({ extended: true })) .use('/api', this.getApiRouter()) .use(frontendMiddleware(this)); if (this.webhook) { @@ -37,7 +38,7 @@ export default class Server { if (this.webhook) { this.webhook.on('push', async (repo: string, data: any) => { - const {ref, head_commit} = data; + const { ref, head_commit } = data; if (ref !== 'refs/heads/master') return; if (!head_commit) throw new Error('The `head_commit` is empty.'); @@ -62,6 +63,12 @@ export default class Server { await tracer.update(data.release); }); } + + if (credentials) { + cron.schedule('0 0 1 * *', () => { + issueHttpsCertificate(); + }); + } } getApiRouter() { diff --git a/src/config/environments.ts b/src/config/environments.ts index 088dca7..912d9c8 100644 --- a/src/config/environments.ts +++ b/src/config/environments.ts @@ -1,8 +1,7 @@ import fs from 'fs'; import { ServerOptions } from 'https'; import path from 'path'; -import { spawn } from 'child_process'; -import { rootDir } from 'config/paths'; +import { issueHttpsCertificate } from '../utils/misc'; require('dotenv-flow').config(); @@ -75,18 +74,7 @@ if (isEnabled(CREDENTIALS_ENABLED)) { cert: readCredentials(CREDENTIALS_CERT), }; } else { - const certbotIniPath = path.resolve(rootDir, 'certbot.ini'); - const childProcess = spawn('certbot', ['certonly', '--non-interactive', '--agree-tos', '--config', certbotIniPath]); - childProcess.stdout.pipe(process.stdout); - childProcess.stderr.pipe(process.stderr); - childProcess.on('error', console.error); - childProcess.on('exit', code => { - if (code === 0) { - process.exit(0); - } else { - console.error(new Error(`certbot failed with exit code ${code}.`)); - } - }); + issueHttpsCertificate(); } } diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 56f73c1..1b5fe67 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -3,10 +3,12 @@ import fs from 'fs-extra'; import { File } from 'models'; import removeMarkdown from 'remove-markdown'; import * as child_process from 'child_process'; -import { ExecOptions } from 'child_process'; +import { ExecOptions, spawn } from 'child_process'; +import { rootDir } from '../config/paths'; +import path from 'path'; export function download(url: string, localPath: string) { - return axios({url, method: 'GET', responseType: 'stream'}) + return axios({ url, method: 'GET', responseType: 'stream' }) .then(response => new Promise((resolve, reject) => { const writer = fs.createWriteStream(localPath); writer.on('finish', resolve); @@ -41,7 +43,7 @@ export function getDescription(files: File[]) { const lines = readmeFile.content.split('\n'); lines.shift(); while (lines.length && !lines[0].trim()) lines.shift(); - let descriptionLines = []; + const descriptionLines = []; while (lines.length && lines[0].trim()) descriptionLines.push(lines.shift()); return removeMarkdown(descriptionLines.join(' ')); } @@ -49,9 +51,9 @@ export function getDescription(files: File[]) { type ExecuteOptions = ExecOptions & { stdout?: NodeJS.WriteStream; stderr?: NodeJS.WriteStream; -} +}; -export function execute(command: string, {stdout, stderr, ...options}: ExecuteOptions = {}): Promise { +export function execute(command: string, { stdout, stderr, ...options }: ExecuteOptions = {}): Promise { return new Promise((resolve, reject) => { const child = child_process.exec(command, options, (error, stdout, stderr) => { if (error) return reject(error.code ? new Error(stderr) : error); @@ -61,3 +63,18 @@ export function execute(command: string, {stdout, stderr, ...options}: ExecuteOp if (child.stderr && stderr) child.stderr.pipe(stderr); }); } + +export function issueHttpsCertificate() { + const certbotIniPath = path.resolve(rootDir, 'certbot.ini'); + const childProcess = spawn('certbot', ['certonly', '--non-interactive', '--agree-tos', '--config', certbotIniPath]); + childProcess.stdout.pipe(process.stdout); + childProcess.stderr.pipe(process.stderr); + childProcess.on('error', console.error); + childProcess.on('exit', code => { + if (code === 0) { + process.exit(0); + } else { + console.error(new Error(`certbot failed with exit code ${code}.`)); + } + }); +}