Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

KSJ Studios // Kay Saint Joseph

This is my website. I post about video game dev, operating systems, and other stuff I like.

Hosted on Vultr using OpenBSD / httpd. Website written in mdbook.

Want to contact me? –> kay at kayjoseph dot xyz.

-Kay (she/her)

Minecraft Server on OpenBSD

All the cool kids use OpenBSD to host their Minecraft servers. You do want to be cool, right?

Intro

This is a (hopefully) helpful guide to setting up a Minecraft server on OpenBSD. I initially did mine on OpenBSD 7.7, hosted on a Vultr instance. This guide is intended for people with some knowledge of Unix-like systems, but maybe not OpenBSD itself. I’ve tried to include a rationale and explanation for every step, even the “obvious” ones.

This guide assumes you have a working OpenBSD base install, with access to a command line. It also assumes a publicly facing server. Commands prefixed with # are to be run as root and commands prefixed with $ are to be run as the standard user. This guide also assumes you are using ksh for your shell (or at least something else POSIX ish) – anything fancy like fish may not work as expected.

This guide uses PaperMC. Paper is a Minecraft game sever allowing a high level of configuration and performance optimisations.

System Setup

Before doing anything Minecraft related, there’s a few steps in the OpenBSD system itself that need to be taken.

Install Java

Minecraft (the good version, at least) is a Java program. Therefore we need to install Java. Like with most Linux/BSD systems, this is easy to do with the package manager:

# pkg_add jdk
...
Ambiguous: choose package for jdk
a       0: <None>
        1: jdk-1.8.0.462.b08.1v0
        2: jdk-11.0.28.6.1v0
        3: jdk-17.0.16.8.1v0
        4: jdk-21.0.8.9.1v0
Your choice:
...

Then pick the required version – I used jdk-21 (as in, I entered ‘4’ when prompted for a choice) and it works fine. The rest of the guide is based on jdk-21.

Path Java

Unlike in many Unix-like OS’s, OpenBSD does not automatically add Java to the path. The following environment variables will add Java to the path. This guide sets them later on, though they can be set in ~/.profile. They are noted here for ease of reference.

PATH=$PATH:/usr/local/jdk-21/bin
JAVA_HOME=/usr/local/jdk-21/

These flags are for jdk-21, other versions will likely have a different name for the JAVA_HOME folder (i.e a different version number. Though the output is verbose, pkg_info -L jdk can give some clues as to where the JAVA_HOME and bin/ directories are.

login.conf / cap_mkdb

OpenBSD is conservative in its defaults. When I tried to run Java with more than 1G of memory, it failed to launch with a allocation error. To correct this, in /etc/login.conf I edited the default class.

default:\
        ...
        :datasize-max=unlimited:\
        :datasize-cur=unlimited:\
        ...

This tells the system to give users who are in the default class as much memory as they want – given Minecraft is fairly memory hungry, this is important.

To apply these changes, the following command creates the login.conf.db.

# cap_mkdb /etc/login.conf

We could also create a specific minecraft login class, and set the memory / path here. There are two reasons I haven’t done this: 1) I want the default class to have unlimited memory anyway, given my user is part of it; and 2) the path is being changed later on in a way that is more clear.

pf

We will be running Minecraft on a publicly accessible server. Therefore we need to open ports in the pf firewall. Minecraft runs on port 25565 so we will be using that. Some will advise running it on a different port to deter hackers, but this is pointless security through obscurity – a correctly configured server should be safe to open to the world; if it isn’t, you have bigger problems than port numbers.

To open our port, we need to add the following line to /etc/pf.conf.

pass in on egress inet proto tcp from any to any port 25565

This allows traffic in via tcp from any IP through port 25565. To make this change take affect, we need to reload the rule set.

# pfctl -f /etc/pf.conf

Minecraft on System

After all the talk of login classes in the last part, it’s now time to get into the actual Minecraft user and required Minecraft specific setup.

Minecraft User

To run our server, we will be using a separate user. This is to ensure a minimum level of privilege separation. If we used our standard login user, a bad actor who manage to hack the server would then be able to do everything our standard login user could do. This isn’t an impossible scenario – log4j comes to mind as a recent example of an RCE in Java. By using a Minecraft specific user (who we do not trust), and giving them limited privileges, we are protecting ourselves from this type of vulnerability.

The easiest way to add a user on OpenBSD is with simply by running the following:

