For my very first blog post here, I’m going to cover how to install and set up a blog on a VPS (or to be more precise; a system with a static IP address!).

The server we’ll be using for this is OpenBSD because quite frankly; it’s extremely simple to set up servers on and has the added benefit of actually being secure by default. Don’t get me wrong… I’m also a sysadmin for production Linux-based web servers, but OpenBSD takes the stress out of life by keeping it simple.

Please note: Any AI assistance in my blog post was kept to the absolute minimal, with its use pretty much limited to finding the regular expressions for grep/sed commands (which I suck at) and learning how to visually organise markdown. AI isn’t quite up to the standard of writing a blog post as fantastical as mines, yet! ;)

For my next blog post, I’ll show you how to host from home on your own hardware and get around the whole dynamic IP address thing.

Enjoy the post!

Prerequisites

  • A cheap virtual private server (VPS) such as one from Linode or Vultr
  • Brand new, unconfigured OpenBSD installation
  • Your domain’s A records pointing to the IP of your VPS server (@ and www)

The Plan

The following steps will take you all the way to having your very own public blog and with https.

I have written this blog for brand new OpenBSD users. As long as you know how to open a terminal on your Windows, Linux or Mac system and you know how to type: ssh root@your-ip-or-hostname.com then you should be able to get an OpenBSD blog up and running in a few minutes if you are in a rush (eg if you were to skip my written text and head to the bottom for the script!) or 20-30 mins if you are wanting to take your time and go through it all.

Along the way, I am going to show you some nifty little tricks for debugging things which I had to use myself when building this server, such as checking for listening ports, seeing which processes were running on which ports, running the httpd (the web server) under daemon mode and other randomness which was crucial for me to write a blog post for you guys which just worked.

Some of you may wonder why I used Relayd as a reverse proxy for the HTTPD server when I could have just skipped that and had HTTPD server listen on port 443 and port 80. You’re right - I didn’t need to. But the truth is, I just wanted to because I really like the way Relayd handles https certs and I wanted to show others how to use the reverse proxy features.

Here are the steps we’ll be following:

  1. Initial system maintenance

    1. Use syspatch to update the server
    2. Setting your hostname
    3. Setting the server variables
    4. Configure the correct time for your region
    5. Change the SSHD port to reduce password attacks
    6. Create a new non-root user, add them to the ‘wheel’ group and configure doas
  2. Create a Barebones HTTPD Web Server

    1. Create directory layout with correct permissions
    2. Generate the httpd config
    3. Testing the httpd web server
    4. Diagnosing httpd issues
  3. Build a TLS Reverse Proxy with Relayd

    1. Generate the relayd config
    2. Testing the relayd config
    3. Diagnosing relayd issues
  4. It’s TLS Time with Let’s Encrypt!

    1. Generate acme-client.conf and request certificates
    2. Testing the new TLS certs with relayd and httpd
    3. Set up a crontab for automated cert generation
    4. Automated certificate renewal
  5. Setting Up a Hugo Blog

    1. Create the hugo site directory
    2. Create a new hugo site
    3. Configuring git and installing the PaperMod theme
    4. Updating the PaperMod theme
    5. Configure hugo.toml
    6. Structure of the hugo directory
    7. Lets write about.md
    8. Build and push to the web directory
    9. Skeleton post
    10. Front matter
    11. Create a post
    12. Build and publish
    13. Building another blog post with cover image

Extra: Easy Setup Script

How to Follow this Guide

I have tried to make this post as useful as possible by sneakily trying to teach a little bit of scripting and debugging.

All code between the following lines can be safely copied and pasted into your terminal in one big chunk: ###### COPYPASTASTART ###### ###### COPYPASTAEND ######

If you see a code block which doesn’t have the ###### COPYPASTASTART ######, it simply means it might be easier to paste in each line of code at a time so you can easily read the output of the commands - but you can still copy/paste whole blocks and it should work regardless if you’re in a rush for a nice new blog.

There is also a full KSH script at the end of this blog post if you prefer.

Initial System Maintenance

1. Use syspatch to Update the Server

Ensure your system is up to date and install the necessary packages with the following commands:

syspatch
pkg_add nano git hugo

Explanation:

  • syspatch: Updates the system with the latest patches and security fixes.
  • pkg_add: Installs the following packages:
    • nano: A text editor.
    • git: A version control system for tracking changes in your codebase.
    • hugo: A static site generator for building websites quickly.

Note: If you’re asked which version of Hugo you want, choose the extended version.

2. Setting Your Hostname

To add your hostname (e.g., tatties.scot) to the /etc/myname file, execute the following command:

###### COPYPASTASTART ######

{
  echo 'A root domain name is like tatties.scot, i.e., without the www part.'
  
  # Loop until a valid input is entered
  while true; do
    read -r "hostname_choice?What is your root hostname?: "
    if [ -n "$hostname_choice" ]; then
      break
    fi
  done
  
  echo "$hostname_choice" > /etc/myname
  hostname -s "$hostname_choice"
}

###### COPYPASTAEND ######

All of the following code will use this hostname you have just set.

3. Setting the Server Variables

These are the variables we are going to use for the server. Change them if you want, or just keep it simple and copy/paste as seen.

If you’re not following this entire guide in one sitting, you may need to come back and copy/paste these again or your variables might be empty - you will know they are empty when all the commands stop working! I have made these variables a bit smart, so it shouldn’t change the WEBSERVERport or the SSHport if they’re already set up.

###### COPYPASTASTART ######

HOSTname="$(hostname)"
HOST_name=$(echo "${HOSTname}" | sed 's/\./_/g')
SUBHOSTname="www.${HOSTname}"
SUB_HOST_name="www_${HOST_name}"
HTTPD_CHROOT="/var/www/htdocs"
REL_WEBSITE_DIR="${HOSTname}"
REL_WEBSITE_DIR_PUBLIC="${REL_WEBSITE_DIR}/public"
REL_ACME_CHALLENGE_DIR="${REL_WEBSITE_DIR}/acme"
ABS_ACME_CHALLENGE_DIR="${HTTPD_CHROOT}/${REL_WEBSITE_DIR}/acme"
ABS_WEBSITE_DIR="${HTTPD_CHROOT}/${REL_WEBSITE_DIR}"
ABS_WEBSITE_DIR_PUBLIC="${ABS_WEBSITE_DIR}/public"
ABS_WEBSITE_DIR_LOGS="${ABS_WEBSITE_DIR}/logs"
HUGO_DIR="${ABS_WEBSITE_DIR}/hugo"

