Running a Terraria Dedicated Server on NixOS
Tags: terraria, nixos, guide
Published: 2024-02-28 14:04:20

---
title: Running a Terraria Dedicated Server on NixOS
published: true
date: 2024-02-19 17:05:00 UTC
tags: Terraria,NixOS,Guide
canonical_url: https://kevincox.ca/2024/02/19/terraria-server-nixos/
---

<main style="--gap:1em;display:grid;gap:var(--gap);grid:none/1fr minmax(0,36em) 1fr;overflow:auto;padding:0;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><p style="box-sizing:border-box;grid-column:2;margin:0">I recently started playing Terraria and have to say it is a very fun game! To let anyone play at any time I decided to spin up a dedicated server. Unfortunately the Terraria server isn’t what I would consider great server software. Here is how I got a good setup working on NixOS.</p>
<p style="box-sizing:border-box;grid-column:2;margin:0">The main flaw with the Terraria server is that it doesn’t gracefully handle SIGTERM. Generally it is expected that upon receiving SIGTERM an application will gracefully shut down. But Terraria just exits immediately <strong style="box-sizing:border-box;margin:0">without saving the world!</strong> With the non-configurable 30min save interval this means that it is easy to lose a lot of data with a naive setup. Even more annoying is that the way to trigger a graceful exit is to run the server in the console and type <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">save</code> or <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">exit</code> (which saves). But I don’t want to do this manually every time I update Terraria, change the config or restart my server! So we need to automate this.</p>
<aside style="background:#333;box-shadow:4px 4px 8px #000;box-sizing:border-box;color:#fff;display:grid;gap:.4em;grid-column:2;grid-template-columns:0 minmax(0,1fr) 0;margin:0;padding:.4em 0"><p style="box-sizing:border-box;grid-column:2;margin:0"><strong style="box-sizing:border-box;margin:0">Note</strong>: <a href="https://github.com/NixOS/nixpkgs/blob/nixos-unstable/nixos/modules/services/games/terraria.nix" style="box-sizing:border-box;margin:0;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray">NixOS does have a Terraria module</a>, but I wasn’t a fan with how it worked. I’ll discuss the improvements as I get to them. Maybe we can pull some of these improvements upstream.</p></aside><h2 id="config" style="box-sizing:border-box;grid-column:2;margin:.4em 0 0"><a href="https://kevincox.ca/2024/02/19/terraria-server-nixos/#config" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">Config</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0">Let’s just start with a big config dump, then I’ll go over the interesting bits later.</p>
<div style="background:#222;border-color:#444;border-style:solid;border-width:1px 0;box-sizing:border-box;display:grid;grid-column:2;grid-column:1/4;grid-template-columns:subgrid;margin:0;overflow-x:auto;padding:0"><pre style="box-sizing:border-box;grid-column:2;line-height:1;margin:.4em 0;-moz-tab-size:2;-o-tab-size:2;tab-size:2"><code style="background:none!important;box-sizing:border-box;color:#fff;margin:0;white-space:pre-wrap;white-space:pre"><span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">config</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">,</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">lib</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">,</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">pkgs</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">,</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">...</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">}:</span> <span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">let</span>
	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">dataDir</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"/var/lib/terraria"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">worldDir</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">${</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">dataDir</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">}</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">/worlds"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>

	<span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap"># Simple config file serializer. Not very robust.</span>
	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">mkConfig</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">options</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span>
		<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">builtins</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">toFile</span>
			<span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"terraria.cfg"</span>
			<span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">lib</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">concatStrings</span>
				<span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">lib</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">mapAttrsToList</span>
					<span style="box-sizing:border-box;color:#999;margin:0;padding:0">(</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">name</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">value</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">${</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">name</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">}</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">=</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">${</span><span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">toString</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">value</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">}</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">\n</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">)</span>
					<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">options</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">));</span>

	<span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap"># Config Generator</span>
	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">mkWorld</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">name</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">:</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">worldSize</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">?</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"large"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">,</span>
	<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}:</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">config</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">mkConfig</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">world</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">${</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">worldDir</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">}</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">/</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">${</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">name</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">}</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">.wld"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">password</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"YOUR PASSWORD HERE!!!"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">seed</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"kevincox-</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">${</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">name</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">}</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">autocreate</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">small</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">1</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">medium</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">2</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">large</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">3</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">${</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">worldSize</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">upnp</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#ffb433;margin:0;padding:0">0</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
		<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
	<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>

	<span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap"># High-level Config</span>
	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">worlds</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">lib</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">mapAttrs</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">mkWorld</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">my-first-world</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{};</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">some-other-world</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">worldSize</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"medium"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
		<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
	<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>

	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">world</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">worlds</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">my-first-world</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
<span style="box-sizing:border-box;color:#8585e0;font-weight:700;margin:0;padding:0">in</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">users</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">users</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">terraria</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">group</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"terraria"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">home</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">dataDir</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">uid</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">config</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">ids</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">uids</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">terraria</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span> <span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap"># NixOS has a Terraria user, so use those IDs.</span>
	<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>

	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">users</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">groups</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">terraria</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">gid</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">config</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">ids</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">gids</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">terraria</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
	<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>

	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">systemd</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">sockets</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">terraria</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">socketConfig</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">ListenFIFO</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">[</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"/run/terraria.sock"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">];</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">SocketUser</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"terraria"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">SocketMode</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"0660"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">RemoveOnStop</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#8585e0;color:#ffb433;font-weight:700;margin:0;padding:0">true</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
		<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
	<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>

	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">systemd</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">services</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">terraria</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">wantedBy</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">[</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"multi-user.target"</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">];</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">after</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">[</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"network.target"</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">];</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">bindsTo</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">[</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"terraria.socket"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">];</span>

		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">preStop</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">''</span><span style="background-color:#1e0010;box-sizing:border-box;color:#960050;margin:0;padding:0">
</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0"> printf '\nexit\n' &gt;/run/terraria.sock</span><span style="background-color:#1e0010;box-sizing:border-box;color:#960050;margin:0;padding:0">
</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0"> ''</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>

		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">serviceConfig</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">{</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">User</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"terraria"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">ExecStart</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">lib</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">escapeShellArgs</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">[</span>
				<span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">${</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">pkgs</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">terraria-server</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">}</span><span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">/bin/TerrariaServer"</span>
				<span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"-config"</span> <span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">world</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">config</span>
			<span style="box-sizing:border-box;color:#999;margin:0;padding:0">];</span>

			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">StateDirectory</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"terraria"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">StateDirectoryMode</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"0750"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>

			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">StandardInput</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"socket"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">StandardOutput</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"journal"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">StandardError</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"journal"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>

			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">KillSignal</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"SIGCONT"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span> <span style="box-sizing:border-box;color:#ccc;font-style:italic;margin:0;padding:0;white-space:pre-wrap"># Wait for exit after `ExecStop` (https://github.com/systemd/systemd/issues/13284)</span>
			<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">TimeoutStopSec</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#b3d733;margin:0;padding:0">"1h"</span><span style="box-sizing:border-box;color:#999;margin:0;padding:0">;</span>
		<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>
	<span style="box-sizing:border-box;color:#999;margin:0;padding:0">};</span>

	<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">kevincox</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">backup</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">terraria</span><span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">.</span><span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">paths</span> <span style="box-sizing:border-box;color:#f5a3a3;font-weight:700;margin:0;padding:0">=</span> <span style="box-sizing:border-box;color:#999;margin:0;padding:0">[</span>
		<span style="box-sizing:border-box;color:#759ef0;margin:0;padding:0">dataDir</span>
	<span style="box-sizing:border-box;color:#999;margin:0;padding:0">];</span>
