add everything!

This commit is contained in:
tezlm 2022-03-15 23:02:09 -07:00
parent f645e0d9cd
commit be9452d87f
16 changed files with 221 additions and 32 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
node_modules
overlay
server.log

View file

@ -1,9 +1,10 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>404</title>
<meta charset="utf8" />
<link rel="stylesheet" href="/assets/style.css"></link>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/assets/style.css?{{time}}"></link>
</head>
<body>
<p>couldn't find <code>{{file}}</code>!</p>

View file

@ -1,10 +1,11 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>index of {{dir}}</title>
<meta charset="utf8" />
<link rel="stylesheet" href="/assets/style.css"></link>
<link rel="stylesheet" href="/assets/directory.css"></link>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/assets/style.css?{{time}}"></link>
<link rel="stylesheet" href="/assets/directory.css?{{time}}"></link>
</head>
<body>
<h1>index of {{dir}}</h1>

View file

@ -1,12 +1,14 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<title>{{title}}</title>
<meta charset="utf8" />
<link rel="stylesheet" href="/assets/style.css"></link>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/assets/style.css?{{time}}"></link>
<link rel="stylesheet" href="/assets/code.css?{{time}}"></link>
</head>
<body>
{{{body}}}
{{{body}}}
</body>
</html>

View file