# adduser

This provides an interactive way to, surprisingly, add a user. Most of the options are up to your discretion, but the important ones are:

Enter username: _minecraft
Enter shell csh ksh nologin sh [sh]: nologin
Login class auth-defaults auth-ftp-defaults daemon default staff [default]:

This guide is based on the user being _minecraft. You can call them something else, but ensure this is changed in the following steps. The nologin group is designed for system users who do not login – such as our Minecraft user. The default class is suitable for our Minecraft user. Note that in previous steps, we adjusted the memory that default class can use, hence why we use it now.

doas -u _minecraft

Append the following into /etc/doas.conf, substituting <main_user> with your main login username.

permit persist keepenv <main_user> as _minecraft

This allows you to then use doas -u _minecraft to run commands as our Minecraft user. For those unfamiliar with OpenBSD, doas works like sudo but is much, much, easier to configure.

A further convenience step is setting up an alias, such as dum=doas -u _minecraft in your own ~/.profile. Given that we can’t login as _minecraft, but are going to be doing a lot of work as them, doing this alias saves a lot of time.

Henceforth, unless otherwise specified, all $ commands are run as user _minecraft.

/var/minecraft

We now need a place to actually run our server from. I use /var/minecraft.

# mkdir /var/minecraft
# chown _minecraft:_minecraft /var/minecraft
# chmod 755 /var/minecraft

This creates the directory, gives it _minecraft, then changes the read access to all users. We will be making this stricter once setup is complete, but for now it makes life a lot easier.

Minecraft Setup

wget paper

We’re now ready to start dealing with the server itself. We can download the PaperMC jar into our Minecraft home, /var/minecraft. For the next steps, I assume your working director is this.

Paper very kindly provide a direct link to download this, making it usable with wget. I have not provided a link to this – it is up to you to find it and verify you have got the right URL.

$ wget -O minecraft/server.jar <papermc-server-url>.jar

start.sh

To start Minecraft, it is helpful to use a script. Here’s one I made earlier. This script assumes you are giving Java 2048M of memory to use – if this is not the case, please change this to reflect your system. We will be looking at more tailored Java launch flags later on.

!#/bin/sh

PATH=$PATH:/usr/local/jdk-21/bin
JAVA_HOME=/usr/local/jdk-21/
JAVA_FLAGS="-Xmx2048M -Xms2048M"

umask 077

java $JAVA_FLAGS -jar /var/minecraft/server.jar --nogui

You will note we have (finally!) added the Java file location to our $PATH here.

This file will need to be executable. I also recommend removing unnecessary permissions from it.

# chmod 700 /var/minecraft/start.sh

This limits start.sh to only be read, written, or executed by _minecraft.

First Launch

The big moment.

$ ./start.sh

From here, we will now have the server console available – we can use this for all the normal Minecraft commands like /op, /tp, /weather, etc. We can also see the chat log and notable events like player deaths and testificate deaths.

Once the server starts completely, so we can move onto configuration of the created files, simply tell it stop in the Minecraft console

> stop

Configuration - Refer Appendix 1

A lot of people, a lot smarter than me, have written multiple guides on server optimisation and configuration – including Java flags, plugins, and config file editing. I’ve put some of my favourites in Appendix 1. This is the time to do it.

Fix Permissions

After doing our configuration, now is a good time to change the permissions and restrict the folder (and everything within) back to our Minecraft user.

# chmod -R go-rwx /var/minecraft/

This command is one of those that, if run in the wrong place, can kill your system. Please ensure you only run it on the Minecraft folder.

Background running with tmux

Currently, we can get Minecraft running in the foreground of our ssh session. If we close the session the server stops. Not ideal. Luckily, we have a way around this.

By using tmux we can run Minecraft in the background, whilst still having console access if we need it. It’s as simple as running:

$ tmux

We then just run the server start script as usual in the tmux window.

$ ./start.sh

Then, Ctrl+B followed by d will “detach” our tmux window and leave Minecraft running in the background. If we need to use the server console again, we can use the following commands.

$ tmux ls
$ tmux attach -dt <session_id>

tmux ls gives the session ID for our tmux session that’s running the server, and tmux attach -dt opens it back up.

Conclusion

We now have a running Minecraft server. All that’s left to do is add the server in your Minecraft client and play! And monitor performance, adjust settings, manage user access, prevent griefing, apply patches, stay on top of costs, manage hardware/software, and do everything else that comes with a publicly accessible server.

