Revert the previous commit and add automated renewal logic

master
Jinseo Park 4 years ago
parent b29024172c
commit 9ac17dff65

@ -1 +1,2 @@
HTTP_PORT = 8080
HTTPS_PORT = 8443

@ -1 +1,2 @@
CREDENTIALS_ENABLED = 0
WEBHOOK_ENABLED = 0

@ -1 +1,6 @@
CREDENTIALS_ENABLED = 1
CREDENTIALS_PATH = /home/ubuntu/.certbot/config/live/algorithm-visualizer.org
CREDENTIALS_CA = fullchain.pem
CREDENTIALS_KEY = privkey.pem
CREDENTIALS_CERT = cert.pem
WEBHOOK_ENABLED = 1

@ -0,0 +1,7 @@
config-dir = /home/ubuntu/.certbot/config
work-dir = /home/ubuntu/.certbot/work
logs-dir = /home/ubuntu/.certbot/logs
email = parkjs814@gmail.com
authenticator = webroot
webroot-path = /home/ubuntu/server/public/frontend-built
domains = algorithm-visualizer.org

2053
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -20,6 +20,7 @@
"@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",
@ -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",

@ -4,20 +4,22 @@ import bodyParser from 'body-parser';
import * as Controllers from 'controllers';
import { NotFound } from 'ts-httpexceptions';
import compression from 'compression';
import { __PROD__, httpPort, webhookOptions } from 'config/environments';
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() {
@ -26,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) {
@ -36,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.');
@ -61,6 +63,12 @@ export default class Server {
await tracer.update(data.release);
});
}
if (credentials) {
cron.schedule('0 0 1 * *', () => {
issueHttpsCertificate();
});
}
}
getApiRouter() {
@ -100,5 +108,11 @@ export default class Server {
const httpServer = http.createServer(this.app);
httpServer.listen(httpPort);
console.info(`http: listening on port ${httpPort}`);
if (credentials) {
const httpsServer = https.createServer(credentials, this.app);
httpsServer.listen(httpsPort);
console.info(`https: listening on port ${httpsPort}`);
}
}
}

@ -1,9 +1,21 @@
import fs from 'fs';
import { ServerOptions } from 'https';
import path from 'path';
import { issueHttpsCertificate } from '../utils/misc';
require('dotenv-flow').config();
const {
NODE_ENV,
HTTP_PORT,
HTTPS_PORT,
CREDENTIALS_ENABLED,
CREDENTIALS_PATH,
CREDENTIALS_CA,
CREDENTIALS_KEY,
CREDENTIALS_CERT,
WEBHOOK_ENABLED,
WEBHOOK_SECRET,
@ -22,6 +34,14 @@ const isEnabled = (v: string) => v === '1';
const missingVars = [
'NODE_ENV',
'HTTP_PORT',
'HTTPS_PORT',
'CREDENTIALS_ENABLED',
...(isEnabled(CREDENTIALS_ENABLED) ? [
'CREDENTIALS_PATH',
'CREDENTIALS_CA',
'CREDENTIALS_KEY',
'CREDENTIALS_CERT',
] : []),
'WEBHOOK_ENABLED',
...(isEnabled(WEBHOOK_ENABLED) ? [
'WEBHOOK_SECRET',
@ -37,12 +57,27 @@ export const __PROD__ = NODE_ENV === 'production';
export const __DEV__ = NODE_ENV === 'development';
export const httpPort = parseInt(HTTP_PORT);
export const httpsPort = parseInt(HTTPS_PORT);
export const webhookOptions = isEnabled(WEBHOOK_ENABLED) ? {
path: '/webhook',
secret: WEBHOOK_SECRET,
} : undefined;
export let credentials: ServerOptions | undefined;
if (isEnabled(CREDENTIALS_ENABLED)) {
if (fs.existsSync(CREDENTIALS_PATH)) {
const readCredentials = (file: string) => fs.readFileSync(path.resolve(CREDENTIALS_PATH, file));
credentials = {
ca: readCredentials(CREDENTIALS_CA),
key: readCredentials(CREDENTIALS_KEY),
cert: readCredentials(CREDENTIALS_CERT),
};
} else {
issueHttpsCertificate();
}
}
export const githubClientId = GITHUB_CLIENT_ID;
export const githubClientSecret = GITHUB_CLIENT_SECRET;

@ -1,9 +1,12 @@
import { NextFunction, Request, Response } from 'express';
import { credentials } from 'config/environments';
export function redirectMiddleware() {
return (req: Request, res: Response, next: NextFunction) => {
if (req.hostname === 'algo-visualizer.jasonpark.me') {
res.redirect(301, 'https://algorithm-visualizer.org/');
} else if (credentials && !req.secure) {
res.redirect(301, `https://${req.hostname}${req.url}`);
} else {
next();
}

@ -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<string> {
export function execute(command: string, { stdout, stderr, ...options }: ExecuteOptions = {}): Promise<string> {
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}.`));
}
});
}

Loading…
Cancel
Save