How to host a Gemini capsule with Node and Nginx
Project Gemini is a text-based web protocol, like a mash-up of TLS + Gopher. It's hyped as a "Small Internet" with outer space imagery, where instead of web sites, we have Gemini capsules. Some people love it, some people hate it, but it's there and I think it's kind of cool. Anyway, recently I was playing with it and I realized there aren't good docs on how to get it running with nginx. So here's a quick howto:
1. Enable the nginx stream module
Depending on your environment, you may need to install the nginx stream module
(eg. sudo apt install libnginx-mod-stream), or it might just need to be
enabled. Assuming it's installed, simply add this to the very top of your
nginx.conf to enable it (the path may be different in your environment):
load_module /usr/lib/nginx/modules/ngx_stream_module.so;
2. Set up a stream directive in nginx.conf
This should be in your nginx.conf as a sibling to the http directive (i.e. not within the http directive or sites_available). Basically in your actual nginx.conf, put it underneath the http directive, like this:
http {
##
# Basic Settings
##
# ...
# ... skipping ahead ...
# ...
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
stream {
##
# Configure ngx_stream_module for Gemini
##
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn_log_level warn;
limit_conn addr 1;
log_format basic '$remote_addr $upstream_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time';
access_log /var/log/nginx/gemini-access.log;
error_log /var/log/nginx/gemini-error.log;
server {
listen 1965;
proxy_buffer_size 16k;
proxy_pass 'localhost:9003'; # set your actual port here
}
}
3. Have a Gemini server listening on the local port specified in nginx.conf
This is really easy to setup using the gemini-server npm package, which is modeled after Express (but really you can use any Gemini server). Here's a simple Node.js server written in TypeScript:
import { readFileSync } from 'fs';
import gemini, { Request, Response, status } from 'gemini-server';
const PORT = 9003;
const app = gemini({
cert: readFileSync('./cert.pem'),
key: readFileSync('./privkey.pem'),
titanEnabled: false
});
app.on('/', (_req: Request, res: Response) => {
res.file('pages/index.gmi');
});
// Get the facts.
app.on('/facts/:file', (_req: Request, res: Response) => {
try {
res.file('pages/facts/' + _req.params.file);
} catch(error) {
res.error(40 as status, 'File not found.')
}
})
app.listen(PORT, () => console.log('Gemini listening on ' + PORT + '...'));
Note that Gemini requires TLS, so you'll have to use a real cert.pem and privkey.pem, but if you already have these for your HTTPS domain you can reuse them. Otherwise check out this wiki to set up a cert.
That's it. Have fun!