Enjoy!

Run into any errors? Something didn’t work like I said it would? Think I’m an idiot with no business running servers, nevermind writing guides about it? Let me know: kay at kayjoseph dot xyz.

Appendix 1 - Tinkering

Basic Security

Two tips for a small server with known members (i.e a friend group):

  • Avoid online-mode=false in your server.properties. There are legitimate use cases for this setting, but this guide does not cater to them.
  • Use a whitelist. Enforce this in server.properties with white-list=true and enforce-whitelist=true. If you need to whitelist someone, use /whitelist add username in the server console or as an op.

Optimisation guides

  • Aikar’s flags: remember when I talked about Java flags earlier? This is where you get them. I have had issues with these flags before, but I suspect that was a case of user error. To use these, simply run the script and replace JAVA_FLAGS="..." with the flags in the command.
  • Paper Chan’s optimisation guide: This is an excellent guide on the various settings that can be changed in Paper to improve performance.

Markdown Converter

Sometimes I write books (or try too, at least). I write in a single markdown file because I find this works for me.

This does, however, pose a problem. You’ve probably notied this website is a mdbook instance that I’ve used as a blog. Needless to say, I think mdbook is really cool.

Unfortunately, mdbook isn’t great for single markdown files – by which I mean it doesn’t work at all. I had a quick look on the web, but couldn’t find anything that would convert for me quick and easily. So I wrote my own! And in the spirit of free software, it is below. Whilst I’d prefer to use PyQt, I have aproject going on at my real work that needs tkinter so I used this as a little practice.

Any problems? Let me know at kay at kayjoseph dot xyz.

#!/usr/bin/env python
'''
Markdown to mdbook converter
Kay Saint Joseph
This program is licensed under the GNU General Public License v3
'''

from tkinter import *
from tkinter import ttk
from tkinter import filedialog
import sys
import os

## convenience function
def read_file(fname) -> list:
    with open(fname, 'r') as f:
        return f.readlines()

## removes comments from the readlines book. Comments are HTML "<!-- ... -->"
def remove_comments(f_lines) -> list:
    comment = False
    ret = []
    for line in f_lines:
        if "<!--" in line:
            comment = True
        if not comment:
            ret.append(line)
        if "-->" in line:
            comment = False
    return ret

# take lines of book. return list of mdbook style SUMMARY.md
def create_summary(lines: list) -> list:
    summary = []
    titles = filter(lambda x: x[0]=='#', lines)
    for title in titles:
        section = len(list(filter(lambda x: x=='#', title)))
        name = title.lstrip(' #').rstrip()
        md = get_chapter_md(title)
        if section == 1:
            summary.append("# {}".format(name))
        else:
            summary.append(" - [{}]({})".format(name, md))
    summary = list(map(lambda x: x+'\n', summary))
    return summary

## takes every line in book and returns dict {chapter name: [line 1, line 2]}
def create_chapters(lines: list) -> dict:
    ret_dict = {}
    chapter_title = ""
    chapter_text = []
    for line in lines:
        if line[0] == '#':
            if chapter_title != "":
                ret_dict[chapter_title] = chapter_text
            chapter_title = line
            chapter_text = []
        else:
            chapter_text.append(line)
    else:
        ret_dict[chapter_title] = chapter_text
    return ret_dict

## debug func
def printdict(d):
    for (k,v) in d.items():
        print(k+": "+str(v[0:3]))

## Take a chapter name such as "The Bog" and turn it into a md file name like
## "thebog.md"
def get_chapter_md(s: str) -> str:
    s = s.lstrip(' #').lower().replace(' ', '_')
    s = ''.join(filter(lambda x: x.isalpha() or x==' ', s))
    s = s + ".md"
    return s

## Add a front page for the mdbook
def write_preamble(s: str):
    with open("src/preamble.md", "w") as f:
        f.writelines(s)

## where the actual work is done
def convert_to_mdbook(book_name: str, author: str, main_md="./main.md", ):
    book = read_file(main_md)
    book = remove_comments(book)
    summary = create_summary(book)
    book = create_chapters(book)
    preamble = ['# ' + book_name + "\n\n", author + "\n"]
    write_preamble(preamble)


    with open("src/SUMMARY.md", "w") as f:
        f.write("[{}](preamble.md)\n".format(book_name))
        f.writelines(summary)
    for (k,v) in book.items():
        fname = "src/" + get_chapter_md(k)
        with open(fname, "w") as f:
            head = "<b>" + k.lstrip(' #') + "</b>\n\n"
            f.write(head)
            f.writelines(v)

