add everything!
This commit is contained in:
parent
f645e0d9cd
commit
be9452d87f
16 changed files with 221 additions and 32 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
node_modules
|
||||
overlay
|
||||
server.log
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
50
handlers/gemini.js
Normal 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, "&").replace(/>/g, ">").replace(/</g, "<");
|
||||
|
||||
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 });
|
||||
}
|
||||
|
|
@ -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,
|
||||
});
|
||||
|
||||
|
|
17
index.js
17
index.js
|
@ -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
16
log.js
Normal 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
59
modules/fallocate.js
Normal 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 {}
|
||||
});
|
||||
}
|
|
@ -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
22
modules/secure.js
Normal 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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
};
|
||||
|
|
31
static.js
31
static.js
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
4
types.js
4
types.js
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue