Note
|
I want to clearly state this is 0 dependencies on the server. The server does not need AsciiDoc installed to operate. AsciiDoc is for the client to use. The server operates with 0 `pkg_add`s |
I have fallen in love with a recent combination of software to make good looking websites, and having an easy to manage web server. I’m a minimalist in many ways. Really, I find that it makes my life easier. I like to keep my blog up here and don’t want to deal with database updates, language exploits, weird migrations, and so on. I also manage my church’s website. I can’t be having complex solutions that require large amounts of maintenance at multiple times a month because of a dozen pieces of software requiring version upgrades or what have you. Sure, some people have needs for elaborate content management systems, but sometimes you only need a simple site too, and why overcomplicate it?
This is why I like my current combo:
-
OpenBSD
-
AsciiDoc
OpenBSD is a simple operating system with simple management needs. I also like AsciiDoc because it is very easy to write content with (like right now for this blog), the output looks nice and clean, and it’s basic script away from being simple HTML/CSS.
OpenBSD
Why do I like OpenBSD for this job? I like OpenBSD because it comes with batteries included
-
webserver
-
firewall
-
acme client for SSL certs
-
openrsync for backups
I also like its strong focus on security, as well as how easy it is to maintain the server with the way I am demonstrating here. Really, the only commands someone has to know to do upkeep is:
-
syspatch
-
reboot
-
sysupgrade
Next, I will talk about how I configured my server to run a simple, static site.
Firewall Configuration
Firewall configuration only requires you edit the /etc/pf.conf
file. This is what I needed to make my webserver work.
table <bruteforce> persist
set skip on lo0
block in log all
pass out on egress
pass in on egress inet proto tcp from any to (egress) port { http, https } keep state
pass in on egress inet6 proto tcp from any to (egress) port { http, https } keep state
pass proto tcp from any to (egress) port ssh \
keep state (max-src-conn 15, max-src-conn-rate 5/3, \
overload <bruteforce> flush global)
pass in on egress inet6 proto icmp6 all icmp6-type { echoreq routeradv neighborsol neighboradv }
pass in on egress inet proto icmp icmp-type { echoreq unreach }
block return in on ! lo0 proto tcp to port 6000:6010
block return out log proto {tcp udp} user _pbuild
Enable these rules with pfctl -f /etc/pf.conf
acme-client configuration
OpenBSD comes with a built-in acme client that you can use to generate new SSL certificates. I get mine from Let’s Encrypt. I then create a cron job under the root user to run every so often to see if there is a renewal that can happen, and then reload.
Here is my configuration for the /etc/acme-client.conf
file
domain example.com {
domain key "etc/ssl/private/example.com.key"
domain certificate "/etc/ssl/example.com.pem"
domain full chian certificate "/etc/ssl/example.com.fullchain.pem"
sign with letsencrypt
}
Then I add the cron job as the root user with crontab -e
30 0 * * 1 acme-client example.com && rcctl reload httpd
Webserver Configuration
I use OpenBSD’s built in HTTP server, which does its job well. They have a file at /etc/examples/httpd.conf
that you can use and mock up. Here is mine
server "example.com" {
listen on * tls port 443
hsts preload
tls {
certificate "/etc/ssl/example.com.fullchain.pem"
key "/etc/ssl/private/example.com.key"
ciphers "HIGH:!aNULL"
}
location "/*" {
root "/htdocs/example.com"
}
}
With the SSL cert and the web server configuration set, you can start and enable the server:
rcctl enable httpd; rcctl start httpd
doas.conf
You can use doas
to make superuser configurations without always being root. Edit the
doas.conf file and add
permit persist <username> as root
AsciiDoc
So, you might be asking why I chose AsciiDoc. I like AsciiDoc because it is a simple markup language that lets me focus on my writing and layout. Markdown can do this too, and is more common than AsciiDoc. However, the fact that there isn’t a standard Markdown and I don’t like any of the rendered output. With AsciiDoc, they give a simple and clean default result in HTML with CSS. I also like that there are other themes I can use readily, or I can make my own. But I end up using the default theme and the dark theme that is available.
With this in mind, it makes it easy for anyone to see what is written and figure out what does what since the syntax is simple. And, if you use editors like Visual Studio Code or AsciiDocFX, you get a nice preview. For me, I use vim. Here is an example of what the Webserver Configuration section looks like:
[#webserver-config]
=== Webserver Configuration
I use OpenBSD's built in HTTP server, which does its job well. They have a file at `/etc/examples/httpd.conf`
that you can use and mock up. Here is mine
[source]
\----
server "example.com" {
listen on * tls port 443
hsts preload
tls {
certificate "/etc/ssl/example.com.fullchain.pem"
key "/etc/ssl/private/example.com.key"
ciphers "HIGH:!aNULL"
}
location "/*" {
root "/htdocs/example.com"
}
}
\----
With the SSL cert and the web server configuration set, you can start and enable the server:
`rcctl enable httpd; rcctl start httpd`
Note
|
The source section starts with a \ so as to not interpret it literally here :)
|
CSS Styles
To get the best out of CSS styles if I am not using the builtin, I like to do what I am doing here for my site:
:stylesheet: https://blog.passwordclass.xyz/css/dark.css
:linkcss:
How I Generate My AsciiDoc
I use a simple script to pull together my content, as well as generate my AsciiDoc into HTML format. I also want to note that I did create some extra custom HTML for my page to be inserted so I could offer a navbar, since in some cases I have come by the Table of Contents feature of AsciiDoc is good, but other times people wanted a standard navbar.
#!/usr/bin/env sh
CWD=$(pwd)
DEST="$CWD/example.com"
rm -rf "$DEST"
mkdir "$DEST"
# For any images you may have
cp -r "$CWD/images" "$DEST/"
# For the CSS to use with adoc
cp -r "$CWD/css" "$DEST/"
# In case you add any extra JS to run
cp -r "$CWD/js" "$DEST/"
# Any custom HTML you may insert into your adoc files
cp -r "$CWD/asciidoc/html" "$DEST/"
# I had a zipfile of favicons, this could just be changed do copying a favicon.ico
unzip "$CWD/favicon.zip" -d "$DEST/"
# Generate index file
asciidoctor -D "$DEST" "$CWD/asciidoc/index.adoc"
adocfiles=$(find asciidoc -type f -name "*.adoc" | xargs)
for f in $adocfiles; do
relative_path=$(echo "${f%/*}/")
mkdir -p "$CWD/$relative_path"
asciidoctor -D "$DEST/$relative_path" $f
done
cd "$DEST"
mv asciidoc/* ./
rmdir asciidoc
cd -
So, I will be in my terminal at this point and be in the root of my working path, where
I have my asciidoc folder and my compile.sh script. Then, when I want to, I run
sh ./compile.sh
to generate the site. At that point, I have a newly made example.com dir.
Uploading Site Content
With a created site, it is time to upload your work. I tend to use sftp or scp to do this. You could even do rsync if you wanted.
Swapping in Your New Site
Now with your content uploaded, presumably to your home directory, you can either run a few commands, or use a script like this to
-
Change ownership of the new website content
-
Change permissions
-
Move the old site
-
Move the new one
Something like this, assuming you have your web content at $HOME
#!/usr/bin/env sh
chown -R root:wheel $HOME/example.com
find $HOME/example.com -type f -exec chmod 644 {} \;
find $HOME/example.com -type d -exec chmod 755 {} \;
mv /var/www/htdocs/example.com /var/www/htdocs/example.com-$(date "+%Y-%m-%d-%H%M%S")
mv $HOME/example.com /var/www/htdocs
This will move the old site away as a backup, then put the new one in place.
Wrap Up
With all of this, if you can install OpenBSD, you can make a simple solution for basic static websites that are also easy to create since you don’t have to learn any (or very little) HTML, no CSS, no heavy site creator to get in the way, and little command line knowledge to configure and maintain.
Bonus
I had previously mentioned backups, so I won’t forget this one. You may find backups "unnecessary" since you should theoretically always have your site content on a local computer. However, if you want to backup your configurations and such, you can accomplish this with scp or openrsync.
Even though I don’t have any secret content, I do like to encrypt my backups. So this is something you can do when creating yours.
This is more or less what my backup script looks like:
#!/usr/bin/env sh
keyfile="/root/.enckey"
key="$(cat $keyfile)"
backupdir="/root/backups"
name="$backupdir/$(date +'%F_%T')_<optional backup name here>"
mkdir -p $name
mkdir -p $name/etc
# copy files to backup directory
etc_files="hostname.if,httpd.conf,pf.conf,rc.conf.local,sysctl.conf,acme-client.conf,doas.conf"
cp /etc/{$etc_files} $name/etc/
other_files="/etc/ssh/sshd_config"
for file in $other_files; do
dir=$(echo "${file%/*}")
bkp_dir="$name/${dir#/}"
mkdir -p $bkp_dir
cp $file $bkp_dir/
done
cp -r /var/www/htdocs $name/
# Compress and encrypt
tar -czf $name.tar.gz $name
openssl enc -aes-256-cbc -md sha512 -pbkdf2 -iter 100000 -salt -in $name.tar.gz -out $name.tar.gz.enc -pass file:$keyfile
# Clean up
rm $name.tar.gz
rm -rf $name
result=$(openrsync -a --delete $backupdir remoteuser@example.com:/path/to/backup/dest 2>&1 > /dev/null)
# Delete files older than 30 days
find $backupdir -mtime +30 -delete
Bonus Bonus
I like to know when my backups are working. So, I want to get notified when a backup is failing. In the previous backup script, you can insert a conditional to say "if the backup happened, submit a pass. If it didn’t for some reason, send a fail". I do this to NodePing with their PUSH checks. Which, if you can make a POST request and format some JSON, you can submit any data.
My method has been to just submit an empty JSON object {"data":{}}
to accomplish my
needs. One issue though, doing a POST request with OpenBSD from just base isn’t quite
as cut and dry. I reached out, and the 2 most common answers were to write some Perl,
which, that’s too much for me, or use nc(1). I opted to use nc.
I created a PUSH check on NodePing and got the checktoken and checkid for the POST request. The nc command looks like:
head='POST /v1?id=<checkid>&checktoken=<checktoken> HTTP/1.1\r\nHost: push.nodeping.com\r\nContent-Type: application/json\r\nContent-Length: 11\r\n\r\n{"data":{}}'
echo -ne $head | nc -c push.nodeping.com 443
Bonus X3
I appreciate knowing when a syspatch is available. OpenBSD provides a handy syspatch -c
flag that will output any patches available. I submit this info to my Matrix server,
but of course you could create another NodePing check or anywhere else you want to
do a POST request with any sort of data to a service that will notify you.
My script basically looks like
#!/bin/sh
if [ ! -z "$(syspatch -c)" ]; then
body="{\"body\":\"$(hostname) syspatch available\"}"
contentlength=$(expr $(echo "$body" | wc -m) - 1)
head="POST /web/path/post/somewhere HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/json\r\nContent-Length: $contentlength\r\n\r\n$body"
echo -ne "$head" | nc -c example.com 443
fi
Next create a daily cron job. We can call this /root/checksyspatch.sh
0 2 * * * /bin/sh /root/checksyspatch.sh
Now you should be notified if there are any updates available for your OpenBSD release.
Conclusion
Now, you have:
-
A running OS with high security
-
Easy OS management, really only needing to run syspatch, reboot, and sysupgrade
-
A nice, clean static website that really only requires simple syntax to know to write
-
Backups with openrsync
-
Backup notifications with NodePing and nc (definitely optional)
-
Update notices
Since it’s not often that a website update happens on some of my sites, I really only need to keep track of running syspatch and upgrading if a new release comes out. Then, any updates that DO need to happen are a basic edit, compile, upload, and put the new site content in place.
EDIT 2022-06-29
I didn’t state clearly enough that my 0-dependency claim is you don’t need to install anything on the server. The server does not need AsciiDoc. The client will, but my point is that the server doesn’t need any pkg_add
and thus, only requires syspatch
, reboot
, and sysupgrade
to maintain the host, and no needing to worry about maintaining other software on top of the OpenBSD base install. :)