Note: not sure where to put this, feel free to move this to a more appropriate place if I missed something.
Just wanted to share a quick hacky way I’ve devised in the past week to put up a quick page when doing a Discourse upgrade. I did read all the existing guides out there, but I know nothing about nginx and a dual-container setup looked very intimidating.
tl;dr; I made something, the code is below.
Node.js to the rescue
So I thought: what is the quickest way to just spin up a super tiny web server with a single page? Then I remembered node.js was installed anyway, could I maybe get away with just using that?
A quick search and copy/paste from Stackoverflow later I had a tiny thing listening to port 80!
The process is super manual, but I think it’s okay, I need to monitor the logging when the upgrade is running anyway, so I wait to see the signal:
Stopping old container
+ /usr/bin/docker stop -t 600 app
At that point, I’d have another SSH session open where I quickly run:
touch index.html
node server.js 80
I touch
the index.html
file so I can read the update date from it, and replace a placeholder. In my index.html
I have this line:
<p>This page was last updated at: LASTUPDATE</p>
In the server.js file I replace that LASTUPDATE
with the date/time when the index.html
file was last updated. This just gives people an idea of when the upgrade started and I do note in the page it will take 10-15 minutes to complete.
This had me easily running something on my dev server as a test and I felt ready to do this on live.
Oops, we need https 
Time to upgrade the live server and show people my new quick holding page!
That didn’t go as expected. The live site is behind Cloudflare and requires full end-to-end certificates to be available so I took down the forum for 15 minutes and only a Cloudflare error page to show for it
Luckily, some more copy/pasting from Stack Overflow helped. I switched the server from being http to https and got a cert.key
and cert.pem
files to make sure the certificate chain was intact.
The next upgrade was a bigger success and the holding pages showed up as designed:
Just show me the code!
Here’s the two files I’m using for this holding page:
index.html
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8">
<title>We'll be right back</title>
</head>
<body>
<h1>We'll be right back</h1>
<p>Sorry for the temporary outage, we're currently applying an update, this usually take 10-15 minutes.</p>
<p>This page was last updated at: LASTUPDATE</p>
</body>
</html>
server.js
var http = require("http"),
https = require("https"),
url = require("url"),
path = require("path"),
fs = require("fs"),
port = process.argv[2] || 81;
const options = {
key: fs.readFileSync("cert.key").toString(),
cert: fs.readFileSync("cert.pem").toString(),
};
https
.createServer(options, function (request, response) {
var uri = url.parse(request.url).pathname,
filename = path.join(process.cwd(), uri);
fs.exists(filename, function (exists) {
if (!exists) {
response.writeHead(404, { "Content-Type": "text/plain" });
response.write("404 Not Found\n");
response.end();
return;
}
let indexFile = (filename += "index.html");
if (fs.statSync(filename).isDirectory()) indexFile;
fs.readFile(filename, "utf8", function (err, file) {
if (err) {
response.writeHead(500, { "Content-Type": "text/plain" });
response.write(err + "\n");
response.end();
return;
}
var stats = fs.statSync(indexFile);
var mtime = stats.mtime;
response.writeHead(200);
file = file.replace(/LASTUPDATE/g, mtime);
response.write(file, "utf8");
response.end();
});
});
})
.listen(parseInt(port, 10));
console.log(
"Static file server running at\n => https://localhost:" +
port +
"/\nCTRL + C to shutdown"
);
As noted above, you can run it as follows:
touch index.html
node server.js 443
The 443
argument is the port you want to run it on, by default it runs on port 81 so that I can do a curl
to test what the output will be, before I actually do this during the upgrade:
curl -k https://localhost:81
At some point during the Discourse upgrade, when it completes, you will see:
Error starting userland proxy: listen tcp4 0.0.0.0:443: bind: address already in use.
That’s when I terminate the Node server and start Discourse again:
./launcher restart app
And that’s it. Hope this helps someone!