diff options
author | Sam Wilkins <samwilkins333@gmail.com> | 2020-01-03 01:03:35 -0800 |
---|---|---|
committer | Sam Wilkins <samwilkins333@gmail.com> | 2020-01-03 01:03:35 -0800 |
commit | 75a0a9be12d4902898f8b82ec1df4e2ce41bb326 (patch) | |
tree | cc872925a1144c71898f3a411f42a590b256e148 | |
parent | e85521e0be77eb01ca34a9346a760c5f7c656a4e (diff) |
various session updates
-rw-r--r-- | src/server/index.ts | 16 | ||||
-rw-r--r-- | src/server/session.ts | 93 | ||||
-rw-r--r-- | src/server/session_manager/crash_email.ts | 36 | ||||
-rw-r--r-- | src/server/session_manager/email.ts | 26 |
4 files changed, 92 insertions, 79 deletions
diff --git a/src/server/index.ts b/src/server/index.ts index 597198c04..8706c2d84 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -10,7 +10,7 @@ import initializeServer from './Initialization'; import RouteManager, { Method, _success, _permission_denied, _error, _invalid, PublicHandler } from './RouteManager'; import * as qs from 'query-string'; import UtilManager from './ApiManagers/UtilManager'; -import { SearchManager, SolrManager } from './ApiManagers/SearchManager'; +import { SearchManager } from './ApiManagers/SearchManager'; import UserManager from './ApiManagers/UserManager'; import { WebSocket } from './Websocket/Websocket'; import DownloadManager from './ApiManagers/DownloadManager'; @@ -21,10 +21,10 @@ import UploadManager from "./ApiManagers/UploadManager"; import { log_execution } from "./ActionUtilities"; import GeneralGoogleManager from "./ApiManagers/GeneralGoogleManager"; import GooglePhotosManager from "./ApiManagers/GooglePhotosManager"; -import { disconnect } from "../server/Initialization"; import { Logger } from "./ProcessFactory"; import { yellow } from "colors"; import { Session } from "./session"; +import { Utils } from "../Utils"; export const publicDirectory = path.resolve(__dirname, "public"); export const filesDirectory = path.resolve(publicDirectory, "files"); @@ -35,6 +35,7 @@ export const filesDirectory = path.resolve(publicDirectory, "files"); * before clients can access the server should be run or awaited here. */ async function preliminaryFunctions() { + await Session.distributeKey(); await Logger.initialize(); await GoogleCredentialsLoader.loadCredentials(); GoogleApiServerUtils.processProjectCredentials(); @@ -89,14 +90,13 @@ function routeSetter({ isRelease, addSupervisedRoute, logRegistrationOutcome }: addSupervisedRoute({ method: Method.GET, - subscription: "/kill", - secureHandler: ({ res }) => { - const { send } = process; - if (send) { + subscription: new RouteSubscriber("kill").add("password"), + secureHandler: ({ req, res }) => { + if (req.params.password === Session.key) { + process.send!({ action: yellow("kill") }); res.send("Server successfully killed."); - send({ action: "kill" }); } else { - res.send("Server worker does not have a viable send protocol. Did not attempt to kill server."); + res.redirect("/home"); } } }); diff --git a/src/server/session.ts b/src/server/session.ts index 1fed9746e..c66461d03 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1,9 +1,8 @@ import { yellow, red, cyan, magenta, green } from "colors"; import { isMaster, on, fork, setupMaster, Worker } from "cluster"; -import { identifier } from "./session_manager/config"; import InputManager from "./session_manager/input_manager"; import { execSync } from "child_process"; -import { CrashEmail } from "./session_manager/crash_email"; +import { Email } from "./session_manager/email"; import { get } from "request-promise"; import { WebSocket } from "./Websocket/Websocket"; import { Utils } from "../Utils"; @@ -15,21 +14,59 @@ const admin = ["samuel_wilkins@brown.edu"]; export namespace Session { - const masterIdentifier = yellow("__master__"); - const workerIdentifier = magenta("__worker__"); - - function killAll() { - execSync(onWindows ? "taskkill /f /im node.exe" : "killall -9 node"); - } + export let key: string; + export const signature = "Best,\nServer Session Manager"; + let activeWorker: Worker; + const masterIdentifier = `${yellow("__master__")}:`; + const workerIdentifier = `${magenta("__worker__")}:`; function log(message?: any, ...optionalParams: any[]) { - const identifier = `${isMaster ? masterIdentifier : workerIdentifier}:`; + const identifier = isMaster ? masterIdentifier : workerIdentifier; console.log(identifier, message, ...optionalParams); } + export async function distributeKey() { + key = Utils.GenerateGuid(); + const timestamp = new Date().toUTCString(); + const content = `The key for this session (started @ ${timestamp}) is ${key}.\n\n${signature}`; + return Promise.all(admin.map(recipient => Email.dispatch(recipient, "Server Termination Key", content))); + } + + function tryKillActiveWorker() { + if (activeWorker && !activeWorker.isDead()) { + activeWorker.process.kill(); + return true; + } + return false; + } + + function messageHandler({ lifecycle, action }: any) { + if (action) { + console.log(`${workerIdentifier} action requested (${action})`); + switch (action) { + case "kill": + log(red("An authorized user has ended the server from the /kill route")); + tryKillActiveWorker(); + process.exit(0); + } + } else if (lifecycle) { + console.log(`${workerIdentifier} lifecycle phase (${lifecycle})`); + } + } + + function crashReport({ name, message, stack }: Error) { + return [ + "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", + `name:\n${name}`, + `message:\n${message}`, + `stack:\n${stack}`, + "The server is already restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress.", + signature + ].join("\n\n"); + } + export async function initialize(work: Function) { let listening = false; - let active: Worker; if (isMaster) { process.on("uncaughtException", error => { if (error.message !== "Channel closed") { @@ -41,21 +78,9 @@ export namespace Session { }); setupMaster({ silent: true }); const spawn = () => { - if (active && !active.isDead()) { - active.process.kill(); - } - active = fork(); - active.on("message", ({ lifecycle, action }) => { - if (action) { - console.log(`${workerIdentifier}: action requested (${action})`); - switch (action) { - case "kill": - log(red("An authorized user has ended the server from the /kill route")); - } - } else if (lifecycle) { - console.log(`${workerIdentifier}: lifecycle phase (${lifecycle})`); - } - }); + tryKillActiveWorker(); + activeWorker = fork(); + activeWorker.on("message", messageHandler); }; spawn(); on("exit", ({ process: { pid } }, code, signal) => { @@ -63,15 +88,13 @@ export namespace Session { log(cyan(prompt)); spawn(); }); - const restart = () => { + const { registerCommand } = new InputManager({ identifier: masterIdentifier }); + registerCommand("exit", [], () => execSync(onWindows ? "taskkill /f /im node.exe" : "killall -9 node")); + registerCommand("pull", [], () => execSync("git pull", { stdio: ["ignore", "inherit", "inherit"] })); + registerCommand("restart", [], () => { listening = false; - const prompt = `Server worker with process id ${active.process.pid} has been manually killed.`; - log(cyan(prompt)); - spawn(); - }; - const { registerCommand } = new InputManager({ identifier }); - registerCommand("exit", [], killAll); - registerCommand("restart", [], restart); + tryKillActiveWorker(); + }); } else { const logLifecycleEvent = (lifecycle: string) => process.send?.({ lifecycle }); logLifecycleEvent(green("initializing...")); @@ -80,7 +103,7 @@ export namespace Session { return; } listening = false; - await CrashEmail.dispatch(error, admin); + await Promise.all(admin.map(recipient => Email.dispatch(recipient, "Dash Web Server Crash", crashReport(error)))); const { _socket } = WebSocket; if (_socket) { Utils.Emit(_socket, MessageStore.ConnectionTerminated, "Manual"); @@ -96,7 +119,7 @@ export namespace Session { try { await get(heartbeat); if (!listening) { - logLifecycleEvent(green("server is now successfully listening...")); + logLifecycleEvent(green("listening...")); } listening = true; resolve(); diff --git a/src/server/session_manager/crash_email.ts b/src/server/session_manager/crash_email.ts deleted file mode 100644 index 7783cd779..000000000 --- a/src/server/session_manager/crash_email.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as nodemailer from "nodemailer"; -import { MailOptions } from "nodemailer/lib/json-transport"; - -export namespace CrashEmail { - - export async function dispatch(error: Error, recipients: string[]): Promise<boolean[]> { - const smtpTransport = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: 'brownptcdash@gmail.com', - pass: 'browngfx1' - } - }); - return Promise.all(recipients.map(recipient => new Promise<boolean>(resolve => { - const mailOptions = { - to: recipient, - from: 'brownptcdash@gmail.com', - subject: 'Dash Server Crash', - text: emailText(recipient, error) - } as MailOptions; - smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); - }))); - } - - function emailText(recipient: string, { name, message, stack }: Error) { - return [ - `Hey ${recipient.split("@")[0]},`, - "You, as a Dash Administrator, are being notified of a server crash event. Here's what we know:", - `name:\n${name}`, - `message:\n${message}`, - `stack:\n${stack}`, - "The server is already restarting itself, but if you're concerned, use the Remote Desktop Connection to monitor progress." - ].join("\n\n"); - } - -}
\ No newline at end of file diff --git a/src/server/session_manager/email.ts b/src/server/session_manager/email.ts new file mode 100644 index 000000000..a638644db --- /dev/null +++ b/src/server/session_manager/email.ts @@ -0,0 +1,26 @@ +import * as nodemailer from "nodemailer"; +import { MailOptions } from "nodemailer/lib/json-transport"; + +export namespace Email { + + const smtpTransport = nodemailer.createTransport({ + service: 'Gmail', + auth: { + user: 'brownptcdash@gmail.com', + pass: 'browngfx1' + } + }); + + export async function dispatch(recipient: string, subject: string, content: string): Promise<boolean> { + const mailOptions = { + to: recipient, + from: 'brownptcdash@gmail.com', + subject, + text: `Hello ${recipient.split("@")[0]},\n\n${content}` + } as MailOptions; + return new Promise<boolean>(resolve => { + smtpTransport.sendMail(mailOptions, (dispatchError: Error | null) => resolve(dispatchError === null)); + }); + } + +}
\ No newline at end of file |