# If httpd.conf already contains a port number, use that. Otherwise, generate a random port number.
WEBSERVERport=$(grep -m 1 -o '127\.0\.0\.1 port [0-9]*' /etc/httpd.conf | awk '{print $3}')
if [ -z "$WEBSERVERport" ]; then
  WEBSERVERport=$((RANDOM % (65535 - 1000 + 1) + 1000))
fi

# If sshd_config already contains the default port number, generate a random port number. Otherwise, use same port from sshd_config.
SSHport=$(grep -m 1 -o '#Port 22' /etc/ssh/sshd_config)
if [ -z "$SSHport" ]; then
  SSHport=$(grep -m 1 -o 'Port [0-9]*' /etc/ssh/sshd_config | awk '{print $2}')
else
  SSHport=$((RANDOM % (65535 - 1000 + 1) + 1000))
fi

###### COPYPASTAEND ######

4. Configure the Correct Time for Your Region

To ensure your log files display the correct time, follow these steps:

  1. List the Available Time Zones and Symlink to Yours:

    ls /usr/share/zoneinfo/Europe/
    
  2. Update the /etc/localtime file:

    Glasgow isn’t in the zoneinfo directory, so pick London or create a symlink:

    ln -s /usr/share/zoneinfo/Europe/London /usr/share/zoneinfo/Europe/Glesga
    ln -sf "/usr/share/zoneinfo/Europe/Glesga" /etc/localtime
    

5. Change SSHD port to reduce password attacks

###### COPYPASTASTART ######

{
  sed -i "s/^\(#\)\{0,1\}\(Port \)[0-9]*/\2${SSHport}/" /etc/ssh/sshd_config
  print "Your new SSH port number is: $SSHport"
  print "In future, you can connect with: ssh -p $SSHport $(whoami)@$(hostname)"
}

###### COPYPASTAEND ######

When you’re ready, you can run rcctl -df restart sshd before logging back in on your new ssh port.

An example of the effectiveness of changing the default SSH port from 22:

# doas tail -n 30 /var/log/authlog

May  6 20:34:39 image sshd[50696]: Invalid user hadoop from 170.64.187.255 port 50890
May  6 20:34:40 image sshd[50696]: Failed password for invalid user hadoop from 170.64.187.255 port 50890 ssh2
May  6 20:34:40 image sshd[50696]: Connection closed by invalid user hadoop 170.64.187.255 port 50890 [preauth]
May  6 20:34:52 image sshd[15427]: Invalid user bigdata from 170.64.187.255 port 47118
May  6 20:34:52 image sshd[15427]: Failed password for invalid user bigdata from 170.64.187.255 port 47118 ssh2
May  6 20:34:52 image sshd[15427]: Connection closed by invalid user bigdata 170.64.187.255 port 47118 [preauth]
May  6 20:35:02 image sshd[96148]: Invalid user flink from 170.64.187.255 port 43344
May  6 20:35:03 image sshd[96148]: Failed password for invalid user flink from 170.64.187.255 port 43344 ssh2
May  6 20:35:03 image sshd[96148]: Connection closed by invalid user flink 170.64.187.255 port 43344 [preauth]
May  6 20:35:14 image sshd[70459]: Invalid user tomcat from 170.64.187.255 port 39580
May  6 20:35:14 image sshd[70459]: Failed password for invalid user tomcat from 170.64.187.255 port 39580 ssh2
May  6 20:35:15 image sshd[70459]: Connection closed by invalid user tomcat 170.64.187.255 port 39580 [preauth]
May  6 20:35:27 image sshd[10843]: Invalid user app from 170.64.187.255 port 35810
May  6 20:35:29 image sshd[10843]: Failed password for invalid user app from 170.64.187.255 port 35810 ssh2
May  6 20:35:29 image sshd[10843]: Connection closed by invalid user app 170.64.187.255 port 35810 [preauth]
May  6 20:35:39 image sshd[97733]: Failed password for www from 170.64.187.255 port 60118 ssh2
May  6 20:35:41 image sshd[97733]: Connection closed by authenticating user www 170.64.187.255 port 60118 [preauth]
May  6 20:35:48 image sshd[78349]: Invalid user mahan from 170.64.187.255 port 56500
May  6 20:35:48 image sshd[78349]: Failed password for invalid user mahan from 170.64.187.255 port 56500 ssh2
May  6 20:35:48 image sshd[78349]: Connection closed by invalid user mahan 170.64.187.255 port 56500 [preauth]
May  6 20:35:57 image sshd[75]: Failed password for root from 170.64.187.255 port 52622 ssh2
May  6 20:35:57 image sshd[75]: Connection closed by authenticating user root 170.64.187.255 port 52622 [preauth]
May  6 20:43:26 image sshd[57025]: Received signal 15; terminating.
May  6 20:43:27 image sshd[59058]: Server listening on 0.0.0.0 port 17556.
May  6 20:43:27 image sshd[59058]: Server listening on :: port 17556.
May  6 20:43:52 image sshd[47664]: Received disconnect from XXX.XXX.XXX.69 port 65380:11: disconnected by user
May  6 20:43:52 image sshd[47664]: Disconnected from user root XXX.XXX.XXX.69 port 65430
May  6 20:44:26 image sshd[56497]: Accepted password for root from XXX.XXX.XXX.69 port 63568 ssh2

18 hours later and you can see how quiet my SSH server is… These connections are just me logging in.

May  6 20:43:26 image sshd[57025]: Received signal 15; terminating.
May  6 20:43:27 image sshd[59058]: Server listening on 0.0.0.0 port 17556.
May  6 20:43:27 image sshd[59058]: Server listening on :: port 17556.
May  6 20:43:52 image sshd[47664]: Received disconnect from XXX.XXX.XXX.69 port 65380:11: disconnected by user
May  6 20:43:52 image sshd[47664]: Disconnected from user root XXX.XXX.XXX.69 port 65380
May  6 20:44:26 image sshd[56497]: Accepted password for root from XXX.XXX.XXX.69 port 63568 ssh2
May  7 15:48:20 image sshd[25576]: Accepted password for root from XXX.XXX.XXX.69 port 54435 ssh2

Get it up yae bots!

7. Create a New Non-Root User, Add Them to the wheel Group and Configure doas

After you have created your blog, you will be able to log in with a brand new user you are about to create:

###### COPYPASTASTART ######

{
  while true; do
  read -r "USERname?Enter your username: "
  if [ -n "$USERname" ]; then
    break
  fi
  done

  # Prompt for PASSword
  while true; do
  read -r "PASSword?Enter your password: "
  if [ -n "$PASSword" ]; then
    break
  fi
  done

  # Let's create the new user
  useradd -m -s /bin/ksh -p $(encrypt ${PASSword}) ${USERname}

  # Modify new user to the 'wheel' group
  usermod -G wheel $USERname

  # Update /etc/doas.conf
  echo "permit persist keepenv :wheel" >> /etc/doas.conf
  echo "permit nopass :wheel as www" >> /etc/doas.conf
}

