MANDOC(7) Miscellaneous Information Manual MANDOC(7)

Building A Website With groff (mandoc)because who needs modern web frameworks when you can format HTML like its 1972.

I want to showcase how I built this website using mandoc(1), a dialect of roff(7). I have a soft spot for roff, which is a typesetting system that processes plain text using formatting commands to produce beautifully formatted output for documents like manuals, books, and technical papers.

The history of roff is complicated (see here and here). But for our purposes, its enough to know that mandoc is a specialized dialect of it for BSD-style manuals. Furthermore, it was developed in part to produce CSS-enabled HTML to display man pages conveniently online. That's why this website looks like a man page itself. Paired with a bit of custom CSS for styling, though, one can use mandoc to create a no-frills website with surprising precision.

Building a website using mandoc is unconventional, but I find it enjoyable and rewarding. Relative to the more popular ways to create websites today — like Wordpress, etc. — mandoc is spartan. But I do it anways, for a few reasons:

  1. It pushes the boundaries of what ought to do with traditional tools. This is also a great way to find and fix bugs. In general, I'm a big fan of finding new uses for old instruments.
  2. It generates synergies. In particular, using mandoc to build this site keeps me familiar with it and the broader roff ecosystem, which I can use later when creating and editing man pages.
  3. I wanted a clean no-nonsense website that loaded quickly.

Each page of this website has its own mandoc file, including the one you're reading right now. Staying true to how man page files on OpenBSD are named, each filename has a file extention of .7, signifying the section of the manual it belongs to. That's why you see the (7) in the header. The .7 file extension is used for documents related to miscellaneous topics. :)

To use mandoc, one uses a series of macros and "formatting directives" to structure content, like LaTeX. The syntax looks cryptic at first, but is straightforward once you get the hang of it.

The man page for mandoc explains each of the macros in detail. For example, the .Dt macro creates the title, while .Sh defines new sections.

Below is the code for the front page as of October 11, 2024:

.Dd October 05, 2024
.Dt "DFDX" 7
.Os OpenBSD 7.5
.
.Sh NAME
.Nm dfdx
.Nd oldschool technology enthusiast
.
.Sh DESCRIPTION
One doesn't need to chase new technology trends. Older technology can still address today's problems -- often
more elegantly.
Therefore, I mainly use this blog to explore how to solve certain modern
problems with tools that have been around for decades.
Instead of relying on the latest software, I focus on alternatives from the golden age of computing that have
stood the test of time.
.
.Sh BLOG POSTS
.TS
center tab(%);
le re reb.
You can get far with httpd in OpenBSD%Getting the most out of OpenBSD's httpd%(TBD)
Creating daemons in OpenBSD%It's not as complicated as you think%(TBD)
Gitea on OpenBSD%A guide to configure gitea on OpenBSD%(TBD)
Pixelfed on OpenBSD%A guide to configure pixelfed on OpenBSD%(TBD)
Snac on OpenBSD%A guide to configure snac on OpenBSD%(TBD)
Calibre-server on OpenBSD%A guide to configure calibre-server on OpenBSD%(TBD)
Nextcloud on OpenBSD%A guide to configure Nextcloud on OpenBSD%(12/28/21)
Encrypted Harddrive on OpenBSD%A guide to install OpenBSD with encrypted hard drives%(12/28/21)
.TE
.
.Sh SEE ALSO
openbsd,
tildeverse,
causal.agency,
gentoo,
suckless,
cosmic.voyage
.
.Sh AUTHOR
.Bd -literal -offset indent
.Cm mail No 	 Lk mailto:intdfdx@gmail.com intdfdx@gmail.com
.Cm irc No 	 #openbsd and #gentoo on Lk https://libera.chat/ libera.chat
.Ed

Once I finish writing a new post or edit the front page, I generate a new HTML file like so:

$ mandoc -T html -O man="https://man.openbsd.org/%N.%S",style=style.css,toc filename.7 > filename.html