## GUI
class App(Frame):
    def __init__(self, master):
        super().__init__(master)
        self.pack()

        ttk.Label(text="MD Booker").pack(pady=10)

        self.t_cont = StringVar()
        self.a_cont = StringVar()
        self.main_md_file = StringVar()

        ttk.Label(text="Title").pack(pady=10)
        self.title = Entry(textvariable=self.t_cont).pack(padx=10)
        ttk.Label(text="Author").pack()
        self.author = Entry(textvariable=self.a_cont).pack(padx=10)

        self.file_label = ttk.Label().pack(side="right")
        self.md_button = ttk.Button(
            text="Pick Markdown File", command=self.md_filepick
        ).pack(pady=10)

        ttk.Button(
            text="Run",
            command=self.process
        ).pack(side="left", pady=10)
        ttk.Button(text="Quit", command=kill_fail).pack(side="right", pady=10)

    def get_title(self):
        return self.t_cont.get()

    def get_author(self):
        return self.a_cont.get()

    ## take the GUI entered info and run the converter itself
    def process(self):
        t = self.get_title()
        a = self.get_author()
        f = self.main_md_file.get()

        if t != "" and a != "" and f != "":
            # print("Title: " + t)
            # print("Author: " + a)
            # print("Main Markdown File: " + f)
            convert_to_mdbook(t, a, main_md=f)
        else:
            raise ValueError("Boxes Empty")
            ttk.messagebox.showinfo("Error", "Boxes Empty")

        sys.exit(0)

    ## pick the main book markdown file to convert
    def md_filepick(self):
        i_dir = os.getcwd()
        filename = filedialog.askopenfilename(
            initialdir = i_dir,
            title = "Pick File"
        )
        self.main_md_file.set(filename)
        self.md_button.config(text=filename)
        return filename

## Option setter from entered args
class Opt():
    def __init__(self, args):
        self.is_gui = False

        if "-h" in args:
            raise UsageError
        elif "-g" in args:
            self.is_gui = True
        else:
            if len(args) != 7:
                raise SyntaxError("Error: too many arguments")
            for i in range(1, len(args)-1):
                if args[i] == "-a":
                    self.author = args[i+1]
                elif args[i] == "-t":
                    self.title = args[i+1]
                elif args[i] == "-f":
                    self.md_file = args[i+1]
                else:
                    continue
            # print("Title: " + self.title)
            # print("Author: " + self.author)
            # print("Markdown file: " + self.md_file)

class UsageError(Exception):
    pass

def kill_fail():
    sys.exit(1)

def usage():
    print(
'''\
MD Converter: a utility for turning a single markdown book into an mdbook
compliant setup.
Usage:  mdconverter -g
        mdconverter -a <author> -t <title> -f <single_markdown_file>
        mdconverter -h
'''
    )
    kill_fail()

def main(args):
    try:
        opt = Opt(args)
    except SyntaxError as e:
        print(e)
        usage()
    except UsageError:
        usage()

    if opt.is_gui:
        root = Tk()
        gui = App(root)
        gui.master.title("MD Book Converter")
        gui.mainloop()
    else:
        try:
            convert_to_mdbook(opt.title, opt.author, main_md=opt.md_file)
        except:
            usage()


if __name__ == "__main__":
    main(sys.argv)

Death’s Season

Someone leaves a voicenote, from deep in the forest, begging for help. A woman answers.

A game by KSJ Studios // Kay Saint Joseph. Made for the Yogscast Jingle Gamejam. The full version is availble on Itch.io, playable in browser.

Play now!


This was the first real game I made. The jam ran from the 1st of December to the 8th of December, 2025. All the assets (except the music and font) were made by me in the 7 or so days. I have since tinkered with it, just to add a little polish.

It didn’t score too well overall, but I wasn’t too surprised as horror games tend to have a specific audience. It also didn’t help that the game jam was for the festive season, rather than Halloween. There’s a couple of technical issues, but all things considered I’m really proud of it.