###### COPYPASTAEND ######

Create a Barebones HTTPD Server

We’ll be placing your web directory inside here: /var/www/htdocs/, so it should look something like /var/www/htdocs/tatties.scot.

1. Create Directory Layout with Correct Permissions

As our HTTPD server runs under the www user, we’ll create the following directory structure and permissions.

###### COPYPASTASTART ######

install -d -m 750 -o www -g www ${ABS_WEBSITE_DIR}/public
install -d -m 750 -o www -g www ${ABS_WEBSITE_DIR}/logs
install -d -m 750 -o www -g www ${ABS_ACME_CHALLENGE_DIR}/logs

###### COPYPASTAEND ######

2. Generate the HTTPD Config

Let’s write the /etc/httpd.conf with a super simple config. Notice that there is no TLS mentioned in this config, because HTTPD won’t be responsible for the HTTPS connection we’ll soon have, Relayd will.

###### COPYPASTASTART ######

httpd_conf=$(cat <<EOF
chroot "${HTTPD_CHROOT}"

logdir "${ABS_WEBSITE_DIR_LOGS}"

# If a connection comes in without a matching hostname, drop and block it
server "default" {
  listen on 127.0.0.1 port ${WEBSERVERport}
  block drop
}

server "${HOSTname}" {
  alias www.${HOSTname}
  listen on 127.0.0.1 port ${WEBSERVERport}
  root "${REL_WEBSITE_DIR_PUBLIC}/"
  directory index index.html
  gzip-static

  # Give Letsencrypt access to http://${HOSTname}/.well-known/acme-challenge/*
  location "/.well-known/acme-challenge/*" {
    root "${REL_ACME_CHALLENGE_DIR}/"
    request strip 2
  }
}
EOF
)

print "$httpd_conf" > /etc/httpd.conf

###### COPYPASTAEND ######

Notice that we are listening on 127.0.0.1 (meaning it’s not directly available to anyone but the system itself) and on a random port.

For all connections which come into our HTTPD server which match "default" (i.e. any connection where the host header does not match the exact server hostnames, e.g. server "tatties.scot"), we block and drop it.

For Let’s Encrypt, we drop them into the /var/www/htdocs/$(hostname)/acme which is where OpenBSD’s acme-client will be able to do its song and dance with Let’s Encrypt to get us our certs.

3. Testing the HTTPD Web Server

We’ll write a txt file to our public web directory with the word 'IMUP!', then we’ll enable and fire up the HTTPD web server and give it a try with curl to see if it’s working.

###### COPYPASTASTART ######

doas -u www echo "IMUP!" > ${ABS_WEBSITE_DIR_PUBLIC}/status.txt

if httpd -n; then
  rcctl -df enable httpd
  rcctl set httpd flags "-f /etc/httpd.conf"
  rcctl stop httpd && rcctl -df start httpd && sleep 3
  curl -s -H "Host: ${HOSTname}" http://127.0.0.1:${WEBSERVERport}/status.txt
else
  echo "Something went wrong!"
fi

###### COPYPASTAEND ######

You should see IMUP! if the web server is up!

Notice the curl command with the flag -H "Host: ${HOSTname}", i.e. -H "Host: tatties.scot"?

The reason for that is because we are making a connection to our local machine 127.0.0.1 on the random port that HTTPD server is running on. But like I was last saying, if the hostname doesn’t match any server block, it will simply block and drop it. So I am having to use a little curl trick to pass along the actual hostname I am trying to access while at the same time using a loopback IP Address within my curl command. That way -H "Host: tatties.scot" matches the correct server "tatties.scot" server block within my /etc/httpd.conf instead of dropping my connection.

4. Diagnosing HTTPD Issues

If you experienced any issues with the previous command, the following may help you figure out why:

# Look for the running httpd processes:
ps aux | grep httpd

# Run HTTPD in the foreground to try spot problems that -n missed:
rcctl -df stop httpd
httpd -d -f /etc/httpd.conf

# Ensure HTTPD is listening on the correct port:
netstat -an | grep 127.0.0.1.${WEBSERVERport}

# Ensure it is HTTPD running on that port:
fstat | grep ':80'

# Kill HTTPD and start over:
pkill httpd

Now we have finished setting up the HTTPD Web Server, we will move on to make it publicly accessible using Relayd.

Build a TLS Reverse Proxy with Relayd

Relayd in OpenBSD is a load balancer and application-layer gateway that distributes traffic across multiple servers, performs health checks, and supports TLS termination. It enhances service reliability, performance, and security by ensuring traffic is directed only to healthy servers and managing secure connections.

We will be using Relayd for it for it’s TLS termination and reverse proxying capabilities.

1. Generate the Relayd Config

The following will configure a reverse proxy to allow port 80/443 connections coming in from the outside to be proxied to the HTTPD web server.

