OpenBSD Server
I run an email server and a webserver on my OpenBSD VM hosted at OpenBSD Amsterdam. This is how I’ve set it up:
Initial Boot
Most of this is actually taken care of by OBSDAms default setup, but here is what I do on new OpenBSD systems:
# syspatch && reboot
# ftp -o - https://meta.sr.ht/~jbauer.keys > .ssh/authorized_keys
# sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
# rcctl reload sshd
# mail
# pkg_add checkrestart ncdu
Daily Jobs
In /etc/daily.local:
next_part "Applying system patches:"
syspatch
next_part "Updating third-party packages:"
pkg_add -u 2>&1
next_part "Do any services need to be restarted?"
/usr/local/sbin/checkrestart
next_part "Disk usage report:"
df -h
In /etc/monthly.local:
Relaying Emails
In /etc/mail/smtpd.conf (with real credentials of course):
table aliases file:/etc/mail/aliases
table credentials { service = service:thisisarandomandsecurepassword }
listen on socket
listen on lo0
action "local_mail" mbox alias <aliases>
action "outbound" relay host smtps://service@mail.example.com tls auth <credentials> mail-from host@example.com
match from local for local action "local_mail"
match from local for any action "outbound"
In /etc/mail/aliases:
root: jbauer@paritybit.ca
manager: root
dumper: root
Finally:
# rcctl restart smtpd
In addition to my normal OpenBSD Server Setup:
TLS Certificates
OpenBSD’s acme-client is used to request certificates. This is the configuration:
authority letsencrypt {
api url "https://acme-v02.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}
domain paritybit.ca {
alternative names { www.paritybit.ca, ftp.paritybit.ca, git.paritybit.ca, jbauer.ca }
domain key "/etc/ssl/private/server.key"
domain full chain certificate "/etc/ssl/server.crt"
sign with letsencrypt
}
My certificate and key are called /etc/ssl/server.crt and
/etc/ssl/private/server.key so I can avoid having to specify their
locations in httpd.conf. If I had more than one domain I was handling
on a server, I’d change this to reflect the explicit domain names.
Renewing the certificates is handled by /etc/monthly.local, which is
run by cron once a month. The output is sent to me in an email.
next_part "Renewing TLS certificate(s):"
acme-client -v -F paritybit.ca
rcctl reload relayd httpd
HTTP Server
The HTTP server uses OpenBSD’s httpd which is very easy to configure and very light on resources. This is the configuration I use:
types {
include "/usr/share/misc/mime.types"
}
# WWW Redirect
server paritybit.ca {
listen on * port 80
listen on * tls port 443
hsts {
max-age 31536000
preload
subdomains
}
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location * {
block return 301 "$REQUEST_SCHEME://www.paritybit.ca$REQUEST_URI"
}
}
server www.paritybit.ca {
listen on * port 80
listen on * tls port 443
hsts {
max-age 31536000
preload
}
default type text/plain
root "/paritybit.ca"
gzip-static
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location "/files/paste/*" {
directory no index
}
location "/files/*" {
directory auto index
}
location match "/([^.]*[^/])$" {
request rewrite "/%1.html"
}
location not found match "/.*[^/]$" {
block return 302 "$REQUEST_SCHEME://www.paritybit.ca$REQUEST_URI/"
}
}
Note that each site is available over HTTP in addition to HTTPS. This is to accommodate older clients that do not have up-to-date browsers, those that are slow enough such that TLS causes significant delay, or those that don’t have a TLS library available. For a site like mine, where user data is not being sent to the server, this is acceptable. Users are free to choose HTTPS.
Additionally, the anything in my paste directory that at least 7 days old
will be automatically cleared with the following job in /etc/daily.local:
find /var/www/paritybit.ca/files/paste/ -type f -ctime 7 -delete
Updating HTTP Server Content
The following script re-compresses and changes ownership of website content whenever new or updated files are pushed to the server:
#!/bin/sh
chown -R www:daemon /var/www/paritybit.ca
gzip -fkrq /var/www/paritybit.ca/ 2>/dev/null
find /var/www/paritybit.ca/files/ -name '*.gz' -exec rm {} \;
Maybe there’s a better way of doing this?
Git Server
This “git server” is really nothing more than a git daemon to handle cloning/fetching/pulling and stagit to generate static pages for each repository so code and changes can be browsed from a web browser. SSH is used to push changes to the server, and the git daemon is invoked using OpenBSD’s inetd.
Static pages generated by stagit are served httpd. Git repositories live in /var/git and updates are pushed there using SSH. The git daemon for cloning using the git protocol is invoked using inetd with the following configuration:
git stream tcp nowait _gitdaemon /usr/local/bin/git git daemon --inetd --verbose --base-path=/var/git --export-all /var/git/
git stream tcp6 nowait _gitdaemon /usr/local/bin/git git daemon --inetd --verbose --base-path=/var/git --export-all /var/git/
The following script is run every 15 minutes to update the static pages and incorporate recently pushed changes. I may switch to using a post-receive hook instead of a cronjob if this doesn’t end up fitting my needs.
#!/bin/sh
# Update all individual repos
for repo in /var/git/*; do
cd /var/www/git.paritybit.ca/"$(basename "$repo" .git)"
/usr/local/bin/stagit "$repo"
done
# Re-generate the index page
cd /var/www/git.paritybit.ca
/usr/local/bin/stagit-index /var/git/* > index.html
The following script is used to make adding a new repository quicker and easier:
#!/bin/sh
printf "Project Name: "
read name
printf "Project Description: "
read desc
#printf "Project URL: "
#read url
url="https://git.merveilles.town/jbauer/$name"
#printf "Project Owner: "
#read owner
owner="Jake Bauer"
cd /var/www/git.paritybit.ca
mkdir "$name" && cd "$name"
ln -s ../favicon.png .
ln -s ../logo.png .
ln -s ../style.css .
cd /var/git
git clone --bare "$url"
echo "$desc" > "$name".git/description
echo "$owner" > "$name".git/owner
echo "$url" > "$name".git/url