summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Smeding <tom.smeding@gmail.com>2020-06-18 21:48:33 +0200
committerTom Smeding <tom.smeding@gmail.com>2020-06-18 22:15:14 +0200
commite66f29ff85392e3c06e9033e37ead06a9d9d5daa (patch)
tree96c2328a9b9df299373290e7bd389e6f7833e9bf
parente92239cec7151cbd3bf1b025375fae70505fe6be (diff)
Add blog
-rw-r--r--.gitignore1
-rw-r--r--Dockerfile3
-rw-r--r--index.html3
-rwxr-xr-xinstall_prepare.sh7
-rw-r--r--modules/blog/.gitignore1
-rw-r--r--modules/blog/blog.js135
-rw-r--r--modules/blog/template.js56
-rwxr-xr-xwebserver.js12
8 files changed, 214 insertions, 4 deletions
diff --git a/.gitignore b/.gitignore
index 768d3c0..5c22b60 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
/globalAccounts.json
/node_modules/
/persist/
+/static/
/web_files
/out.log
/pid.txt
diff --git a/Dockerfile b/Dockerfile
index 15a5afc..3ac1a0c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -17,7 +17,8 @@ RUN npm install --unsafe-perm
FROM alpine:3.11 AS runner
# texlive: for pdfjam for pdfrotate
-RUN apk add --no-cache nodejs texlive
+# git: for blog
+RUN apk add --no-cache nodejs texlive git
WORKDIR /webserver
COPY --from=builder /webserver /webserver
diff --git a/index.html b/index.html
index 6264244..099460d 100644
--- a/index.html
+++ b/index.html
@@ -31,6 +31,9 @@ div.main-content{
among others.
Besides Github, I also have a number of projects on my <a href="https://git.tomsmeding.com">own server</a>.
</p>
+ <p>
+ I've also <a href="/blog">written some notes</a> at some point.
+ </p>
</div>
</body>
</html>
diff --git a/install_prepare.sh b/install_prepare.sh
index 2b39985..ced8f40 100755
--- a/install_prepare.sh
+++ b/install_prepare.sh
@@ -7,3 +7,10 @@ test -f "$abbrgen_execname" || \
test -f modules/unicode/UnicodeData.txt || \
curl -Ls 'https://www.unicode.org/Public/13.0.0/ucd/UnicodeData.txt' >modules/unicode/UnicodeData.txt
+
+mkdir -p static/fonts
+test -f static/fonts/mononoki-Regular.woff || {
+ wget 'https://github.com/madmalik/mononoki/releases/download/1.2/mononoki.zip' -O /tmp/mononoki.zip
+ trap "rm /tmp/mononoki.zip" EXIT
+ unzip /tmp/mononoki.zip -d static/fonts/
+}
diff --git a/modules/blog/.gitignore b/modules/blog/.gitignore
new file mode 100644
index 0000000..ff33cf1
--- /dev/null
+++ b/modules/blog/.gitignore
@@ -0,0 +1 @@
+/repo/
diff --git a/modules/blog/blog.js b/modules/blog/blog.js
new file mode 100644
index 0000000..0b7785b
--- /dev/null
+++ b/modules/blog/blog.js
@@ -0,0 +1,135 @@
+const cmn = require("../$common.js");
+const fs = require("fs");
+const https = require("https");
+const child_process = require("child_process");
+
+const generateTemplate = require("./template.js");
+
+let moddir = null;
+let repodir = null;
+const repoRemote = "https://git.tomsmeding.com/blog";
+
+let templateCache = new Map();
+
+function fetch(url) {
+ return new Promise((resolve, reject) => {
+ https.get(url, res => {
+ if (res.statusCode != 200) {
+ reject(`HTTP status code ${res.statusCode}`);
+ return;
+ }
+
+ let buffers = [];
+ res.on("data", d => buffers.push(d));
+ res.on("end", () => {
+ resolve(Buffer.concat(buffers));
+ });
+ }).on("error", err => {
+ reject(err);
+ });
+ });
+}
+
+function runCommand(cmd, args) {
+ console.log(`blog: ${cmd} ${JSON.stringify(args)}`);
+ child_process.execFileSync(
+ cmd, args,
+ { stdio: "inherit", timeout: 20000 }
+ );
+}
+
+function runCommandOutput(cmd, args) {
+ return child_process.execFileSync(
+ cmd, args,
+ { timeout: 20000 }
+ );
+}
+
+function ensureRepo() {
+ try {
+ const stats = fs.statSync(repodir);
+ if (stats.isDirectory()) return;
+ } catch (e) {}
+
+ runCommand("git", ["clone", "https://git.tomsmeding.com/blog", repodir]);
+}
+
+function currentCommit() {
+ return runCommandOutput("git", ["-C", repodir, "rev-parse", "HEAD"]).toString().trim();
+}
+
+async function upstreamCommit() {
+ const body = await fetch(repoRemote + "/info/refs");
+ return body.toString().split("\t")[0];
+}
+
+function updateRepo() {
+ try {
+ runCommand("git", ["-C", repodir, "fetch", "--all"]);
+ runCommand("git", ["-C", repodir, "reset", "origin/master", "--hard"]);
+ // Reset cache _after_ the commands succeeded; if anything failed, we
+ // might at least still have stale cache data
+ templateCache = new Map();
+ } catch (e) {
+ console.error("Cannot update blog git repo!");
+ console.error(e);
+ }
+}
+
+async function periodicUpdateCheck() {
+ let shouldUpdate = false;
+ try {
+ const local = currentCommit();
+ const remote = await upstreamCommit();
+ if (local != remote) shouldUpdate = true;
+ } catch (e) {
+ // Also update if our pre-check fails for some reason
+ shouldUpdate = true;
+ }
+ if (shouldUpdate) updateRepo();
+}
+
+setInterval(function () {
+ periodicUpdateCheck().catch(err => console.error(err));
+}, 3600 * 1000);
+
+module.exports = (app, io, _moddir) => {
+ moddir = _moddir;
+ repodir = moddir + "/repo";
+
+ ensureRepo();
+ updateRepo();
+
+ app.get("/blog", (req, res, next) => {
+ req.url = "/blog/";
+ next();
+ });
+
+ app.get("/blog/*", (req, res) => {
+ if (req.path.indexOf("/.") != -1) {
+ res.sendStatus(404);
+ return;
+ }
+
+ const path = req.path.slice(6).replace(/\.html$/, "");
+
+ if (templateCache.has(path)) {
+ res.send(templateCache.get(path));
+ } else {
+ generateTemplate(repodir, path ? path + ".html" : undefined)
+ .then(rendered => {
+ // TODO: fix rendering race condition
+ templateCache.set(path, rendered);
+ res.send(rendered);
+ })
+ .catch(err => {
+ if (err.code && err.code == "ENOENT") {
+ res.sendStatus(404);
+ } else {
+ console.error(err);
+ res.sendStatus(500);
+ }
+ });
+ }
+ });
+};
diff --git a/modules/blog/template.js b/modules/blog/template.js
new file mode 100644
index 0000000..1f26e82
--- /dev/null
+++ b/modules/blog/template.js
@@ -0,0 +1,56 @@
+const fs = require("fs").promises;
+
+const pathRoot = "blog";
+
+async function recursiveTree(dir) {
+ const res = new Map();
+
+ const entries = await fs.readdir(dir, { withFileTypes: true });
+ for (const entry of entries) {
+ if (entry.name[0] == ".") continue;
+
+ if (entry.isDirectory()) {
+ res.set(entry.name, await recursiveTree(dir + "/" + entry.name));
+ } else if (entry.isFile() && entry.name.endsWith(".html")) {
+ res.set(entry.name.slice(0, entry.name.length - 5), true);
+ }
+ }
+
+ return res;
+}
+
+function generateTree(tree, path) {
+ let out = "";
+
+ for (const [entry, sub] of tree) {
+ if (sub === true) { // file
+ const elt = `<div class="tree-node tree-file"><a href="/${path}/${entry}">${entry}</a></div>\n`;
+ out += elt;
+ } else { // subdirectory
+ out += '<div class="tree-node tree-dir">\n';
+ out += `<span class="tree-dir-name">${entry}/</span>\n`;
+ out += '<div class="tree-sub">\n';
+ out += generateTree(sub, path + "/" + entry);
+ out += "</div></div>\n";
+ }
+ }
+
+ return out;
+}
+
+async function template(repodir, contentPath) {
+ const tree = await recursiveTree(repodir);
+ tree.delete("index");
+
+ let html = await fs.readFile(repodir + "/index.html", { encoding: "utf-8" });
+
+ html = html.replace("<!-- REPLACE TREE -->", generateTree(tree, pathRoot));
+ if (contentPath) {
+ const content = await fs.readFile(repodir + "/" + contentPath, { encoding: "utf-8" });
+ html = html.replace("<!-- REPLACE CONTENT -->", content);
+ }
+
+ return html;
+}
+
+module.exports = template;
diff --git a/webserver.js b/webserver.js
index 5ebdc41..b53d9ab 100755
--- a/webserver.js
+++ b/webserver.js
@@ -1,9 +1,8 @@
#!/usr/bin/env node
const cmn = require("./modules/$common.js");
-const app = require("express")();
+const express = require("express");
const http = require("http");
-const httpServer = http.Server(app);
-const io = require("socket.io")(httpServer);
+const socketio = require("socket.io");
const url = require("url");
const fs = require("fs");
const util = require("util");
@@ -20,6 +19,11 @@ if (process.argv.length > 3) {
const PORT = process.argv.length == 3 ? +process.argv[2] : 80;
+const app = express();
+const httpServer = http.Server(app);
+const io = socketio(httpServer);
+
+
// const reqlogger = new Logger("request_log.txt");
@@ -67,6 +71,8 @@ refreshWhatpulse();
app.use(bodyParser.text());
+app.use(express.static("static"));
+
const module_list = fs.readdirSync("modules").filter(function (f) {
return ["$common.js", ".DS_Store"].indexOf(f) == -1 && f[0] != "$";