From e66f29ff85392e3c06e9033e37ead06a9d9d5daa Mon Sep 17 00:00:00 2001 From: Tom Smeding Date: Thu, 18 Jun 2020 21:48:33 +0200 Subject: Add blog --- .gitignore | 1 + Dockerfile | 3 +- index.html | 3 ++ install_prepare.sh | 7 +++ modules/blog/.gitignore | 1 + modules/blog/blog.js | 135 +++++++++++++++++++++++++++++++++++++++++++++++ modules/blog/template.js | 56 ++++++++++++++++++++ webserver.js | 12 +++-- 8 files changed, 214 insertions(+), 4 deletions(-) create mode 100644 modules/blog/.gitignore create mode 100644 modules/blog/blog.js create mode 100644 modules/blog/template.js 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 own server.

+

+ I've also written some notes at some point. +

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 = `
${entry}
\n`; + out += elt; + } else { // subdirectory + out += '
\n'; + out += `${entry}/\n`; + out += '
\n'; + out += generateTree(sub, path + "/" + entry); + out += "
\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("", generateTree(tree, pathRoot)); + if (contentPath) { + const content = await fs.readFile(repodir + "/" + contentPath, { encoding: "utf-8" }); + html = html.replace("", 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] != "$"; -- cgit v1.2.3