###### COPYPASTASTART ######

  relayd_conf=$(cat <<EOF
table <${HOST_name}> { 127.0.0.1 }
table <${SUB_HOST_name}> { 127.0.0.1 }

http protocol "http" {
match request header append "FORWARDED_FOR" value "\$REMOTE_ADDR"
match request header set "FORWARDED_PROTO" value "\$REQUEST_SCHEME"
match request header set "HTTP_HOST" value "\$HOST"
match request header append "REMOTE_ADDR" value "\$REMOTE_ADDR"
match request header set "DOCUMENT_URI" value "\$DOCUMENT_URI"
match request header set "QUERY_STRING" value "\$QUERY_STRING"
match request header set "QUERY_STRING_ENC" value "\$QUERY_STRING_ENC"
match request header set "REMOTE_PORT" value "\$REMOTE_PORT"
match request header set "REMOTE_USER" value "\$REMOTE_USER"
match request header set "SERVER_ADDR" value "\$SERVER_ADDR"
match request header set "SERVER_PORT" value "\$SERVER_PORT"
match request header set "SERVER_NAME" value "\$SERVER_NAME"
match request header set "REQUEST_SCHEME" value "\$REQUEST_SCHEME"
match request header set "REQUEST_URI" value "\$REQUEST_URI"

match request header "Host" value "${HOSTname}" forward to <${HOST_name}>
match request header "Host" value "${SUBHOSTname}" forward to <${SUB_HOST_name}>
}

#£http protocol "https" {
#£  match request header append "FORWARDED_FOR" value "\$REMOTE_ADDR"
#£  match request header set "FORWARDED_PROTO" value "\$REQUEST_SCHEME"
#£  match request header set "HTTP_HOST" value "\$HOST"
#£  match request header append "REMOTE_ADDR" value "\$REMOTE_ADDR"
#£  match request header set "DOCUMENT_URI" value "\$DOCUMENT_URI"
#£  match request header set "QUERY_STRING" value "\$QUERY_STRING"
#£  match request header set "QUERY_STRING_ENC" value "\$QUERY_STRING_ENC"
#£  match request header set "REMOTE_PORT" value "\$REMOTE_PORT"
#£  match request header set "REMOTE_USER" value "\$REMOTE_USER"
#£  match request header set "SERVER_ADDR" value "\$SERVER_ADDR"
#£  match request header set "SERVER_PORT" value "\$SERVER_PORT"
#£  match request header set "SERVER_NAME" value "\$SERVER_NAME"
#£  match request header set "REQUEST_SCHEME" value "\$REQUEST_SCHEME"
#£  match request header set "REQUEST_URI" value "\$REQUEST_URI"
#£  tls keypair "${HOSTname}"
#£  match request header "Host" value "${HOSTname}" forward to <${HOST_name}>
#£  match request header "Host" value "${SUBHOSTname}" forward to <${SUB_HOST_name}>
#£}

relay "${HOST_name}_http_relay" {
listen on 0.0.0.0 port 80
protocol "http"
forward to <${HOST_name}> port ${WEBSERVERport}
forward to <${SUB_HOST_name}> port ${WEBSERVERport}
}

#£relay "${HOST_name}_https_relay" {
#£  listen on 0.0.0.0 port 443 tls
#£  protocol "https"
#£  forward to <${HOST_name}> port ${WEBSERVERport}
#£  forward to <${SUB_HOST_name}> port ${WEBSERVERport}
#£}

EOF
)

  print "$relayd_conf" > /etc/relayd.conf

###### COPYPASTAEND ######

If you are wondering what all those are, those are just comments which will be removed as soon as we have our TLS certs in from Letsencrypt. If we don’t comment those lines out, relayd will error because we haven’t got them yet!

2. Testing the Relayd Config

We’ll check the relayd.conf for any errors before enabling the service and restarting it.

###### COPYPASTSTART ######

relayd -n && rcctl -df enable relayd httpd
rcctl -df stop relayd httpd && rcctl -df start relayd httpd && sleep 3
curl http://${HOSTname}/status.txt

###### COPYPASTAEND ######

You should see 'IMUP!' which confirms that connections coming in are being proxied to the HTTPD web server running on our custom port.

3. Diagnosing Relayd Issues

# Try running curl more verbose:
curl -v https://${HOSTname}/status.txt

# You could also check if Relayd is really up and listening on port 80:
fstat | grep ':80'

# You will see Relayd and HTTPD listening on ports with this one:
netstat -Aan | grep LISTEN

# If all else fails:
reboot

It’s TLS Time with Let’s Encrypt!

The biggest change Edward Snowden brought to the world was the mass adoption of https. These days without https, your site will utterly tank for SEO, so let’s get that dealt with.

1. Generate acme-client.conf and Request Certificates

###### COPYPASTASTART ######

cat << EOF > /etc/acme-client.conf
    authority letsencrypt {
        api url "https://acme-v02.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-privkey.pem"
    }

    authority letsencrypt-staging {
        api url "https://acme-staging-v02.api.letsencrypt.org/directory"
        account key "/etc/acme/letsencrypt-staging-privkey.pem"
    }

    # We will point to our own conf
    include "/etc/acme-client.d/${HOST_name}.conf"
EOF

mkdir -p /etc/acme-client.d

cat << EOF > /etc/acme-client.d/${HOST_name}.conf
    domain ${HOSTname} {
        alternative names { www.${HOSTname} }
        domain key "/etc/ssl/private/${HOSTname}:443.key"
        domain full chain certificate "/etc/ssl/${HOSTname}:443.crt"
        challengedir "${ABS_ACME_CHALLENGE_DIR}"
        sign with letsencrypt
    }
EOF

acme-client -vv "${HOSTname}"

###### COPYPASTAEND ######

Assuming you had no errors and Letsencrypt went swimmingly, we will need to remove the ’s from the relayd.conf and restart it to make use of our new TLS certs.

2. Testing Our New TLS Certs with Relayd and HTTPD

###### COPYPASTASTART ######

# Remove all mentions of '#£' from /etc/relayd.conf
sed -i 's/^#£//' /etc/relayd.conf

relayd -n && rcctl -df restart relayd && sleep 3
curl https://${HOSTname}/status.txt

###### COPYPASTAEND ######

The last curl command should have given you a reply of IMUP!

3. Diagnosing Relayd Issues

Here’s how you can diagnose any issue with Relayd:

# Check if Relayd is listening on port 443 (and 80 whilst we're at it):
netstat -an | grep 'LISTEN' | grep -e '*.80' -e '*.443'

# Try running relayd in the foreground to see what's happening:
relayd -d

# Try running curl more verbose:
curl -v https://${HOSTname}/status.txt

# Check HTTPD is up and listening on the correct port:
fstat | grep -e '127.0.0.1:${WEBSERVERport}'

You shouldn’t have had any errors in the last command, try this if you have: rcctl -df restart httpd relayd.

4. Automated Certificate Renewal

Now we know your acme-client can successfully retrieve certs with Let’s Encrypt, we will want to automate the process for renewing these certs before the 90 day expiry period. We’ll go for once a day at 1am.

(crontab -l 2>/dev/null; echo "0 1 * * * acme-client ${HOSTname}") | crontab -

Setting Up a Hugo Blog

Now that we have our web server and reverse proxy configured, it’s time to set up a Hugo blog to serve some content. Hugo is a fast and flexible static site generator that allows us to create a blog with minimal effort.

1. Create the Hugo Site Directory

###### COPYPASTASTART ######

umask 002
if [ ! -d "${HUGO_DIR}" ]; then
    install -d -m 755 -o www -g www ${HUGO_DIR}
fi

###### COPYPASTAEND ######

2. Create a New Hugo Site

We’ll now create the Hugo directory within our site directory, but not in the public directory. This will use the standard TOML formatting.

###### COPYPASTASTART ######

cd "${HUGO_DIR}"
doas -u www hugo new site .

###### COPYPASTAEND ######

YAML instead of TOML:

If you wish to use YAML instead, you’ll slightly adjust the following command with a --format yaml like so:

###### COPYPASTASTART ######

