Blog

Hint

Minimal, perhaps trivial, most definitely liminal OpenBSD and code related miscellany pertaining to the installation, configuration, administration and ongoing operation of the Open Berkeley UNIX derivative.

Unit testing with pytest—not easily ignored

July 20, 2019

Not as easy as advertised!

Days 10–12 of the 100 Days of Code course from TalkPython is dedicated to unit testing with pytest, and has been a plenitude of information. Prior to this, I had very little experience with pytest and found it less intuitive than the language itself—at least at first. And I think this was due to trying to sophisticate the actual tests; rather than hardcoding the input and expected output of a given test for a function with the parametrize decorator, I would essentially try to rewrite the function logic in a different way to reproduce the expected output. This, however, is counterintuitive because you’re introducing another possibility for faulty logic, albeit in your test, so if your tests pass but both models are flawed, you’d never know. That, and I was testing a command-line driven app, so figuring out how to write tests for sys.argv input took a bit of understanding—and I’m still not entirely sure I’ve written the most efficient tests but I did achieve complete coverage so I’m satisfied with the result. What I want to share, though, is the apparent discrepancy between the advertised way to ignore or exclude directories in your project repository, and what’s actually needed to get it done.

First, the pytest docs suggest that pytest will “intelligently identify and ignore a virtualenv”—but this wasn’t happening for me. So I tried the norecursedirs in my setup.cfg to no avail. Next, I took a chance with collect_ignore and collect_ignore_glob in my conftest.py. This, too, came up short. I then thought I’d settle for invoking pytest with the --ignore option and was duly mystified when that proceeded to fail. Unwilling to accept defeat, I returned to my setup.cfg, drew up a [coverage:run] section, and threw in omit—alas, it worked! Here’s what you need in your setup.cfg if you run into the same problem:

[coverage:run]
omit =
     .env/*

Save it in your project root, and pytest will omit the .env directory contents from its testing. Obviously, you can specify any number of locations, and replace .env with your Python virtual environment directory name.

Tangentially, if—like me—you’re obstinately chasing 100% coverage, you can add the following to circumvent the Pythonic problem:

[coverage:report]
exclude_lines =
     if __name__ == "__main__":

If you haven’t already been using pytest, I highly recommend you give it a go!

pytest coverage report

pytest coverage report for netcalc.py


100 Days of Code

July 09, 2019

Days 1–3: Working with Python’s datetime module

I’m two weeks into a protracted break from school—a 6-week long holiday before term 2 commences—so I picked up a free online MIT IAP 4-week course in C and C++, and finally started a couple Python courses I bought about a year ago from TalkPython taught by Mike Kennedy. They’re admittedly a little basic for my level of Python now, but I’m still learning some Pythonic fundamentals I missed as well as reinforcing good habits so it’s been a sound investment of my extra free time while on term break. I completed the first course in a week, which involved building ten apps that each focused on a core Python concept—such as list comprehensions, generators, file IO, OOP (e.g., classes, inheritance, polymorphism), recursion, lambdas, etc.—and highlighted the syntactic sugar Python provides for a lot of common programming scenarios that greatly reduce the amount of code needed. Not only does this make your source code look better, but less code reduces bug potential and simplifies code review so it’s a win-win all around. In particular, it enlightened me to the yield from keyword—that was introduced in 3.4, I believe, with PEP 380—that opens up a lot of possibilities. It basically allows you to delegate generator responsibilities along a pipeline of chained sub-generators till you’re ready to have your task terminate, return a value, process some data, or whatever it is your coroutine is designed to perform. I found the implementation in the course app interesting enough to do some further research which led me to David Beazley’s presentation at PyCon 2014. It really is some mind blowing stuff! And unsurprisingly forms a lot of the basis of asyncio in Python. I encourage you to at least read through the slides and code examples—but the entire 3-hour talk is well worth the time.

The following code is excerpted from the keyword search app in the course that traverses the file system from a given path and parses each file for a given term:

def search(path, pattern):
   for root, _, files in os.walk(path):
      for file in files:
         file_path = os.path.join(root, file)
         yield from parse(file_path, pattern)

def parse(file, term):
   MatchFound = collections.namedtuple("MatchFound", "file, line, col,\
      text")
   with open(file) as f:
      for idx, line in enumerate(f, 1):
         if line.lower().find(term) >= 0:
            m = MatchFound(
                file=file,
                line=idx,
                col=line.lower().find(term) + 1,
                text=line.strip(),
            )
            yield m

You can see it saves appending each match to an iterable to be returned in its entirety to the calling function. Instead, only one file’s worth of results are held in memory at a given time, which saves resources, improves performance, and makes the code more manageable without sacrificing useability; you still have an iterable at the end of the chain without the expense of a more common implementation. This is just the bare minimum that yield from provides, though, and David does an exemplary job of showcasing its utility.

As part of the second course that I just started, and the namesake of this blog post—100 Days of Code—while doing the unit on datetime, I incidentally found a somewhat terse method to process keyboard input upon each individual keystroke—so a program can continue from any key press without needing to enter return, for example, or a simple robot can be controlled with each key press. Given Python’s notorious concision I was surpised there isn’t something less verbose, but this is a handy function, nevertheless, to have on hand:

import os, sys, termios, time, tty

def charkey():
   fd = sys.stdin.fileno()
   oldset = termios.tcgetattr(fd)
   try:
      tty.setraw(fd)
      ck = sys.stdin.read(1)
   finally:
      termios.tcsetattr(fd, termios.TCSADRAIN, oldset)
      return ck

It’s akin to the curses getch() routine using cbreak mode without having to install the curses module. As part of the datetime unit, the course suggests building a simple Pomodoro timer, and I thought about adding the feature to pause and resume the timer during a work interval with any key press, which led me to this solution using only the standard library. You can pass the returned character to ord() and run it through either a series of if-elif statements or a dictionary to produce the desired outcome. I’ve built the Pomodoro timer, at least as it pertains to the unit’s learning outcomes—utilising datetime and not just time—but haven’t yet added the pause and resume functionality; I’ll share it once I do.


Time Machine Backups on OpenBSD with Netatalk

May 22, 2019

Introduction

Apple’s automatic backup app Time Machine is a fantastic utility that does hourly, daily, and weekly backups of local snapshots. This enables you to restore the system to a previous state in the event of a catastrophic failure—a somewhat rare occurence on the ever-so-stable macOS. The caveat being that storage is limited to AFP (Apple Filing Protocol) compatible devices like the Apple AirPort Time Capsule. Fortunately, Netatalk provides an open source AFP file server that works flawlessly on OpenBSD, and setup is trivial.

Install Netatalk

Like most apps, installation is made super simple with OpenBSD’s pkg utility:

# pkg_add netatalk
quirks-3.124 signed on 2019-04-15T12:10:16Z
Ambiguous: choose package for netatalk
a    0: <None>
     1: netatalk-2.2.6
     2: netatalk-2.2.6-avahi
     3: netatalk-3.1.12
Your choice: 3
netatalk-3.1.12:libevent-2.0.22p1: ok
netatalk-3.1.12: ok
The following new rcscripts were installed: /etc/rc.d/netatalk
See rcctl(8) for details.

Choose the 3.1.x option, as at May 2019 that’s option 3.

Server Configuration

There are not a lot of steps involved; but we first need to enable the dameons before configuring some server side options. Use the rcctl utility:

# rcctl enable messagebus avahi_daemon netatalk
# rcctl order messagebus avahi_daemon netatalk

Netatalk is configured in /etc/netatalk/afp.conf and there are myriad options available so I advise you read the afp.conf man page but you can have a running setup with a fairly minimal configuration. I opted to create a new user specially for Time Machine, but this isn’t required:

[Global]
vol preset = default_for_all_vol
hostname = t420bsd
log file = /var/log/netatalk.log
mimic model = Xserver
keep sessions = yes

[default_for_all_vol]
file perm = 0664
directory perm = 0774
cnid scheme = dbd

[TimeMachine]
time machine = yes
path = /home/timemachine
vol size limit = 500000
valid users = timemachine

The options are mostly self-explanatory, and the afp.conf man page is quite exhaustive, but the gist of it is that global defaults are applied through the [default_for_all_vol] label that sets permissions and database access restrictions, then a specific TimeMachine service is created that maps to the /home/timemachine directory for the one user timemachine with a maximum disk size of 500 GB—this doesn’t need to be the actual disk size, in fact its purpose is to limit Time Machine to only using x amount of disk space otherwise it will use up the entire disk.

Then, run adduser to create the timemachine account that has authorised access to the fileserver:

# adduser
Use option ``-silent'' if you don't want to see all warnings and questions.

Reading /etc/shells
;
Check /etc/master.passwd
Check /etc/group

Ok, let's go.
Don't worry about mistakes. There will be a chance later to correct any input.
Enter username []: timemachine
Enter full name []: Time Machine
Enter shell bash csh ksh nologin sh [ksh]:
Uid [1002]:
Login group timemachine [timemachine]:
Login group is ``timemachine''. Invite timemachine into other groups: guest no
[no]:
Login class authpf bgpd daemon default pbuild staff unbound
[default]:
Enter password []:
Enter password again []:

Name:             timemachine
Password:    ****
Fullname:    Time Machine
Uid:      1002
Gid:      1002 (timemachine)
Groups:           timemachine
;
Login Class: default
HOME:             /home/timemachine
Shell:            /bin/ksh
OK? (y/n) [y]:
Added user ``timemachine''
Copy files from /etc/skel to /home/timemachine
Add another user? (y/n) [y]: n
Goodbye!

Now, simply start the daemons:

# rcctl start messagebus avahi_daemon netatalk

And that’s it for server side setup; time to move onto your macOS client.

macOS Client Setup

Drop into a terminal, and enter the following command to allow macOS to show unsupported—or non-proprietary Apple appliances such as the Time Capsule—network drives:

$ defaults write com.apple.systempreferences TMShowUnsupportedNetworkVolumes 1

Tip

If this will be the first time performing a snapshot and backup of your macOS system, it’s highly advisable you disable CPU throttling for low priority processes by entering the following in your terminal—it will speed up the process exponentially!

$ sudo sysctl debug.lowpri_throttle_enabled=0
Password:
debug.lowpri_throttle_enabled: 1 -> 0

Now open Time Machine, and when you hit Select Disk... you should find your file server available—in the abovementioned setup as t420bsd—for selection!

Caution

It’s equally advisable you re-enable throttling once your first backup is complete, as it also impacts other system processes—processes that you might not want consuming valuable system resources. This can be done with sysctl debug.lowpri_throttle_enabled=1. Alternatively, it will automatically reactivate next system restart.


Samba Filesharing Server on OpenBSD for macOS Client

May 22, 2019

Introduction

I do most of my work on one of two MacBooks—a 2014 Air or 2018 Pro—and occasionally on an older model Lenovo ThinkPad running OpenBSD 6.5. Staying synced between the two Macs is trivial as they’re both in the cloud but apart from using my own Nextcloud server on an OpenBSD VPS for storing some personal data, I wanted a seamless option for filesharing between the ThinkPad and MacBooks when at home on the local network. This was a relatively pain-free task that took all of five minutes.

Package Installation

First, install Samba. OpenBSD has made this as easy as 10,000 other packages with pkg:

# pkg_add samba

Required dependencies will install automatically. Once finished, you can read the post-install documentation provided but configuration is really simple so you can skip this if you trust my instructions (hint: always read the documentation):

$ cat /usr/local/share/doc/pkg-readmes/samba

It’s a relatively short file but it points you to a comprehensive configuration example file, which you should read to get an idea of the available options:

$ cat /usr/local/share/examples/samba/smb.conf.default

Samba Setup

After a thorough read of the provided documentation, open /etc/samba/smb.conf and set your desired options. Mine follow:

[global]
workgroup = HOMELAND
server string = %h server
socket options = TCP_NODELAY IPTOS_LOWDELAY SO_KEEPALIVE SO_RCVBUF=8192 SO_SNDBUF=8192
dns proxy = no
log file = /var/log/samba/%m.log
max log size = 1024
syslog = 3
server role = standalone
hosts allow = 10.0.0.0/255.255.255.0 127.0.0.1
map to guest = Bad User
invalid users = root

[t420]
path = /home/mark
writable = yes
browseable = yes
valid users = mark

But you can make do with a rather minimal smb.conf with the following that will even allow guest users access to a limited guest account:

[global]
workgroup = WORKGROUP
server string = Samba File Server
dns proxy = no
log file = /var/log/samba/%m.log
max log size = 1024
server role = standalone
map to guest = Bad User

[fileshare]
path = /home/username
guest ok = yes
writable = yes
browseable = yes
public = yes

If the path maps to a non-existent directory (i.e., the user doesn’t exist and you don’t want to create a new user), make it now:

# mkdir /home/username
# chown nobody:nobody /home/username

You’re now ready to start the file server.

Start Samba Service

This is made easy with OpenBSD’s rc system control, first enable and then start the dameons:

# rcctl enable smbd nmbd
# rcctl start smbd nmbd

macOS Client Setup

In Finder, open the Go menu and select Connect to server... or use the hotkey command-K then enter your server IP or hostname and shared location into the Server Address field:

smb://serveraddress/fileshare

Another dialog box will appear requiring your username and password; once entered, hit connect and you’re all set!


Computer Science: Session 1, 2019

May 21, 2019

As the end of the session draws near, I came to a few realisations about my move to ECU.

Although this is session 1, for me, it’s closer to the denouement of a first year computer science undergrad. I started this degree in session 2, 2018 at another university, finished four units, and then transferred to ECU in March of this year. Having completed two of the first four units in ECU’s course syllabus at my previous school, I’m totalling the credit points accrued from a year of studies with the completion of just two units this session. And next session, too, I’ll only be taking two units as I also completed part of the syllabus for session 2 last year.

This has been great on two fronts: first, my enrolment finished late, so I didn’t have access to the course material till the end of the second week of studies. And second, my application for advanced standing took several weeks; during that time, I maintained the course load of both schools because in the event my request was denied, I wouldn’t proceed with the transfer—I’d instead  remain at my first school. The cost of repeating units wasn’t worth the opportunity. And having received High Distinction for all but one unit (where I received a Distinction), I knew I’d learned the subject, and understood it well. I say this to reinforce the notion that repeating units was unjustifiably expensive.

My motives for transferring were the offerings at ECU that weren’t available at my first school; namely, the cyber security major. This is where my interest is greatest so I considered the move worthwhile. Fortunately, all signs thus far suggest there was more to be gained from my transfer than the units comprising my major of choice; which  alone were enough for me to make the move in the first place—anything else is a bonus. In addition to them, I’ve also enjoyed a 200% increase in the professorial delivery of course content with 3 hours of lectures and tutorials per unit each week—previously, 1-hour weekly lectures were delivered for each unit. And the language for our software projects is C++, which I consider to be a huge advantage—Java was the language at my last school.

There are numerous other benefits, some trivial, some not so trivial such as, for example, our Microsoft Azure DevOps account and industry folk delivering guest lectures, which provide invaluable insight into work in the field.

Suffice to say, I’m pleased with my decision to study at ECU—the future is bright!


Boost Nextcloud Performance with Redis Cache on OpenBSD

March 7, 2018

Introduction

A PHP memory caching utility such as Redis can significantly decrease load times, speeding up PHP requests by storing compiled files for quicker retrieval.

Install Redis

Both the Redis database and PHP interfacing extension need to be installed:

# pkg_add redis pecl-redis

Add to rc.d to run at startup and then start Redis:

# rcctl enable redis
# rcctl start redis

Redis and Nextcloud Configuration

First, make the directory with appropriate ownership and permissions in the chroot where Redis will create the unix socket file:

# mkdir /var/www/redis
# chown -R www:www /var/www/redis
# chmod 0775 /var/www/redis

Determine the GID of the web server user by either looking at the group file or using id:

# cat /etc/group
wheel:*:0:root

<snip>

www:*:67:

# id -g www
67

Now add user _redis to the www group by either calling usermod or using chpass to replace the existing GID of user _redis with 67:

# usermod -g www _redis
# chpass _redis

Confirm user _redis has been added to www group:

# id -Gn _redis
www

Open /etc/redis.conf and switch communication from TCP to unix socket connection with the following changes:

port 0
unixsocket /var/www/redis/redis.sock
unixsocketperm 770
logfile "redis.log"

Ensure you point Redis to the unix socket file at its absolute path because, unlike httpd, it’s not operating within the chroot.

Now edit Nextcloud configuration to make use of the Redis caching database by opening /var/www/nexctloud/config/config.php in your favourite editor and appending the following to the bottom before the closing );:

'memcache.local' => '\OC\Memcache\Redis',
'memcache.locking' => '\OC\Memcache\Redis',
'redis' =>
array(
'host' => '/redis/redis.sock',
'port' => 0,
'timeout' => 1.5,
),

Make sure you point Nextcloud to the unix socket file at its relative location within the chroot.

Deployment

Restart Redis and all associated daemons:

# rcctl restart redis php56_fpm httpd

Check that the Redis process is running:

# ps -aux |grep redis
_redis   89954  0.0  0.2 13772  2204 ??  Ss    10:21PM    0:00.47 redis-server: /usr/local/sbin/redis-server 127.0.0.1:0 (redis-server)

And you can view redis.log to check for any problems:

# cat /var/redis/redis.log
86204:C 07 Mar 22:21:12.562 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
86204:C 07 Mar 22:21:12.563 # Redis version=4.0.2, bits=32, commit=00000000, modified=0, pid=86204, just started
86204:C 07 Mar 22:21:12.564 # Configuration loaded
89954:M 07 Mar 22:21:12.601 # Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now.
_._
_.-``__ ''-._
_.-``    `.  `_.  ''-._           Redis 4.0.2 (00000000/0) 32 bit
.-`` .-```.  ```\/    _.,_ ''-._
(    '      ,       .-`  | `,    )     Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'|     Port: 0
|    `-._   `._    /     _.-'    |     PID: 89954
`-._    `-._  `-./  _.-'    _.-'
|`-._`-._    `-.__.-'    _.-'_.-'|
|    `-._`-._        _.-'_.-'    |           http://redis.io
`-._    `-._`-.__.-'_.-'    _.-'
|`-._`-._    `-.__.-'    _.-'_.-'|
|    `-._`-._        _.-'_.-'    |
`-._    `-._`-.__.-'_.-'    _.-'
`-._    `-.__.-'    _.-'
`-._        _.-'
`-.__.-'

89954:M 07 Mar 22:21:12.608 # Server initialized
89954:M 07 Mar 22:21:12.609 * DB loaded from disk: 0.000 seconds
89954:M 07 Mar 22:21:12.609 * The server is now ready to accept connections at /var/www/redis/redis.sock

Further Reading

  1. Nextcloud Memory Caching
  2. Redis Official Documentation

Let’s Encrypt HTTPS with acme-client on OpenBSD

March 3, 2018

Introduction

Since OpenBSD 5.9, the base system comes with acme-client: an open source implementation in C that requests a free HTTPS/TLS certificate from the Let’s Encrypt Certificate Authority. It is really simple to setup and even easier to use. And once your certificate is issued, a cronjob will ensure your website stays TLS encrypted for the remainder of its lifetime.

ACME Setup

Open the file /etc/acme-client.conf in your favourite editor and ensure both instances of the agreement url contain the most up-to-date link. Then provide the domain and any subdomains to be covered by the certificate, as well as specifying where to output the generated files, and where challenges should be sent to the web server:

# $OpenBSD: acme-client.conf,v 1.4 2017/03/22 11:14:14 benno Exp $
#
authority letsencrypt {
agreement url "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf"
api url "https://acme-v01.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-privkey.pem"
}

authority letsencrypt-staging {
agreement url "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf"
api url "https://acme-staging.api.letsencrypt.org/directory"
account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

domain www.domain.tld {
alternative names { domain.tld sub1.domain.tld sub2.domain.tld sub3.domain.tld }
domain key "/etc/ssl/private/domain.tld.key"
domain certificate "/etc/ssl/domain.tld.crt"
domain full chain certificate "/etc/ssl/domain.tld.fullchain.pem"
sign with letsencrypt
challengedir "/var/www/htdocs/acme"
}

That finishes acme-client setup.

Web Server Configuration

Edit /etc/httpd.conf so that the web server is able to handle requests from Let’s Encrypt who will issue challenges that need to be processed to determine that you are in control of the domains that will be covered by the requested certificate:

ext_addr="*"

server "domain.tld" {
alias www.domain.tld
listen on $ext_addr port 80

location "/.well-known/acme-challenge/*" {
root "/htdocs/acme"
root strip 2
}
}

Lines 7 to 10 are all that is needed to handle requests from the Certificate Authority. Before restarting the daemon, make and bestow ownership to the web server the directory in httpd‘s chroot where challenges will be processed:

# mkdir /var/www/htdocs/acme
# chown -R www:www /var/www/htdocs/acme

That concludes server configuration.

Certificate Request

Before submitting the certificate signing request to Let’s Encrypt, ensure that any subdomains listed in acme-client.conf are properly setup with your registrar. That is, requests to sub.domain.tld will reach your web server:

# acme-client -vvAD www.domain.tld

A successful result will output the private key, public certificate, and full chain of trust into the ssl directory as specified in acme-client.conf:

/etc/ssl/private/domain.tld.key
/etc/ssl/domain.tld.crt
/etc/ssl/domain.tld.fullchain.pem

Now you can setup your server to receive HTTPS/TLS requests.

Further Reading

  1. Let’s Encrypt Documentation
  2. OpenBSD acme-client Manual Page
  3. Official acme-client Site

OpenBSD Web Server with httpd, MariaDB, PHP and WordPress

March 1, 2018

Introduction

The ubiquitous LAMP (Linux / Apache / MySQL / PHP) Stack that runs on just about every private or SOHO, and even enterprise level, deployment has scores of guides available across the Internet. If you are establishing your own web server for the first time, you can google “LAMP Stack $linux_flavour” and Google will return thousands of results; many of them up-to-date and accurate enough that you can reliably follow the steps provided to deploy a secure production environment where you can serve anything from your WordPress blog to private cloud storage with webmail and online calendar application. Attempting to do the same on OpenBSD, however, is not as fruitful; current, correct, and comprehensive guides are not as forthcoming from a Google search. To start, there is no comparable search pattern. For example, “OAMP Stack” (OpenBSD / Apache / MySQL / PHP) returns 23 results, most of which aren’t relevant, and less than a handful that are current and correct albeit short of comprehensive. Further, the base system web server included in an OpenBSD installation is named httpd, which presents more of a challenge in searching for quality content. There is, however, a silver lining for OpenBSD users: quality documentation and rock solid design. The official OpenBSD documentation is clear and concise; it’s super easy to follow and always correct. And certainly much more reliable than the commonly praised and exhaustive, albeit garrulous, official documentation of its better known cousin, FreeBSD. Further, installation of OpenBSD ported packages provides additional system specific information in /usr/local/share/doc/pkg-readmes with step-by-step instructions that produce sane configuration and a working setup out of the box. OpenBSD developers have put in a lot of work to ensure users are met with a precision in documentation that is equal to the stability and security of the system. It is an example of quality trumping quantity and engineering brilliance!

With that in mind, you will find these pages presenting information sourced from the official OpenBSD documentation, provided in the website FAQ, as well as software official documentation. Proper use of these two sources combined with ad hoc use of relevant Manual pages are more often than not all that is needed for successful installation, configuration, and deployment of all services.

HTTP Daemon Setup

OpenBSD comes with its own web server: httpd. It is secure, lightweight and very simple to use.

In OpenBSD, rcctl is used to configure and control daemons and services; to enable and start httpd enter:

# rcctl enable httpd
# rcctl start httpd

The enable command adds an entry to /etc/rc.conf.local, which is where all the scripts that control the system daemons and services are either set or unset to run during system startup. The start command, as you would suspect, starts the httpd daemon.

OpenBSD provides an example configuration file in /etc/examples/httpd.conf that you can peruse if not use for yourself. Creating your own, though, is really very simple and can be done in as little as a half dozen non-whitespace lines.

Create a new /etc/httpd.conf file with your editor of choice, and add the following (replacing domain.tld with your site particulars):

server "domain.tld" {
     alias "www.domain.tld"
     listen on * port 80
     root "/site_name"
     directory index index.php

     location "/*.php*" { fastcgi socket "/run/php-fpm.sock" }

     location "/.well-known/acme-challenge/*" {
             root "/htdocs/acme"
             root strip 2
     }
}

The meanings of each line can be ascertained from the httpd.conf(5) man page. In short, the above will serve regular http (i.e., port 80) domain.tld and www.domain.tld requests from /var/www/site_name, and will process all PHP requests through a unix socket connection to the FastCGI Process Manager. The second location directive handles acme challenges that will be received when generating a HTTPS certificate with acme-client(1), which is described in detail in this post.

Before moving onto MariaDB installation, check that the syntax is correct with:

# httpd -vnf /etc/httpd.conf
configuration OK

That concludes the rather simple setup of the OpenBSD HTTP daemon, httpd.

Database Installation

OpenBSD offers both PostgreSQL and MariaDB, the open source MySQL fork, as binary packages available through pkg_add. PostgreSQL is an excellent (likely superior) alternative to MySQL, but its configuration is not as widely covered by the official documentation of many third-party applications. Plus, for most personal and SOHO environments it is arguably overkill, with any real advantage not being utilised. In contrast, MySQL/MariaDB installations share common syntax with most documentation from third-party apps. In short, MySQL/MariaDB is simpler than PostgreSQL and more than enough firepower for this use case so it will be installed with:

# pkg_add mariadb-server

Once downloaded, use the install script provided before enabling and starting the daemon:

# mysql_install_db
# rcctl enable mysqld
# rcctl start mysqld

MariaDB provides a post-install script to secure the daemon and create a root superuser account, which should be run with:

# mysql_secure_installation

A series of easily answered questions will follow; set the root password and answer yes to each question.

Warning

Once finished, before accessing the database, change the environment to delete MySQL input rather than have it logged in clear text by adding the following to /etc/rc.conf.local

export MYSQL_HISTFILE=/dev/null

Now access MariaDB as the superuser to create a database for the upcoming WordPress installation:

# mysql -u root -p

Enter the root password when prompted, then:

CREATE DATABASE wordpress;
GRANT ALL ON wordpress.* to 'wp-admin'@'localhost' IDENTIFIED BY '$password';
FLUSH PRIVILEGES;
quit;

To ensure MariaDB can communicate with httpd through a unix socket, edit /etc/my.cnf and change the socket location under both the client and mysqld sections to the absolute path of the file located within the chroot:

[client]
socket = /var/www/var/run/mysql/mysql.sock
[mysqld]
socket = /var/www/var/run/mysql/mysql.sock

Restart the daemon to make changes take effect before moving onto the PHP installation:

# rcctl restart mysqld

PHP Configuration

In OpenBSD, like all applications provided via pkg, PHP is offered as a pre-compiled binary package in one configuration, and any desired extensions that aren’t built in the default provision can be installed as additional packages. Given that not all commonly required extensions are available in the latest available version, at this point in time, it is best to select version 5.6.31 when prompted by the interactive pkg prompt. In addition to the MySQL PHP extensions needed for this deployment, we will install some additional extensions commonly used by other popular applications:

# pkg_add php php-gd php-mysql php-mysqli php-bz2 php-curl php-intl php-mcrypt php-zip php-pdo_dblib php-pdo_mysql

The web server is already configured to handle PHP requests so we can move straight onto PHP configuration by enabling all installed extensions:

# cp /etc/php-5.6.sample/* /etc/php-5.6/.

Some minor adjustments to the default php-fpm.conf file will be made for runtime optimisation. Bring up /etc/php-fpm.conf in your favourite editor and search for the string ‘pm =’ to make the following edits:

pm = ondemand
pm.max_children = 50
pm.max_requests = 500

Next, to cache compiled PHP files and improve performance, activate Zend OpCache by either uncommenting and editing, or simply appending, the following entries to /etc/php-5.6.ini:

[opcache]
opcache.enable=1
opcache.enable_cli=1
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.memory_consumption=128
opcache.save_comments=1
opcache.revalidate_freq=1

Now enable and start php-fpm:

# rcctl enable php56_fpm
# rcctl start php56_fpm

Testing

At this point, httpd is up and serving out of /var/www/site_name, MariaDB has been installed and is ready to create databases required by different web applications, and PHP is configured with FastCGI Process Manager handling requests made through the httpd web server. To test that the setup is working as intended, bring up a new file /var/www/site_name/info.php in your favourite editor and add:

<?

phpinfo();

?>

Now enter domain.tld/info.php in a browser and you should be greeted with the easily recognisable PHP info page.

Web Deployment

To standup a basic website and blog, download the latest WordPress tarball into the /var/www directory:

# ftp -S do&nbsp;https://en-au.wordpress.org/wordpress-4.9.4-en_AU.tar.gz

Extract the contents, then open /etc/httpd.conf to change the root of domain.tld requests to the wordpress directory at its relative path located within the chroot:

root "/wordpress"

Now restart the daemon for changes to take effect:

# rcctl restart httpd

Point your browser to domain.tld where you will be greeted by the WordPress setup screen. Enter the previously created database name, user, password, and host. Leave the database table prefix as is and click Let’s go! You should immediately be met by WordPress advising that it cannot create the wp-config.php file, but providing you with the contents of this file instead. Copy the provided text, and open up /var/www/wordpress/wp-config.php in your favourite editor. Enter the text provided by WordPress, then save and exit the file.0 Now point your browser again to domain.tld where you will be presented with your newly created WordPress site.

If you have any issues following this guide, don’t hesitate to comment or email mark [at] bsdbox [dot] org.

Tip

Alternatively, before pointing your browser to your WordPress installation for the first time, you can chmod 775 /var/www/wordpress and chown :www /var/www/wordpress so that WordPress itself can configure the wp-config.php file without further manual intervention.