<span style="box-sizing:border-box;color:#999;margin:0;padding:0">}</span>
</code></pre></div>
<aside style="background:#333;background:#600;box-shadow:4px 4px 8px #000;box-sizing:border-box;color:#fff;display:grid;gap:.4em;grid-column:2;grid-template-columns:0 minmax(0,1fr) 0;margin:0;padding:.4em 0"><p style="box-sizing:border-box;grid-column:2;margin:0"><strong style="box-sizing:border-box;margin:0">Warning</strong>: I currently just hardcode the password in the config file. This means that it is visible both in my Git repo and world-readable in the Nix store. I don’t consider this password very sensitive so I consider that fine. If desired the config file could be moved to a secret deployed via something like <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">nixops</code>. But for now I am fine with this.</p></aside><h2 id="graceful-exits" style="box-sizing:border-box;grid-column:2;margin:.4em 0 0"><a href="https://kevincox.ca/2024/02/19/terraria-server-nixos/#graceful-exits" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">Graceful Exits</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0">As mentioned earlier Terraria has very ungraceful exits by default. Most of the complexity in this configuration is just handling that. The basic strategy is:</p>
<ol style="-webkit-padding-start:2em;box-sizing:border-box;grid-column:2;margin:0;padding-inline-start:2em">
<li style="box-sizing:border-box;margin:0">Create a socket for stdin.</li>
<li style="box-sizing:border-box;margin:0">Set up <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">ExecStop</code> (via <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">preStop</code>) to request a graceful exit.</li>
</ol>
<p style="box-sizing:border-box;grid-column:2;margin:0">The idea is simple but the implementation is tedious. It took a lot of doc-reading and trial-and-error to get everything working. I’m not going to go over the details but just try deleting any of the related lines to watch it fail.</p>
<ol style="-webkit-padding-start:2em;box-sizing:border-box;grid-column:2;margin:0;padding-inline-start:2em">
<li style="box-sizing:border-box;margin:0">Create a <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">terraria.socket</code> unit for the stdin pipe.</li>
<li style="box-sizing:border-box;margin:0">Bind the units together. Otherwise stopping the socket leaves you unable to gracefully kill the server.</li>
<li style="box-sizing:border-box;margin:0">Configure <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">StandardInput</code> which requires configuring <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">StandardOutput</code> and <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">StandardError</code> as the defaults would otherwise change.</li>
<li style="box-sizing:border-box;margin:0">Configure <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">preStop</code> to request an exit.</li>
<li style="box-sizing:border-box;margin:0">Because our <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">preStop</code> is async we need to disable the <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">KillSignal</code>. We do this by using a noop signal.</li>
</ol>
<p style="box-sizing:border-box;grid-column:2;margin:0">As a side benefit we can just echo whatever we want to the socket to run other admin commands. It is a bit awkward because the output will show up in the journal rather than your console but this seems like the best tradeoff. Just run <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">journalctl -f terraria</code> in another window to see the output.</p>
<aside style="background:#333;box-shadow:4px 4px 8px #000;box-sizing:border-box;color:#fff;display:grid;gap:.4em;grid-column:2;grid-template-columns:0 minmax(0,1fr) 0;margin:0;padding:.4em 0"><p style="box-sizing:border-box;grid-column:2;margin:0"><strong style="box-sizing:border-box;margin:0">Note</strong>: The NixOS module handles this by setting up <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">tmux</code>. While this does give a better interactive experience it increased complexity and swallowed logs. Also the socket is put in the state directory (<code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">/var/lib</code>) rather than the runtime directory (<code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">/run</code>) which made my backup tool angry. Both the logs and socket location were fixable, but I opted for the system with less moving parts in the end.</p></aside><h2 id="world-management" style="box-sizing:border-box;grid-column:2;margin:.4em 0 0"><a href="https://kevincox.ca/2024/02/19/terraria-server-nixos/#world-management" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">World Management</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0">I wanted to be able to declaratively provision new worlds. This is what the <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">worlds</code> variable is for. I can define a bunch of different worlds and then switch between them just by updating the <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">world</code> pointer. The first time a world is loaded it will be automatically generated according to the settings. Most settings (like seed and world size) then become inert. So it is not perfectly declarative, but that is largely expected for persistent data and accomplishes my main use cases of automatically provisioning a new world with just a config change.</p>
<p style="box-sizing:border-box;grid-column:2;margin:0">For now the only option I support is <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">worldSize</code>, but I will surely add more knobs as I need them. For example if I start up another world I might want a per-world password.</p>
<aside style="background:#333;background:#600;box-shadow:4px 4px 8px #000;box-sizing:border-box;color:#fff;display:grid;gap:.4em;grid-column:2;grid-template-columns:0 minmax(0,1fr) 0;margin:0;padding:.4em 0"><p style="box-sizing:border-box;grid-column:2;margin:0"><strong style="box-sizing:border-box;margin:0">Warning</strong>: The NixOS module seems to have trouble with this. When tyring to start up with <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">services.terraria.worldPath</code> set to a location that didn’t exist it would create a new world in <code style="background:#000;border:1px solid #555;border-radius:.3em;box-sizing:border-box;color:#fff;margin:0;padding:0 .2em;white-space:pre-wrap">/var/lib/terraria/.local/share/Terraria/Worlds</code> every time the server started. I don’t know why this is as I didn’t have any issues when creating my module, maybe the config file option is handled better than the command-line flag?</p></aside><h2 id="config-file" style="box-sizing:border-box;grid-column:2;margin:.4em 0 0"><a href="https://kevincox.ca/2024/02/19/terraria-server-nixos/#config-file" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">Config File</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0">I chose to generate a config file rather than passing command-line flags. The main reason for this is that the config file appears to contain a superset of the options available on the command line. So I figured if I start using those options it will be easier to just keep everything in the config file rather than generating both flags and config.</p>
<p style="box-sizing:border-box;grid-column:2;margin:0">For now the config file generator uses no escaping. I don’t even know if Terraria supports any escaping. Ideally I would at least assert that the substituted value doesn’t contain a newline, but I didn’t feel like it.</p>
<h2 id="backups" style="box-sizing:border-box;grid-column:2;margin:.4em 0 0"><a href="https://kevincox.ca/2024/02/19/terraria-server-nixos/#backups" style="box-sizing:border-box;color:inherit!important;margin:0;text-decoration:inherit;text-decoration:underline;-webkit-text-decoration-color:gray;text-decoration-color:gray;text-shadow:inherit!important">Backups</a></h2>
<p style="box-sizing:border-box;grid-column:2;margin:0">Another nice feature of a dedicated server is that I can take and publish world backups. Any player can grab a copy if they want and data-loss in the case of a catastrophic event is limited. Of course, you can also take backups on the host’s computer. But it is nice to just roll into the regular process and monitoring that I already have on my servers.</p></main>