cd "${HUGO_DIR}"
doas -u www hugo new site . --format yaml

###### COPYPASTAEND ######

3. Configuring GIT and Installing the PaperMod Theme

We’ll be adding the PaperMod theme as a submodule… This is the ‘recommended way’ do do things.

###### COPYPASTASTART ######

cd "${HUGO_DIR}"
doas -u www git init
doas -u www git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
doas -u www git submodule update --init --recursive

###### COPYPASTAEND ######

4. Updating the PaperMod Theme

When you want to update the PaperMod theme to the latest version.

###### COPYPASTASTART ######

cd "${HUGO_DIR}"
doas -u www git submodule update --init --recursive

###### COPYPASTAEND ######

5. Configure the hugo.toml file

This file is super important as it contains the site settings. There are a LOT of settings to configure for a real nice custom blog, but we’ll only be showing the basics. When you are ready to delve deeper, check out the PaperMod features here.

hugo.toml We’ll set the basics here such as your base url, activate the PaperMod theme and add a little menu.

###### COPYPASTASTART ######

doas -u www tee hugo.toml > /dev/null <<EOF
baseURL = "https://${HOSTname}/"
languageCode = "en-us"
title = "My Blog"
theme = "PaperMod"
[params]
description = "This is my brand new blog"
author = "John Doe"

[[menu.main]]
identifier = "home"
name = "Home"
url = "/"
weight = 1

[[menu.main]]
identifier = "about"
name = "About"
url = "/about/"
weight = 2
EOF

###### COPYPASTAEND ######

hugo.yaml (Only if you chose to go with YAML formatting)

###### COPYPASTASTART ######

doas -u www tee hugo.yaml > /dev/null <<EOF
baseURL: "https://${HOSTname}/"
languageCode: "en-us"
title: "My Blog"
theme: "PaperMod"
params:
  description: "This is my brand new blog"
  author: "John Doe"

menu:
  main:
    - identifier: "home"
      name: "Home"
      url: "/"
      weight: 1
    - identifier: "about"
      name: "About"
      url: "/about/"
      weight: 2
EOF

###### COPYPASTAEND ######

6. Structure of the Hugo Directory

layouts/ Themes come with their own ’layouts’ directory, these html layouts. If we want to customise our layouts then we can change the layouts within the theme’s directory or we can copy the layouts to our own empty ’layouts’ directory from the root of the Hugo directory and edit it from there.

The advantage of copying the layouts to our own empty layouts directory is we can update our theme without overwriting our custom layouts we’ve created. Think of it like child themes you may be used to in the Wordpress world.

static/ All static assets like images

content/ This is where all our actual content goes, such as our written blog posts.

We’ll create a layouts directory and then copy all the layouts from the theme into it. The means if we want to customise our layouts that they won’t be written over when we next update the theme. But, you could just skip this step and only copy in the layout files you actually want to customise instead of pulling everything in.

###### COPYPASTASTART ######