@ -1,7 +1,7 @@
const fs = require("fs");
const path = require("path");
const mustache = require("mustache");
const time = Date.now().toString(32).toString(36);
const template = fs.readFileSync("assets/directory.html", "utf8");
exports.hash = (where) => {
@ -20,10 +20,11 @@ exports.render = (where, name) => {
for(let file of names) {
const stat = fs.statSync(path.join(where, file));
if(stat.isDirectory()) file = file + "/";
files.push({
name: file,
link: path.join(name, file),
date: stat.mtime.toISOString(),
date: stat.mtime.toISOString().slice(0, -5).replace("T", " "),
size: stat.isDirectory() ? "dir" : fmtSize(stat.size),
});
}
@ -33,7 +34,7 @@ exports.render = (where, name) => {
a.size !== "dir" && b.size === "dir" ? 1 :
a.name > b.name);
return mustache.render(template, { dir: name, files: sorted });
return mustache.render(template, { dir: name, files: sorted, time });
}
function fmtSize(size) {

View file

@ -1,4 +1,4 @@
const fs = require("fs");
exports.hash = (where) => fs.statSync(where).mtime;
exports.render = (where) => fs.readFileSync(where, "utf8");
exports.hash = (where) => fs.statSync(where).mtime.valueOf();
exports.render = (where) => fs.readFileSync(where);

50
handlers/gemini.js Normal file
View file

@ -0,0 +1,50 @@
const fs = require("fs");
const path = require("path");
const mustache = require("mustache");
const time = Date.now().toString(36);
const template = fs.readFileSync("assets/wrapper.html", "utf8");
const clean = (str) => str.replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;");
exports.hash = (where) => fs.statSync(where).mtime.valueOf();
exports.render = (where) => {
let pre = false;
let list = false;
let html = "";
for(let line of fs.readFileSync(where, "utf8").split("\n")) {
const header = line.match(/^(#{1,3}) */);
const link = line.match(/^=> *(\S+) *(.+)/);
if(line.startsWith("*")) {
if(!list) html += "<ul>\n";
list = true;
html += `\t<li>${clean(line.replace(/^\* */, ""))}</li>\n`;
continue;
} else if(list) {
html += "</ul>\n";
list = false;
}
if(line === "```") {
html += `<${pre ? "/" : ""}pre>\n`
pre = !pre;
} else if(pre) {
html += clean(line) + "\n";
} else if(header) {
const n = header[1].length;
html += `<h${n}>${clean(line)}</h${n}>\n`;
} else if(line.startsWith(">")) {
html += `<blockquote>${clean(line.replace(/^> */, ""))}</blockquote>\n`;
} else if(link) {
html += `<p><a href="${link[1]}">${clean(link[2])}</a></p>\n`;
} else if(line) {
html += `<p>${clean(line)}</p>\n`;
}
}
return mustache.render(template, { body: html, title: path.basename(where), time });
}

View file

@ -3,6 +3,8 @@ const path = require("path");
const mustache = require("mustache");
const hljs = require('highlight.js');
const md = require("markdown-it")({
html: true,
xhtmlOut: true,
linkify: true,
typographer: true,
highlight(str, lang) {
@ -15,12 +17,15 @@ const md = require("markdown-it")({
}
});
const template = fs.readFileSync("assets/wrapper.html", "utf8");
const time = Date.now().toString(36);
const template = fs.readFileSync("assets/wrapper.html", "utf8").valueOf();
exports.hash = (where) => fs.statSync(where).mtime;
exports.render = (where) => mustache.render(template, {
title: path.basename(where),
body: md.render(fs.readFileSync(where, "utf8")),
time,
});

View file

@ -1,16 +1,25 @@
const express = require("express");
const fs = require("fs");
const log = require("./log.js");
const app = express();
const dir = __dirname;
app.use(require("compression")())
log.info("starting server");
app.use(require("compression")());
require("express-ws")(app);
app.get("*", (req, res, next) => {
// log.debug(`request from ${req.headers['x-forwarded-for'] || req.connection.remoteAddress} for ${req.url}`);
log.debug(`request for ${req.url}`);
next();
});
for(let file of fs.readdirSync(dir + "/modules")) {
require(dir + "/modules/" + file)(app, dir);
require(dir + "/modules/" + file)(app, dir, log);
log.info(`loded module ${file}`);
}
require("./static.js")(app, dir);
require("./static.js")(app, dir, log);
app.listen(3000);
app.listen(3000, () => log.info("ready!"));

16
log.js Normal file
View file

@ -0,0 +1,16 @@
const fs = require("fs");
const file = fs.createWriteStream("server.log", { flags: "a" });
const label = (color, str) => `\x1b[${color}m[${str}]\x1b[0m`
const log = (color, label) => (text) => {
const time = new Date().toLocaleString();
file.write(`[${time}] [${label}] ${text}\n`);
console.log(`\x1b[90m[${time}] \x1b[${color}m[${label}]\x1b[0m ${text}`);
}
module.exports = {
info: log("34", "I"),
warn: log("33", "W"),
error: log("31", "E"),
debug: log("35", "D"),
};

59
modules/fallocate.js Normal file
View file

@ -0,0 +1,59 @@
const mb = Buffer.alloc(1024 ** 2);
const manpage = `
FALLOCATE(1) User Commands FALLOCATE(1)
NAME
fallocate - allocate space to a file
SYNOPSIS
/fallocate/[size]
DESCRIPTION
fallocate is used to allocate space for a file purely through the
sheer power of cloud-based services. this is a very good idea and
i see absolutely nothing wrong with it.
AUTHORS
me
REPORTING BUGS
you can't
cloud-linux 2.37.2 2022-03-15 FALLOCATE(1)
`.trim();
function parseSize(size) {
const [_, exp, mult] = size.match(/^([0-9]+)(.)?$/);
const num = parseInt(exp, 10);
switch(mult?.toLowerCase()) {
case "k": case "kib": return num * 1024;
case "m": case "mib": return num * 1024 ** 2;
case "g": case "gib": return num * 1024 ** 3;
case "t": case "tib": return num * 1024 ** 4;
case "kb": return num * 1000;
case "mb": return num * 1000 ** 2;
case "gb": return num * 1000 ** 3;
case "tb": return num * 1000 ** 4;
default: return num;
}
}
module.exports = (app, dir, log) => {
app.get("/fallocate", (req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end(manpage);
});
app.get("/fallocate/:size", (req, res) => {
try {
let size = parseSize(req.params.size);
log.warn(`fallocating ${req.params.size}`);
res.writeHead(200, { "Content-Length": size, "Content-Type": "application/octet-stream" });
while(size > 0) {
res.write(size >= 1024 ** 2 ? mb : Buffer.alloc(size));
size -= 1024 ** 2;
}
res.end();
} catch {}
});
}

View file

@ -3,7 +3,7 @@ const path = require("path");
const types = require("../types.js");
const getType = (name) => types.get(path.extname(name).slice(1));
module.exports = (app, dir) => {
module.exports = (app, dir, log) => {
const file = (name) => {
const data = fs.readFileSync(path.join(dir, "overlay", name));
return (req, res) => res.writeHead(200, { "Content-Type": getType(name) }).end(data);
@ -11,5 +11,7 @@ module.exports = (app, dir) => {
app.get("/", file("index.html"));
app.get("/stats", file("stats.html"));
app.get("/unlist", (req, res) => res.redirect("/nope"));
// app.get("/cinny", file("cinny/index.html"));
};

22
modules/secure.js Normal file
View file

@ -0,0 +1,22 @@
// authentication thing, maybe would be useful later?
module.exports = (app, dir, log) => {
const authorize = (req, res, next) => {
const auth = req.headers.authorization;
try {
const [user, pass] = atob(auth.slice(6)).split(":");
if(user !== "username") throw "no";
if(pass !== "password") throw "no";
next();
} catch (err) {
if(err === "no") log.warn("attemped login on /secrets");
res.writeHead(401, {
"WWW-Authenticate": 'Basic realm="secrets..."',
}).end();
}
};
// app.get("/secrets/*", authorize);
// app.get("/secrets/", authorize);
}

View file

@ -1,8 +1,9 @@
const os = require("node-os-utils");
const stats = {};
module.exports = (app, dir) => {
module.exports = (app, dir, log) => {
app.ws("/stats/live", (ws) => {
log.info("stats webhook activated");
const timer = setInterval(update, 1000);
ws.on("message", () => update(true));
ws.on("close", () => clearInterval(timer));
@ -12,25 +13,27 @@ module.exports = (app, dir) => {
cpuusage: stats.cpuusage,
memused: stats.memused,
diskused: stats.diskused,
netin: stats.netin,
netout: stats.netout,
uptime: stats.uptime,
...(full ? {
memtotal: stats.memtotal,
disktotal: stats.disktotal,
} : {}),
}
console.log(statsFiltered);
ws.send(JSON.stringify(statsFiltered));
}
});
setInterval(async () => {
const mem = await os.mem.used();
const disk = await os.drive.used();
const [mem, disk, net] = await Promise.all([os.mem.used(), os.drive.used(), os.netstat.inOut()])
stats.cpuusage = await os.cpu.usage();
stats.memused = mem.usedMemMb * 1e6;
stats.memtotal = mem.totalMemMb * 1e6;
stats.diskused = disk.usedGb * 1e9;
stats.disktotal = disk.totalGb * 1e9;
stats.netin = net.total.inputBytes;
stats.netout = net.total.outputBytes;
stats.uptime = os.os.uptime();
}, 500);
};

View file

@ -10,34 +10,47 @@ const template404 = fs.readFileSync("assets/404.html", "utf8");
const handlers = {
directory: require("./handlers/directory.js"),
markdown: require("./handlers/markdown.js"),
gemini: require("./handlers/gemini.js"),
file: require("./handlers/file.js"),
};
module.exports = (app, dir) => {
const cacheable = [
"/assets/style.css",
"/assets/directory.css",
"/assets/code.css",
];
module.exports = (app, dir, log) => {
app.get("*", (req, res) => {
const where = path.resolve(dir + "/overlay/" + path.normalize(req.path));
const name = path.normalize(req.path);
if(!fs.existsSync(where)) {
res.writeHead(404).end(mustache.render(template404, { file: name }));
return;
}
const stat = fs.statSync(where);
const raw = "raw" in req.query;
if(stat.isDirectory()) {
head().end(run(handlers.directory));
} else if(path.extname(where) === ".md" && !("raw" in req.query)) {
head().end(run(handlers.markdown));
head("text/html").end(run(handlers.directory));
} else if(path.extname(where) === ".md" && !raw) {
head("text/html").end(run(handlers.markdown));
} else if(path.extname(where) === ".gmi" && !raw) {
head("text/html").end(run(handlers.gemini));
} else if(stat.size < 1024 * 1024 * 8) {
head(types.get(path.extname(where).slice(1))).end(run(handlers.file));
} else {
log.info("sending large file");
fs.createReadStream(where).pipe(res);
}
function head(type = "text/html") {
const headers = {};
if(type) headers["Content-Type"] = type;
function head(type = "text/plain") {
const headers = { "Content-Type": type };
if(cacheable.includes(name)) {
headers["Cache-Control"] = "public, max-age=" + 60 * 60 * 24;
}
return res.writeHead(200, headers);
}

View file

@ -4,6 +4,7 @@ types.set("html", "text/html");
types.set("txt", "text/plain");
types.set("js", "text/javascript");
types.set("md", "text/markdown");
types.set("gmi", "text/gemini");
types.set("png", "image/png");
types.set("jpg", "image/jpeg");
types.set("jpeg", "image/jpeg");
@ -12,6 +13,9 @@ types.set("ogg", "audio/ogg");
types.set("mp3", "audio/mp3");
types.set("mkv", "video/mkv");
types.set("mp4", "video/mp4");
types.set("pdf", "application/pdf");
types.set("json", "application/json");
types.set("zip", "application/zip");
module.exports = types;