The comments from those who did like it were positive. The dialogue, atmosphere and story got high praise – these were I skills like to think I had before the jam started (as opposed to the pixel art and using Godot for a real project), so to receive these reviews was great.

Mother’s pixel art was my favourite – unfortunately it wasn’t staged well within the level, so players didn’t see it in its full glory.

The tight time limit forced me to stick with the first thing I did. There was no time for corrections or looking backwards. If it was done, it was in the game as it was. It also forced me to review the scope of the game. The initial plan included choices and multiple endings – these all got scrapped (though I have retrospectively added some of these back in).

Even the story, for instance, was a snap decision. The theme (being “if you can’t beat them, join them”) was announced at 5PM, and as soon as it came out I had to pick a game direction. By around 6:30PM I was programming and designing characters for the game I’d picked.

I was undoubtedly inspired by Ready or Not’s level Carriers of the Vine. There was a concern that the inspiration stretched into straight plagiarism – again though, once I had picked the game direction there was no time to go back. In the end, I think Death’s Season took enough of a different direction that those worries were unfounded.

I’d do a game jam again – maybe not December though, as doing a 9-5 then a 6-2 at Christmastime wasn’t ideal.

- Kay

Seasick

“… you can’t run forever, Cap’n. Sooner or later you run out of ocean to sail. The edge of the map seems far away, but so does a maelstrom till you tilter on the precipice…”

Presenting: SEASICK – a horror videogame by KSJ Studios // Kay Saint Joseph. A sea captain ruled by fear runs from a mistake. But can she ever outrun her sin?

Play as The Captain, steward of The Amber Plague, as she sails the Labyrinthian Sea trying to escape. Travel through uncertain waters and the immaterial; fight for survival; and search for undeserved peace.

Follow along with development through my updates below:)

First Steps

I created the “minimal debug version” today – a main menu with usable buttons; that leads to a small map where the player can control a sprite with very basic movement; and all with music playing.

As the above would require, I also created the main sprite of the the player character’s ship. Pixel art always intimidates me, but once I’m in the zone I love making it. Ask me again in sixteen ships time though…

I also designed a flag for The Amber Plague. I’m sure you will agree, this would strike fear into the heart of any sailor unlucky enough to see it.

The Amber Plague

Tune in next time to see how the more advanced movement controls for the ship go.

Movement & Fun With Classes

I got the movement working pretty nice. The ship accelerates and decelerates slowly, like a ship would. It’s probably a bit sluggish to be “fun” for a player – but since when do game devs like me care about the player? The player is an abstract concept that exists to interfere with my vision.

In any case, the framework is there to tweak as required until it is fun.

In a separate breakthrough, I watched a Youtube video on classes and refactored a lot my ship code into a class for Ship. This includes methods for – among other things – getting the direction the ship is facing, picking an animation to play, and the the movement code. The values for speed and accel are even exported, if you can believe that.

At some point, I managed to introduce a bug that killed The Amber Plague’s movement speed. Given that the movement mechanics had worked well until this point, I was a more than a little concerned by this. However, after poring over the new class to find the code that didn’t like being refactored, I eventually noticed that _process in The Amber Plague’s script had changed to _physics_process.

This must have been the Godot’s editor’s fault. Clearly, it had followed my keystroke instructions when it should’ve just known what I really wanted to do, and done that instead.

Anyway, onwards – next on the todo list, tilemaps.

More Movement! More Sprites!

I didn’t do tilemaps.

To avoid doing tilemaps, and to celebrate the previous update’s movement advancement, I instead drew some shiny new sprites. This means the ship can now face directions – sailing up, sails up. Sailing left or right, sails left or right. Sailing down? That’s right! The Amber Plauge sails down.

There is a slight issue with the sprites, in that the depth and perspective is off. Left and right are fine, but up and down are driving straight out the water or straight into the water. On an almost perpendicular plane. I also think that the rear view of The Amber Plague is too bulky compared to the other views, yet also doesn’t match the size of them.

The art is cool though, so it can stay for now. There’s also the risk that changing it now means I tinker with that forever, and get nothing else done.

We’re moving

I don’t think I can avoid tilemaps any longer. They are the blocker on this section of the roadmaps. I’m not sure why they scare me so much – I think I saw a scary Youtube video at some point.

Improving The Ship Class

In my ongoing quest to avoid doing tilemaps, which will probably end up being one of the simplest concepts in Godot, I have refactored yet more code into the Ship class. The result is that the The Amber Plague’s gdscript has been reduced further into simply:

extends Ship

func _process(delta) -> void:
    var direction = 
        Input.get_vector("move_left", "move_right", "move_up", "move_down")
    var v_delta = direction * accel * delta
    move_and_set_animation(v_delta, delta)
    move_and_slide()

Whilst the ship class previouly had methods to do this, they had to be manually called to get the facing direction, to set the speed, to the animation name for the direction, to make the sprite play said animation, etc. It’s now all done by move_and_set_animation. Three cheers for encapsulation!

It will expand again once I add actual mechanics to the game, no doubt about it, but for now it’s nice and clean.

Enemy ships are even better off – they require no script whatsoever. The Ship class’s _process function handles it all for them, no override needed. Though The Amber Plague will more than likely require more bespoke code, with the enemy ships I should be able to keep their entire code in the class.

To celebrate my successes, I set up a simple enemy ship example and spawned in five thousand of them.

Oooohhhh my PCCCCCCC

A Quick Note on Tilemaps

Tilemaps are actually pretty easy! I only went into the basics, but even the more advanced usage doesn’t look too bad. All my fear was for nothing!

Except.

It wasn’t tilemaps I needed. My thinking was that it should be possible to infinitely generate tile maps wherever the player moved. Unfortunately, tilemaps don’t do that and aren’t for that. There’s probably a way to make it work by making a big one and teleporting it under the player every time the water/waves animation finishes (and starts again) or something, but that sounds 1) really hacky, and 2) unsustainable when I want to add more levels.

This is meant to be a fun cool game, but it’s also a learning experience for me, Kay, and doing hacky stuff doesn’t progress that goal. I’ll happily hack away in a gamejam (like Death’s Season – some of the level scenes in that are duct taped together), but I want to gain something from more from this. There’s no rush – life is about the journey too:)

Anyway, once I’ve watched the hour of so of videos that I have queued up, I’ll be back.

Stardew Valley: Whaling

This is more a vague idea than a real project I’m going to pursue, but wouldn’t it be cool if cozy game Stardew Valley had whaling? A three day event (for example) where you go and commit an act of environmental hatred and also earn shitloads of cash from it. I think it would be cool.

I haven’t really thought through how it would actually work other than get on a ship with Willy or maybe custom whaling NPCs and go hunt whales. I like the idea of having ships like in Dishonored that carry the whale. To my knowledge this particular part of the ship was fictionalised (though the rest of the ship were heavily inspired by real trawlers).

Dishonored whaling ship

I’ve looked on Nexus but couldn’t find anything about a preexisting mod for this. Maybe I’ll make my own, though that will mean learning C#.

Feel free to steal this wonderful idea – just tell me if you do so I can play it.

OS Reviews

Have you ever wondered about alternative operating systems? Have you ever wanted to dip your toe into unix and unix like OSs? Have you ever wanted to read reviews of said OSs by someone woefully underqualified to write them?

Well now you can!

Welcome to my OS review service, where the aspiring (and bona fide!) *nix user can read about what OS is right for them. Featuring all the latest and hottest Unices and Linux distros like:

  • Ubuntu
  • OpenIndiana
  • Slackware
  • openSUSE
  • And more!

* listed OS reviews subject to availability

So settle down, get a hot drink, and prepare to find your next OS.

My Favourite Stuff

In no particular order:

Movies

  • Raw (2016)
  • Pearl (2022)
  • Pirate of the Caribbean: Dead Man’s Chest (2006)
  • Blade Runner 2049 (2017)
  • The Godfather (1972)

Games

  • Prey (2017)
  • Dishonored (2012)
  • Fallout New Vegas (2010)
  • Control (2019)
  • Satisfactory (2019)
  • Ready or Not (2023)
  • Stardew Valley (2016)

Albums

  • Pure Heroine – Lorde
  • My Beautiful Dark Twisted Fantasy – Kanye West
  • Seed – Looming
  • I Don’t Like Shit I Don’t Go Outside – Earl Sweatshirt
  • Goners – Laura Gibson

Books

  • Tender is the Flesh – Agustina Bazterrica
  • Our Wives Under the Sea – Julia Armfield
  • Piranesi – Susanna Clarke
  • Paradise Rot – Jenny Hval
  • Carmilla – J. Sheridan Le Fanu

TV

  • Twin Peaks