doas -u www mkdir -p ${HUGO_DIR}/layouts
doas -u www cp -R ${HUGO_DIR}themes/PaperMod/layouts/* layouts/

###### COPYPASTAEND ######

7. Lets Write about.md

###### COPYPASTASTART ######

doas -u www tee content/about.md > /dev/null <<EOF
---
title: "About"
date: $(date +"%Y-%m-%dT%H:%M:%S%z")
---

I'm John Doe, an OpenBSD enthusiast and developer. I created this blog to share tutorials and insights about OpenBSD.

Feel free to explore the blog and learn more about OpenBSD!
EOF

###### COPYPASTAEND ######

8. Build and Push to the Web Directory

We need to tell Hugo to turn the mardown into actual HTML and fire it into our public facing HTTPD directory.

doas -u www hugo --destination="${ABS_WEBSITE_DIR_PUBLIC}" --cleanDestinationDir=true

The --cleanDestinationDir=true simply removes all other files from the public web directory before building and publishing again. Does what it says really!

Now go and check the blog’s about page, it’s basic still, but we’ll get on to the good stuff next.

9. Skeleton Post

First we’ll create the 'posts' directory and then we’ll use Hugo to generate our first skeleton post with 'Front Matter'.

###### COPYPASTASTART ######

doas -u www mkdir -p content/posts
doas -u www hugo new posts/adding-users-and-setting-up-doas.md

###### COPYPASTAEND ######

Hugo takes the posts/adding-users-and-setting-up-doas.md argument and uses it to create a skeleton post in the contents/posts directory. If we had another directory we wanted to use, we’d give it something like:
doas -u www hugo new anotherdir/a-post-within-anotherdir.md

10. Front Matter

Let’s take a look at content/posts/adding-users-and-setting-up-doas.md (the post we generated above):

tatties.scot# cat content/posts/adding-users-and-setting-up-doas.md

+++
title = 'Adding Users and Setting Up Doas'
date = 2024-05-14T15:17:07+01:00
draft = true
+++

These are the dynamically generated settings. But we can hand edit these settings to also include a cover image and more (we’ll get to that later).

11. Create a Post

As we already have the 'Adding Users and Setting Up Doas' skeleton post with ‘Front Matter’, we’ll use the append flag (tee -a) so to keep the title/date/draft section which hugo created:

###### COPYPASTASTART ######

doas -u www tee -a content/posts/adding-users-and-setting-up-doas.md >> /dev/null <<EOF
In this tutorial, we'll cover how to add users and set up doas on OpenBSD.

## Adding a User

To add a new user, use the \`useradd\` command:

\`\`\`
# useradd -m john
# passwd john
\`\`\`

## Configuring Doas

Nano is a my favourite text editor:

1. Install the nano package:

   \`\`\`
   # pkg_add nano
   \`\`\`

2. Create the /etc/doas.conf file and add the necessary permissions:

   \`\`\`
   # echo "permit keepenv :wheel" > /etc/doas.conf
   \`\`\`

   This allows users in the wheel group to run commands with doas.

3. Add the user to the wheel group:

   \`\`\`
   # usermod -G wheel john
   \`\`\`

Now you can use doas to run commands with elevated privileges.

Happy OpenBSD adventures!
EOF

###### COPYPASTAEND ######

Now, of course.. You don’t need to use KSH HEREDOCS (the EOF bits!) to write blog posts… I’m doing that for the sake of quickly showing you how to get this blog up and running.

Personally, I use Joplin to write my markdown and then I copy and paste it into the .md files or upload to content/posts directory using FileZilla, sftp or scp.

12. Build and Publish

Before we build and publish, we need to change draft = true to draft = false:

doas -u www sed -i 's/draft = true/draft = false/' content/posts/adding-users-and-setting-up-doas.md

Build the static pages and publish to the public web directory

doas -u www hugo --destination="${ABS_WEBSITE_DIR_PUBLIC}" --cleanDestinationDir=true

Time to head over and see your first blog post!

13. Building A Blog Post with Cover Image

Housekeeping

To keep our static directory clutter-free, we’ll create an images directory for this before downloading an image to there.

###### COPYPASTASTART ######

doas -u www mkdir -p static/images
doas -u www ftp https://www.openbsd.org/images/puffy$(uname -r | tr -d .).gif
doas -u www mv puffy*.gif static/images

###### COPYPASTAEND ######

How to Add Cover Images to Posts

We’ll be using the official PaperMod instructions https://adityatelange.github.io/hugo-PaperMod/posts/papermod/papermod-features

Problem is, they only give these instuctions in YAML, but I chose to use TOML…

Not an issue, I will use a YAML to TOML Converter

YAML (the before)

cover:
  image: "<image path/url>"
  # can also paste direct link from external site
  # ex. https://i.ibb.co/K0HVPBd/paper-mod-profilemode.png
  alt: "<alt text>"
  caption: "<text>"
  relative: false # To use relative path for cover image, used in hugo Page-bundles

After the Conversion to TOML

[cover]
  image = "<image path/url>"
  alt = "<alt text>"
  caption = "<text>"
  relative = false

Building The Blog Post with Cover Image

#COPYPASTABEGIN

doas -u www tee content/posts/installing-nginx-on-openbsd.md >> /dev/null <<EOF
+++
title = "Installing Nginx on OpenBSD"
date = 2024-05-14T15:17:07+01:00
[cover]
  image = "images/puffy$(uname -r | tr -d .).gif"
  alt = "Puffy doing puffy things"
  caption = "Puffy"
  relative = true
+++

In this tutorial, we'll walk through the steps to install Nginx on OpenBSD and set up a "Hello, World!" page.

## Installing Nginx

1. Install the nginx package:

   \`\`\`
   # pkg_add nginx
   \`\`\`

2. Enable and start the nginx service:

   \`\`\`
   # rcctl enable nginx
   # rcctl start nginx
   \`\`\`

## Configuring a "Hello, World!" Page

1. Create a new directory for your website:

   \`\`\`
   # mkdir -p /var/www/htdocs/example.com
   \`\`\`

2. Create an index.html file with the following content:

   \`\`\`html
   <!DOCTYPE html>
   <html>
   <head>
     <title>Hello, World!</title>
   </head>
   <body>
     <h1>Hello, World!</h1>
     <p>Welcome to my OpenBSD website!</p>
   </body>
   </html>
   \`\`\`

3. Configure Nginx to serve your website by creating a new configuration file:

   \`\`\`
   # vi /etc/nginx/sites-available/example.com
   \`\`\`

   Add the following content to the file:

   \`\`\`nginx
   server {
       listen 80;
       server_name example.com;
       root /var/www/htdocs/example.com;
       index index.html;
   }
   \`\`\`

4. Create a symlink to enable the site:

   \`\`\`
   # ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
   \`\`\`

5. Restart Nginx:

   \`\`\`
   # rcctl restart nginx
   \`\`\`

Now, when you visit your OpenBSD server's IP address or domain name, you'll see the "Hello, World!" page.

Enjoy your new Nginx setup on OpenBSD!
EOF

# And change the draft status again:
doas -u www sed -i 's/draft = true/draft = false/' content/posts/installing-nginx-on-openbsd.md

#COPYPASTAEND

Build and Publish

doas -u www hugo --destination="${ABS_WEBSITE_DIR_PUBLIC}" --cleanDestinationDir=true

And go check out your creation.


Full Script

#!/bin/ksh

initial_system_maintenance() {
  print "Initial system maintenance"

  syspatch
  pkg_add nano git hugo

  echo 'A root domain name is like tatties.scot, i.e., without the www part.'

}

setting_your_hostname() {
  print "Setting your hostname"

  while true; do
  read -r "hostname_choice?What is your root hostname?: "
  if [ -n "$hostname_choice" ]; then
    break
  fi
  done

  echo "$hostname_choice" > /etc/myname
  hostname -s "$hostname_choice"

}

setting_the_server_variables() {
  print "Setting the server variables"

  HOSTname="$(hostname)"
  HOST_name=$(echo "${HOSTname}" | sed 's/\./_/g')
  SUBHOSTname="www.${HOSTname}"
  SUB_HOST_name="www_${HOST_name}"
  HTTPD_CHROOT="/var/www/htdocs"
  REL_WEBSITE_DIR="${HOSTname}"
  REL_WEBSITE_DIR_PUBLIC="${REL_WEBSITE_DIR}/public"
  REL_ACME_CHALLENGE_DIR="${REL_WEBSITE_DIR}/acme"
  ABS_ACME_CHALLENGE_DIR="${HTTPD_CHROOT}/${REL_WEBSITE_DIR}/acme"
  ABS_WEBSITE_DIR="${HTTPD_CHROOT}/${REL_WEBSITE_DIR}"
  ABS_WEBSITE_DIR_PUBLIC="${ABS_WEBSITE_DIR}/public"
  ABS_WEBSITE_DIR_LOGS="${ABS_WEBSITE_DIR}/logs"
  HUGO_DIR="${ABS_WEBSITE_DIR}/hugo"

  # If httpd.conf already contains a port number, use that. Otherwise, generate a random port number.
  WEBSERVERport=$(grep -m 1 -o '127\.0\.0\.1 port [0-9]*' /etc/httpd.conf | awk '{print $3}')
  if [ -z "$WEBSERVERport" ]; then
    WEBSERVERport=$((RANDOM % (65535 - 1000 + 1) + 1000))
  fi

  # If sshd_config already contains the default port number, generate a random port number. Otherwise, use same port from sshd_config.
  SSHport=$(grep -m 1 -o '#Port 22' /etc/ssh/sshd_config)
  if [ -z "$SSHport" ]; then
    SSHport=$(grep -m 1 -o 'Port [0-9]*' /etc/ssh/sshd_config | awk '{print $2}')
  else
    SSHport=$((RANDOM % (65535 - 1000 + 1) + 1000))
  fi

}

change_sshd_port() {
  print "Change SSHD port"
  sed -i "s/^\(#\)\{0,1\}\(Port \)[0-9]*/\2${SSHport}/" /etc/ssh/sshd_config
  print "Your new SSH port number is: $SSHport"
  print "In future, you can connect with: ssh -p $SSHport $(whoami)@$(hostname)"
}

