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.
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 without saving the world! 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 save
or exit
(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.
Let’s just start with a big config dump, then I’ll go over the interesting bits later.
{config, lib, pkgs, ...}: let
dataDir = "/var/lib/terraria";
worldDir = "${dataDir}/worlds";
# Simple config file serializer. Not very robust.
mkConfig = options:
builtins.toFile
"terraria.cfg"
(lib.concatStrings
(lib.mapAttrsToList
(name: value: "${name}=${toString value}\n")
options));
# Config Generator
mkWorld = name: {
worldSize ? "large",
}: {
config = mkConfig {
world = "${worldDir}/${name}.wld";
password = "YOUR PASSWORD HERE!!!";
seed = "kevincox-${name}";
autocreate = { small = 1; medium = 2; large = 3; }.${worldSize};
upnp = 0;
};
};
# High-level Config
worlds = lib.mapAttrs mkWorld {
my-first-world = {};
some-other-world = {
worldSize = "medium";
};
};
world = worlds.my-first-world;
in {
users.users.terraria = {
group = "terraria";
home = dataDir;
uid = config.ids.uids.terraria; # NixOS has a Terraria user, so use those IDs.
};
users.groups.terraria = {
gid = config.ids.gids.terraria;
};
systemd.sockets.terraria = {
socketConfig = {
ListenFIFO = ["/run/terraria.sock"];
SocketUser = "terraria";
SocketMode = "0660";
RemoveOnStop = true;
};
};
systemd.services.terraria = {
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
bindsTo = ["terraria.socket"];
preStop = ''
printf '\nexit\n' >/run/terraria.sock
'';
serviceConfig = {
User = "terraria";
ExecStart = lib.escapeShellArgs [
"${pkgs.terraria-server}/bin/TerrariaServer"
"-config" world.config
];
StateDirectory = "terraria";
StateDirectoryMode = "0750";
StandardInput = "socket";
StandardOutput = "journal";
StandardError = "journal";
KillSignal = "SIGCONT"; # Wait for exit after `ExecStop` (https://github.com/systemd/systemd/issues/13284)
TimeoutStopSec = "1h";
};
};
kevincox.backup.terraria.paths = [
dataDir
];
}
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:
ExecStop
(via preStop
) to request a graceful exit.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.
terraria.socket
unit for the stdin pipe.StandardInput
which requires configuring StandardOutput
and StandardError
as the defaults would otherwise change.preStop
to request an exit.preStop
is async we need to disable the KillSignal
. We do this by using a noop signal.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 journalctl -f terraria
in another window to see the output.
I wanted to be able to declaratively provision new worlds. This is what the worlds
variable is for. I can define a bunch of different worlds and then switch between them just by updating the world
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.
For now the only option I support is worldSize
, 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.
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.
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.
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.
]]>AI is hot right now. Everyone is excited, some people are positive, but many are concerned. One common opposition I hear is “AI will steal our jobs”. This phrase always catches my attention, because while it was always framed negatively it really should be a positive. We can now have computers do things that people had to do. Our society can be more productive with less work!
So why are people scared? People are scared because in our society you need a job. Without a job you can’t afford a roof over your head or food on the table. But isn’t that the actual problem? Our society no longer needs this task, but instead of being freed from a now unnecessary duty the labourers are being punished. We have now more resources for everyone, but instead we are just taking resources from people who need them the most, and giving them to billionaires who benefit little.
This isn’t a new problem either. Every time there are talks about closing a coal power plant the coal lobby talks about how many jobs are being created by that power plant and in the mines. But this isn’t a good thing. Jobs aren’t a goal, they are a cost. We have people killing themselves to mine coal so that we can burn that coal to kill more people with pollution. But the coal plant can’t be replaced with a cheaper solar farm because that would require too few jobs. This is a lose-lose-lose scenario. We need to stop paying people to do harmful things. We would be better off paying them to sit at home.
I don’t know what the best solution is. But capitalism clearly isn’t producing the best world for everyone. On this planet we have enough resources for everyone, but many still suffer. Then we tell ourselves that they would suffer more if we didn’t force them to labour for a pittance. This twisted logic is used to justify harmful decisions that make the original problem worse.
We need to make it so that improving our society’s efficiency and productivity doesn’t harm us. People relieved from work mustn’t be punished. I think we should still reward those who do spend their time being “productive” (whatever that means), but that should be a choice you make, not a necessity. Our current society effectively forces people to work. The existence of minimum wage is a symptom of this. If people had complete agency when negotiating a salary they would be able to walk away from offers that were too low. But people don’t have a free choice, they need a job. If people truly had a free choice when signing an employment contract we wouldn’t need rules about minimum wage, we wouldn’t need most worker protections. These are only needed because workers don’t have a free choice, the employer has far more power.
In a good society AI taking people’s jobs would lead to 3-day working weeks, not poverty.
]]>When viewing large numbers it can be helpful to separate thousands or some other grouping. Some languages provide a way to do this natively (such as 1_000_000
) but others do not, or sometimes you are editing a generated file that doesn’t do this. I wrote a highlight rule for Vim to highlight the groups to make these numbers easier to read.
match SpellRare /\d\{1,3}\ze\%(\d\{6}\)*\d\{3}\>/
Replace SpellRare
with any highlight group of your choosing. In my editor it looks like this:
1 1
2 12
3 1234
4 123456
5 1234567890
6 12345678901234
I can imagine it being slightly smarter like not highlighting a 4-digit number, but it is simple and doesn’t noticeably slow down my editor. I’ve tried this in Vim and Neovim, and it works well in both.
Basically it is matching a set of 1-3 digits that is followed by exactly digits then an end-of-word.
]]>