This command (1) convets the content of into a static HTML file, (2) links certain references to a specific website (in this case https://man.openbsd.org), (3) links the path of a CSS stylesheet (which I discuss below), and (4) automatically generates a table of contents. mandoc handles the formatting of headers, paragraphs, and tables quite well. Here's an excerpt of what the resulting HTML looks like:

<pre><!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <link rel="stylesheet" href="style.css" type="text/css" media="all"/> <title>DFDX(7)</title> </head> <body> <div class="head" role="doc-pageheader" aria-label="Manual header line"><span class="head-ltitle">DFDX(7)</span> <span class="head-vol">Miscellaneous Information Manual</span> <span class="head-rtitle">DFDX(7)</span></div> <main class="manual-text"> <section class="Sh"> <h2 class="Sh" id="NAME"><a class="permalink" href="#NAME">NAME</a></h2> <p class="Pp"><code class="Nm">dfdx</code> &amp;#x2014; <span class="Nd" role="doc-subtitle">oldschool technology enthusiast</span></p> </section> <section class="Sh"> <h2 class="Sh" id="DESCRIPTION"><a class="permalink" href="#DESCRIPTION">DESCRIPTION</a></h2> <p class="Pp">One doesn't need to chase new technology trends. Older technology can still address today's problems -- often more elegantly. Therefore, I mainly use this blog to explore how to solve certain modern problems with tools that have been around for decades. Instead of relying on the latest software, I focus on alternatives from the golden age of computing that have stood the test of time.</p> </section> <section class="Sh"> <h2 class="Sh" id="BLOG_POSTS"><a class="permalink" href="#BLOG_POSTS">BLOG POSTS</a></h2> <table class="tbl"> <tr> <td><a href="https://dfdx.io/_posts/2024-10-05-httpd.html">You can get far with httpd in OpenBSD</a></td> <td style="text-align: right;">Getting the most out of OpenBSD's httpd</td> <td style="text-align: right;"><b>(TBD)</b></td> </tr> <tr> </tr> <tr> <td><a href="https://dfdx.io/_posts/2024-10-05-openbsd-daemons.html">Creating daemons in OpenBSD</a></td> <td style="text-align: right;">It's not as complicated as you think</td> <td style="text-align: right;"><b>(TBD)</b></td> </tr> <tr> </tr> <tr> <td><a href="https://dfdx.io/_posts/2024-10-05-gitea.html">Gitea on OpenBSD</a></td> <td style="text-align: right;">A guide to configure gitea on OpenBSD</td> <td style="text-align: right;"><b>(TBD)</b></td> </tr> <tr> </tr> <tr> <td><a href="https://dfdx.io/_posts/2024-10-05-pixelfed.html">Pixelfed on OpenBSD</a></td> <td style="text-align: right;">A guide to configure pixelfed on OpenBSD</td> <td style="text-align: right;"><b>(TBD)</b></td> </tr> <tr> </tr> <tr> <td><a href="https://dfdx.io/_posts/2024-10-05-snac.html">Snac on OpenBSD</a></td> <td style="text-align: right;">A guide to configure snac on OpenBSD</td> <td style="text-align: right;"><b>(TBD)</b></td> </tr> <tr> </tr> <tr> <td><a href="https://dfdx.io/_posts/2024-10-05-calibre.html">Calibre-server on OpenBSD</a></td> <td style="text-align: right;">A guide to configure calibre-server on OpenBSD</td> <td style="text-align: right;"><b>(TBD)</b></td> </tr> <tr> </tr> <tr> <td><a href="https://dfdx.io/_posts/2021-12-28-nextcloud.html">Nextcloud on OpenBSD</a></td> <td style="text-align: right;">A guide to configure Nextcloud on OpenBSD</td> <td style="text-align: right;"><b>(12/28/21)</b></td> </tr> <tr> </tr> <tr> <td><a href="https://dfdx.io/_posts/2021-12-28-diskencryption.html">Encrypted Harddrive on OpenBSD</a></td> <td style="text-align: right;">A guide to install OpenBSD with encrypted hard drives</td> <td style="text-align: right;"><b>(12/28/21)</b></td> </tr> </table> </section> <section class="Sh"> <h2 class="Sh" id="SEE_ALSO"><a class="permalink" href="#SEE_ALSO">SEE ALSO</a></h2> <p class="Pp"><a href="https://openbsd.org">openbsd</a>, <a href="https://tildeverse.org">tildeverse</a>, <a href="https://causal.agency">causal.agency</a>, <a href="https://gentoo.org">gentoo</a>, <a href="https://suckless.org">suckless</a>, <a href="https://cosmic.voyage">cosmic.voyage</a></p> </section> <section class="Sh"> <h2 class="Sh" id="AUTHOR"><a class="permalink" href="#AUTHOR">AUTHOR</a></h2> <div class="Bd Li"> <pre><code class="Cm">mail</code> <span class="No"> </span> <a class="Lk" href="mailto:intdfdx@gmail.com">intdfdx@gmail.com</a> <code class="Cm">irc</code> <span class="No"> #openbsd and #gentoo on</span> <a class="Lk" href="https://libera.chat/">libera.chat</a></pre> </div> <p class="Pp"></p> </section> </main> <div class="foot" role="doc-pagefooter" aria-label="Manual footer line"><span class="foot-left"></span><span class="foot-date">October 5, 2024</span> <span class="foot-os">OpenBSD 7.5</span></div> </body> </html></pre>

mandoc's generated HTML provides the core structure of the website, but I wanted some visual enhancement or else it literally would look like a BSD-style man page. To do that, I created a CSS stylesheet to modify the look and feel of the HTML. With CSS, I am able to adjust the font, colors, and spacing, giving the website — which, again, is essentially a glorified man page — a more modern and personalized look. It may not be for everyone, but to me, the combination of mandoc for structure and CSS for styling works nicely.

As of October 11, 2024, here is the stylesheet that I use:

html { background-color: #161616; color: #f1f1f1; }
html { font-family: monospace; font-size: 16px; line-height: 1.25em; }
body { max-width: 180ch; margin: 1em auto; padding: 0 1ch; }
/* header */
div[role=doc-pageheader] {
		display: flex;
		margin-bottom: 1em;
		}
.head-ltitle {	flex: 1; }
.head-vol {	flex: 0 1 auto;
		text-align: center; }
.head-rtitle {	flex: 1;
		text-align: right; }
div[role=doc-pagefooter] {
		display: flex;
		justify-content: space-between;
		margin-top: 1em;
		}
/* footer */
.foot {
    display: flex; /* Use flexbox for alignment */
    justify-content: space-between; /* Space between children */
    width: 100%; /* Make footer as wide as the webpage */
}
.foot-date {	flex: 0 1 auto;
		text-align: left; }
.foot-os {	flex: 1;
		text-align: right; }
table { border-collapse: collapse; }
table.head, table.foot { width: 100%; }
table.foot { margin-top: 1em; }
table.Nm code.Nm { padding-right: 1ch; }
/* table.tbl { margin-left: auto; margin-right: auto; } */ /* centers table */
table.tbl { width: 100% }	/* table width */
table.tbl td { padding: 0.15em 0; } /* vertical padding between rows */
td.head-rtitle, td.foot-os { text-align: right; }
td.head-vol { text-align: center; }
table.tbl tr:hover { background-color: #ff7f50; } /* #ddd */
table.tbl tr:nth-child(even) { background-color: #4caf50; }
div.Pp { margin: 1ex 0ex; }
div.Nd, div.Bf, div.Op { display: inline; }
div.Bd-indent { margin-left: 4ch; }
div.Bd-indent-two { margin-left: 8ch; }
span.Pa, span.Ad { font-style: italic; }
span.Ms { font-weight: bold; }
dl.Bl-diag > dt { font-weight: bold; }
code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn,
code.Cd { font-weight: bold; font-family: inherit; }
section { margin-bottom: 8ex; }
h2.Sh#NAME { color: #000; margin-bottom: -0.75ex; }
h2.Sh#DESCRIPTION { margin-bottom: -0.75ex; }
h2.Sh {
	color: #ff7f50;
	margin-bottom: 0.5ex;
}
h2.Sh#SEE_ALSO { margin-bottom: -0.75ex; }
h2.Sh#AUTHOR { margin-bottom: -0.75ex; }
p {
	padding-left: 2em;
	padding-right: 2em;
	text-align: justify;
}
a { color: #f1f1f1; }
a:visited { color: #7A4955 }
a.permalink { color: #ff7f50; text-decoration: none; }

Of course, mandoc is not perfect. It's an old tool that doesn't receive updates very often. For example, one thing I've noticed is that it generates malformed HTML if there are URLs in tables. I fix this issue with a small shell script:

#!/bin/sh

sed -i "s/&lt;/</g" $1
sed -i "s/&gt;/>/g" $1
sed -i "s/&quot;/"/g" $1

One day, when I'm feeling particularly adventurous, I'll seee if I can create dynamic webpages using CGI with man.cgi(8).

October 11, 2024 dfdx