create_new_user() {
  print "Create new user"
  while true; do
  read -r "USERname?Enter your username: "
  if [ -n "$USERname" ]; then
  break
  fi
  done
  
  # Prompt for PASSword
  while true; do
  read -r "PASSword?Enter your password: "
  if [ -n "$PASSword" ]; then
  break
  fi
  done

  useradd -m -s /bin/ksh -p $(encrypt ${PASSword}) ${USERname}
  usermod -G wheel $USERname

  echo "permit persist keepenv :wheel" >> /etc/doas.conf
  echo "permit nopass :wheel as www" >> /etc/doas.conf
}

create_httpd_server() {
  print "Create HTTPD server"
  local RESPONSE=""
  
  install -d -m 750 -o www -g www ${ABS_WEBSITE_DIR}/public
  install -d -m 750 -o www -g www ${ABS_WEBSITE_DIR}/logs
  install -d -m 750 -o www -g www ${ABS_ACME_CHALLENGE_DIR}/logs

  httpd_conf=$(cat <<EOF
chroot "${HTTPD_CHROOT}"

logdir "${ABS_WEBSITE_DIR_LOGS}"

# If a connection comes in without a matching hostname, drop and block it
server "default" {
  listen on 127.0.0.1 port ${WEBSERVERport}
  block drop
}

server "${HOSTname}" {
  alias www.${HOSTname}
  listen on 127.0.0.1 port ${WEBSERVERport}
  root "${REL_WEBSITE_DIR_PUBLIC}/"
  directory index index.html
  gzip-static

  # Give Letsencrypt access to http://${HOSTname}/.well-known/acme-challenge/*
  location "/.well-known/acme-challenge/*" {
  root "${REL_ACME_CHALLENGE_DIR}/"
  request strip 2
  }
}
EOF
)

  print "$httpd_conf" > /etc/httpd.conf

  doas -u www echo "IMUP!" > ${ABS_WEBSITE_DIR_PUBLIC}/status.txt

  if httpd -n; then
    rcctl -df enable httpd
    rcctl set httpd flags "-f /etc/httpd.conf"
    rcctl stop httpd && rcctl -df start httpd && sleep 3

    RESPONSE=$(curl -s -H "Host: ${HOSTname}" http://127.0.0.1:${WEBSERVERport}/status.txt)

    if [ "$RESPONSE" != "IMUP!" ]; then
        echo "Something went wrong during testing this command: curl -s -H "Host: ${HOSTname}" http://127.0.0.1:${WEBSERVERport}/status.txt"
        exit 1
    fi

  else
    echo "Something went wrong during testing this command: httpd -n"
  fi

  print "Finished writing HTTPD config"

}

relayd_proxy() {
  print "Create Relayd server"
  local RESPONSE=""

  relayd_conf=$(cat <<EOF
table <${HOST_name}> { 127.0.0.1 }
table <${SUB_HOST_name}> { 127.0.0.1 }

http protocol "http" {
match request header append "FORWARDED_FOR" value "\$REMOTE_ADDR"
match request header set "FORWARDED_PROTO" value "\$REQUEST_SCHEME"
match request header set "HTTP_HOST" value "\$HOST"
match request header append "REMOTE_ADDR" value "\$REMOTE_ADDR"
match request header set "DOCUMENT_URI" value "\$DOCUMENT_URI"
match request header set "QUERY_STRING" value "\$QUERY_STRING"
match request header set "QUERY_STRING_ENC" value "\$QUERY_STRING_ENC"
match request header set "REMOTE_PORT" value "\$REMOTE_PORT"
match request header set "REMOTE_USER" value "\$REMOTE_USER"
match request header set "SERVER_ADDR" value "\$SERVER_ADDR"
match request header set "SERVER_PORT" value "\$SERVER_PORT"
match request header set "SERVER_NAME" value "\$SERVER_NAME"
match request header set "REQUEST_SCHEME" value "\$REQUEST_SCHEME"
match request header set "REQUEST_URI" value "\$REQUEST_URI"
match request header "Host" value "${HOSTname}" forward to <${HOST_name}>
match request header "Host" value "${SUBHOSTname}" forward to <${SUB_HOST_name}>
}

#£http protocol "https" {
#£  match request header append "FORWARDED_FOR" value "\$REMOTE_ADDR"
#£  match request header set "FORWARDED_PROTO" value "\$REQUEST_SCHEME"
#£  match request header set "HTTP_HOST" value "\$HOST"
#£  match request header append "REMOTE_ADDR" value "\$REMOTE_ADDR"
#£  match request header set "DOCUMENT_URI" value "\$DOCUMENT_URI"
#£  match request header set "QUERY_STRING" value "\$QUERY_STRING"
#£  match request header set "QUERY_STRING_ENC" value "\$QUERY_STRING_ENC"
#£  match request header set "REMOTE_PORT" value "\$REMOTE_PORT"
#£  match request header set "REMOTE_USER" value "\$REMOTE_USER"
#£  match request header set "SERVER_ADDR" value "\$SERVER_ADDR"
#£  match request header set "SERVER_PORT" value "\$SERVER_PORT"
#£  match request header set "SERVER_NAME" value "\$SERVER_NAME"
#£  match request header set "REQUEST_SCHEME" value "\$REQUEST_SCHEME"
#£  match request header set "REQUEST_URI" value "\$REQUEST_URI"
#£  tls keypair "${HOSTname}"
#£  match request header "Host" value "${HOSTname}" forward to <${HOST_name}>
#£  match request header "Host" value "${SUBHOSTname}" forward to <${SUB_HOST_name}>
#£}

relay "${HOST_name}_http_relay" {
listen on 0.0.0.0 port 80
protocol "http"
forward to <${HOST_name}> port ${WEBSERVERport}
forward to <${SUB_HOST_name}> port ${WEBSERVERport}
}

#£relay "${HOST_name}_https_relay" {
#£  listen on 0.0.0.0 port 443 tls
#£  protocol "https"
#£  forward to <${HOST_name}> port ${WEBSERVERport}
#£  forward to <${SUB_HOST_name}> port ${WEBSERVERport}
#£}

EOF
)

  print "$relayd_conf" > /etc/relayd.conf

  relayd -n && rcctl -df enable relayd httpd
  rcctl -df stop relayd httpd && rcctl -df start relayd httpd && sleep 3

  RESPONSE=$(curl -s http://${HOSTname}/status.txt)

  if [ "$RESPONSE" != "IMUP!" ]; then
      echo "Something went wrong during testing this command: curl -s http://${HOSTname}/status.txt"
      exit 1
  fi

}

acme_setup() {
  print "Create TLS certs"
  local RESPONSE=""

  cat << EOF > /etc/acme-client.conf
authority letsencrypt {
    api url "https://acme-v02.api.letsencrypt.org/directory"
    account key "/etc/acme/letsencrypt-privkey.pem"
}

authority letsencrypt-staging {
    api url "https://acme-staging-v02.api.letsencrypt.org/directory"
    account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

# We will point to our own conf
include "/etc/acme-client.d/${HOST_name}.conf"
EOF

  mkdir -p /etc/acme-client.d

  cat << EOF > /etc/acme-client.d/${HOST_name}.conf
domain ${HOSTname} {
    alternative names { www.${HOSTname} }
    domain key "/etc/ssl/private/${HOSTname}:443.key"
    domain full chain certificate "/etc/ssl/${HOSTname}:443.crt"
    challengedir "${ABS_ACME_CHALLENGE_DIR}"
    sign with letsencrypt
}
EOF

  acme-client -vv "${HOSTname}" && sleep 1
  sed -i 's/^#£//' /etc/relayd.conf
  relayd -n && rcctl -df restart relayd && sleep 3

  RESPONSE=$(curl -s https://${HOSTname}/status.txt)

  if [ "$RESPONSE" != "IMUP!" ]; then
      echo "Something went wrong during testing this command: curl -s https://${HOSTname}/status.txt"
      exit 1
  fi

  (crontab -l 2>/dev/null; echo "0 1 * * * acme-client ${HOSTname}") | crontab -

}

hugo_setup() {
  print "Set up Hugo Blog"

  umask 002
  if [ ! -d "${HUGO_DIR}" ]; then
      install -d -m 755 -o www -g www ${HUGO_DIR}
  fi

  cd "${HUGO_DIR}"
  doas -u www hugo new site .

  doas -u www git init
  doas -u www git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
  doas -u www git submodule update --init --recursive

doas -u www tee hugo.toml > /dev/null <<EOF
baseURL = "https://${HOSTname}/"
languageCode = "en-us"
title = "My Blog"
theme = "PaperMod"
[params]
description = "This is my brand new blog"
author = "John Doe"

[[menu.main]]
identifier = "home"
name = "Home"
url = "/"
weight = 1

[[menu.main]]
identifier = "about"
name = "About"
url = "/about/"
weight = 2
EOF

  doas -u www mkdir -p layouts
  doas -u www cp -R themes/PaperMod/layouts/* layouts/

doas -u www tee content/about.md > /dev/null <<EOF
---
title: "About"
date: $(date +"%Y-%m-%dT%H:%M:%S%z")
---

I'm John Doe, an OpenBSD enthusiast and developer. I created this blog to share tutorials and insights about OpenBSD.

Feel free to explore the blog and learn more about OpenBSD!
EOF

  doas -u www mkdir -p content/posts
  doas -u www mkdir -p static/images
  doas -u www ftp https://www.openbsd.org/images/puffy$(uname -r | tr -d .).gif
  doas -u www mv puffy*.gif static/images
  doas -u www tee content/posts/installing-nginx-on-openbsd.md >> /dev/null <<EOF
+++
title = "Installing Nginx on OpenBSD"
date = 2024-05-14T15:17:07+01:00
[cover]
  image = "images/puffy$(uname -r | tr -d .).gif"
  alt = "Puffy doing puffy things"
  caption = "Puffy"
  relative = true
+++

In this tutorial, we'll walk through the steps to install Nginx on OpenBSD and set up a "Hello, World!" page.

## Installing Nginx

1. Install the nginx package:

   \`\`\`
   # pkg_add nginx
   \`\`\`

2. Enable and start the nginx service:

   \`\`\`
   # rcctl enable nginx
   # rcctl start nginx
   \`\`\`

## Configuring a "Hello, World!" Page

1. Create a new directory for your website:

   \`\`\`
   # mkdir -p /var/www/htdocs/example.com
   \`\`\`

2. Create an index.html file with the following content:

   \`\`\`html
   <!DOCTYPE html>
   <html>
   <head>
     <title>Hello, World!</title>
   </head>
   <body>
     <h1>Hello, World!</h1>
     <p>Welcome to my OpenBSD website!</p>
   </body>
   </html>
   \`\`\`

3. Configure Nginx to serve your website by creating a new configuration file:

   \`\`\`
   # vi /etc/nginx/sites-available/example.com
   \`\`\`

   Add the following content to the file:

   \`\`\`nginx
   server {
       listen 80;
       server_name example.com;
       root /var/www/htdocs/example.com;
       index index.html;
   }
   \`\`\`

4. Create a symlink to enable the site:

   \`\`\`
   # ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
   \`\`\`

5. Restart Nginx:

   \`\`\`
   # rcctl restart nginx
   \`\`\`

Now, when you visit your OpenBSD server's IP address or domain name, you'll see the "Hello, World!" page.

Enjoy your new Nginx setup on OpenBSD!
EOF

  doas -u www sed -i 's/draft = true/draft = false/' content/posts/installing-nginx-on-openbsd.md
  doas -u www hugo --destination="${ABS_WEBSITE_DIR_PUBLIC}" --cleanDestinationDir=true
}

initial_system_maintenance
setting_your_hostname
setting_the_server_variables
change_sshd_port
create_new_user
create_httpd_server
relayd_proxy
acme_setup
hugo_setup


echo "Here's a copy of the variables we used for this:

export HOSTname=\"${HOSTname}\"
export SSHport=\"${SSHport}\"
export USERname=\"${USERname}\"
export PASSword=\"${PASSword}\"
export WEBSERVERport=\"${WEBSERVERport}\"
export HTTPD_CHROOT=\"${HTTPD_CHROOT}\"
export REL_WEBSITE_DIR=\"${REL_WEBSITE_DIR}\"
export REL_WEBSITE_DIR_PUBLIC=\"${REL_WEBSITE_DIR_PUBLIC}\"
export REL_ACME_CHALLENGE_DIR=\"${REL_ACME_CHALLENGE_DIR}\"
export ABS_ACME_CHALLENGE_DIR=\"${ABS_ACME_CHALLENGE_DIR}\"
export ABS_WEBSITE_DIR=\"${ABS_WEBSITE_DIR}\"
export ABS_WEBSITE_DIR_PUBLIC=\"${ABS_WEBSITE_DIR_PUBLIC}\"
export HOST_name=\"${HOST_name}\"
export SUBHOSTname=\"${SUBHOSTname}\"
export SUB_HOST_name=\"${SUB_HOST_name}\"
export HUGO_DIR=\"${HUGO_DIR}\"
> "