WORKALRSGDJHX / src /server.ts
Xaiph's picture
Upload 62 files
9de8f9d
import { assertConfigIsValid, config } from "./config";
import "source-map-support/register";
import express from "express";
import cors from "cors";
import pinoHttp from "pino-http";
import childProcess from "child_process";
import { logger } from "./logger";
import { keyPool } from "./key-management";
import { adminRouter } from "./admin/routes";
import { proxyRouter } from "./proxy/routes";
import { handleInfoPage } from "./info-page";
import { logQueue } from "./prompt-logging";
import { start as startRequestQueue } from "./proxy/queue";
import { init as initUserStore } from "./proxy/auth/user-store";
import { checkOrigin } from "./proxy/check-origin";
const PORT = config.port;
const INTERVAL_TIME = 60000; // Config check interval time in milliseconds (e.g., 60000 ms = 1 minute)
const app = express();
// middleware
app.use(
pinoHttp({
quietReqLogger: true,
logger,
autoLogging: {
ignore: (req) => {
const ignored = ["/proxy/kobold/api/v1/model", "/health"];
return ignored.includes(req.url as string);
},
},
redact: {
paths: [
"req.headers.cookie",
'res.headers["set-cookie"]',
//"req.headers.authorization",
//'req.headers["x-api-key"]',
//'req.headers["x-forwarded-for"]',
//'req.headers["x-real-ip"]',
//'req.headers["true-client-ip"]',
//'req.headers["cf-connecting-ip"]',
// Don't log the prompt text on transform errors
//"body.messages",
//"body.prompt",
],
censor: "********",
},
})
);
app.get("/health", (_req, res) => res.sendStatus(200));
app.use((req, _res, next) => {
req.startTime = Date.now();
req.retryCount = 0;
next();
});
app.use(cors());
app.use(
express.json({ limit: "10mb" }),
express.urlencoded({ extended: true, limit: "10mb" })
);
// TODO: Detect (or support manual configuration of) whether the app is behind
// a load balancer/reverse proxy, which is necessary to determine request IP
// addresses correctly.
app.set("trust proxy", true);
// routes
app.use(checkOrigin);
app.get("/", handleInfoPage);
app.use("/admin", adminRouter);
app.use("/proxy", proxyRouter);
// 500 and 404
app.use((err: any, _req: unknown, res: express.Response, _next: unknown) => {
if (err.status) {
res.status(err.status).json({ error: err.message });
} else {
logger.error(err);
res.status(500).json({
error: {
type: "proxy_error",
message: err.message,
stack: err.stack,
proxy_note: `Reverse proxy encountered an internal server error.`,
},
});
}
});
app.use((_req: unknown, res: express.Response) => {
res.status(404).json({ error: "Not found" });
});
async function start() {
logger.info("Server starting up...");
await setBuildInfo();
logger.info("Checking configs and external dependencies...");
await assertConfigIsValid();
keyPool.init();
if (config.gatekeeper === "user_token") {
await initUserStore();
}
if (config.promptLogging) {
logger.info("Starting prompt logging...");
logQueue.start();
}
if (config.queueMode !== "none") {
logger.info("Starting request queue...");
startRequestQueue();
}
app.listen(PORT, async () => {
logger.info({ port: PORT }, "Now listening for connections.");
registerUncaughtExceptionHandler();
});
logger.info(
{ build: process.env.BUILD_INFO, nodeEnv: process.env.NODE_ENV },
"Startup complete."
);
setInterval(async () => {
logger.info("-!!!-ALERT-!!!- CHECKING ONLINE CONFIG. SERVER MAY HANG. -!!!-ALERT-!!!-");
await assertConfigIsValid();
}, INTERVAL_TIME);
}
function registerUncaughtExceptionHandler() {
process.on("uncaughtException", (err: any) => {
logger.error(
{ err, stack: err?.stack },
"UNCAUGHT EXCEPTION. Please report this error trace."
);
});
process.on("unhandledRejection", (err: any) => {
logger.error(
{ err, stack: err?.stack },
"UNCAUGHT PROMISE REJECTION. Please report this error trace."
);
});
}
/**
* Attepts to collect information about the current build from either the
* environment or the git repo used to build the image (only works if not
* .dockerignore'd). If you're running a sekrit club fork, you can no-op this
* function and set the BUILD_INFO env var manually, though I would prefer you
* didn't set it to something misleading.
*/
async function setBuildInfo() {
/* // Render .dockerignore's the .git directory but provides info in the env
if (process.env.RENDER) {
const sha = process.env.RENDER_GIT_COMMIT?.slice(0, 7) || "unknown SHA";
const branch = process.env.RENDER_GIT_BRANCH || "unknown branch";
const repo = process.env.RENDER_GIT_REPO_SLUG || "unknown repo";
const buildInfo = `${sha} (${branch}@${repo})`;
//process.env.BUILD_INFO = buildInfo;
logger.info({ build: buildInfo }, "Got build info from Render config.");
return;
}
try {
// Ignore git's complaints about dubious directory ownership on Huggingface
// (which evidently runs dockerized Spaces on Windows with weird NTFS perms)
if (process.env.SPACE_ID) {
childProcess.execSync("git config --global --add safe.directory /app");
}
const promisifyExec = (cmd: string) =>
new Promise((resolve, reject) => {
childProcess.exec(cmd, (err, stdout) =>
err ? reject(err) : resolve(stdout)
);
});
const promises = [
promisifyExec("git rev-parse --short HEAD"),
promisifyExec("git rev-parse --abbrev-ref HEAD"),
promisifyExec("git config --get remote.origin.url"),
promisifyExec("git status --porcelain"),
].map((p) => p.then((result: any) => result.toString().trim()));
let [sha, branch, remote, status] = await Promise.all(promises);
remote = remote.match(/.*[\/:]([\w-]+)\/([\w\-\.]+?)(?:\.git)?$/) || [];
const repo = remote.slice(-2).join("/");
status = status
// ignore Dockerfile changes since that's how the user deploys the app
.split("\n")
.filter((line: string) => !line.endsWith("Dockerfile") && line);
const changes = status.length > 0;
const build = `${sha}${changes ? " (modified)" : ""} (${branch}@${repo})`;
process.env.BUILD_INFO = build;
logger.info({ build, status, changes }, "Got build info from Git.");
} catch (error: any) {
logger.error(
{
error,
stdout: error.stdout.toString(),
stderr: error.stderr.toString(),
},
"Failed to get commit SHA.",
error
);
process.env.BUILD_INFO = "unknown";
}*/
process.env.BUILD_INFO = "96cf4a0 (main@khanon/oai-reverse-proxy)";
}
start();