aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/server/index.ts16
-rw-r--r--src/server/session.ts93
-rw-r--r--src/server/session_manager/crash_email.ts36
-rw-r--r--src/server/session_manager/email.ts26
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