- terractl upgrade <name> lists the zips an instance can use; with a zip or 'latest' it checks the zip, stops the server, backs up worlds and configs to instances/.backups/, pins ZIP=, unpacks right away and starts the server again. Downgrades need --downgrade. - reinstall pins the installed zip when ZIP= is blank, so it never changes version, and keeps server/.installed (uses a .reinstall flag). - Resolve terractl's own path before cd /, so a relative $0 works. - Docs, completion and the daemon's command list; version 0.0.12. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1067 lines
60 KiB
Markdown
1067 lines
60 KiB
Markdown
# terraria-server-manager
|
|
|
|
Run any number of vanilla Terraria and tModLoader dedicated servers on one Linux host, each managed as its own systemd service, with the `terractl` command.
|
|
|
|
## Contents
|
|
|
|
1. [Overview](#overview)
|
|
2. [Components](#components)
|
|
3. [Directory layout](#directory-layout)
|
|
4. [Installation](#installation)
|
|
5. [Migrating from the manual install](#migrating-from-the-manual-install)
|
|
6. [Server zips](#server-zips)
|
|
7. [Creating an instance](#creating-an-instance)
|
|
8. [Starting, stopping and boot](#starting-stopping-and-boot)
|
|
9. [The server console](#the-server-console)
|
|
10. [Configuration](#configuration)
|
|
11. [Mods (tModLoader)](#mods-tmodloader)
|
|
12. [Versions and upgrades](#versions-and-upgrades)
|
|
13. [Deleting, renaming and restoring](#deleting-renaming-and-restoring)
|
|
14. [Exporting and importing](#exporting-and-importing)
|
|
15. [Managing other hosts](#managing-other-hosts)
|
|
16. [Backups](#backups)
|
|
17. [Permissions](#permissions)
|
|
18. [Running by hand for debugging](#running-by-hand-for-debugging)
|
|
19. [Troubleshooting](#troubleshooting)
|
|
20. [Tab completion](#tab-completion)
|
|
21. [Command reference](#command-reference)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Each server is an **instance** with a short name such as `survival` or `calamity`. An instance is:
|
|
|
|
- a directory, `/var/lib/terraria/instances/<name>/`, holding everything about that server;
|
|
- a systemd service, `terraria@<name>.service`, that runs it as the unprivileged `terraria` user.
|
|
|
|
Server software is never installed by hand. You drop the official zips into `/var/lib/terraria/dist`, and the first time an instance starts, the right zip is unpacked into that instance's `server/` folder. Worlds, mods and settings live separately in `data/`, so server software can be replaced or upgraded without touching them.
|
|
|
|
Each instance chooses between **vanilla** and **tModLoader** independently, so both kinds can run side by side on different ports.
|
|
|
|
---
|
|
|
|
## Components
|
|
|
|
Everything is installed by the `terraria-server-manager` package:
|
|
|
|
| File | Purpose |
|
|
|---|---|
|
|
| `/usr/lib/systemd/system/terraria@.service` | Template service. One copy runs per instance. |
|
|
| `/usr/lib/systemd/system/terraria@.socket` | Creates the console pipe the server reads commands from. |
|
|
| `/usr/bin/terractl` | Helper script. The service calls it to set up, run and stop servers; you use it for everyday management. |
|
|
| `/usr/lib/terraria-server-manager/terractl-remote` | The part of `terractl` that manages other hosts (`--host`, `remote`, `daemon`, `copy`, `move`), in Python. |
|
|
| `/usr/lib/systemd/system/terractld.service` | The daemon that lets other hosts manage this one. Off until you turn it on (see [Managing other hosts](#managing-other-hosts)). |
|
|
| `/etc/default/terractl` | Optional overrides (locations, stop timeout, the daemon's address). |
|
|
| `/etc/systemd/journald@terraria.conf` | Size and retention of the servers' own journal (see [Logs](#logs)). |
|
|
| `/usr/lib/sysusers.d/terractl.conf` | Defines the `terraria` system user. |
|
|
| `/usr/lib/tmpfiles.d/terractl.conf` | Creates `/var/lib/terraria` and its subdirectories. |
|
|
| `/usr/share/polkit-1/rules.d/50-terractl.rules` | Lets members of the `terraria` group start, stop and restart servers without `sudo`. |
|
|
| `/usr/share/terraria-server-manager/export-tmod-mods.ps1` | Optional. Copy to your Windows PC to collect your enabled tModLoader mods. |
|
|
|
|
What happens when an instance starts:
|
|
|
|
1. systemd creates the console pipe `/run/terraria/<name>.stdin` (from the socket unit).
|
|
2. `terractl prepare <name>` creates the instance directory and default configs if they're missing, and unpacks the server zip if it hasn't been unpacked yet.
|
|
3. `terractl run <name>` starts the vanilla or tModLoader server, reading commands from the pipe and logging to the servers' own journal (see [Logs](#logs)).
|
|
|
|
On stop, `terractl service-stop` sends `exit` through the pipe so the world is saved before the server shuts down.
|
|
|
|
---
|
|
|
|
## Directory layout
|
|
|
|
```
|
|
/var/lib/terraria/ Home of the 'terraria' user
|
|
├── dist/ Server zips (you add these; never modified)
|
|
│ ├── terraria-server-1458.zip
|
|
│ └── tModLoader-v2026.07.3.0.zip
|
|
└── instances/
|
|
├── <name>/ One directory per instance
|
|
│ ├── instance.conf Which server type and zip to use
|
|
│ ├── serverconfig.txt Terraria's own settings (port, world, password...)
|
|
│ ├── server/ Unpacked server software (replaceable)
|
|
│ │ └── .installed Records what was unpacked
|
|
│ └── data/ Your data (never touched by upgrades)
|
|
│ ├── Worlds/ World files
|
|
│ ├── Mods/ tModLoader mods + enabled.json
|
|
│ ├── ModConfigs/ tModLoader mod settings
|
|
│ └── banlist.txt
|
|
├── .backups/ Backups made by 'terractl upgrade'
|
|
└── .deleted/ Backups made by 'terractl delete'
|
|
|
|
/run/terraria/<name>.stdin Console pipe (exists only while running)
|
|
|
|
/var/lib/terractl/ The daemon's certificate and trusted computers (root only)
|
|
```
|
|
|
|
The server can write only inside `instances/`. It reads `dist/` but can never change it.
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
The package works on Debian 12 or newer and Ubuntu 22.04 or newer. The easiest way is the apt repository, since `sudo apt upgrade` then keeps it up to date:
|
|
|
|
```bash
|
|
sudo install -d -m 0755 /etc/apt/keyrings
|
|
sudo curl -fsSL https://gitea.adbraun.lol/api/packages/adam/debian/repository.key \
|
|
-o /etc/apt/keyrings/terraria-server-manager.asc
|
|
echo "deb [signed-by=/etc/apt/keyrings/terraria-server-manager.asc] https://gitea.adbraun.lol/api/packages/adam/debian stable main" |
|
|
sudo tee /etc/apt/sources.list.d/terraria-server-manager.list
|
|
sudo apt update
|
|
sudo apt install terraria-server-manager
|
|
```
|
|
|
|
The repository is signed; the first two commands fetch its key and let apt trust it for this repository only. Alternatively, download a `.deb` from the releases page and install it directly:
|
|
|
|
```bash
|
|
sudo apt install ./terraria-server-manager_0.0.9_all.deb
|
|
```
|
|
|
|
Using `apt install` (rather than `dpkg -i`) installs the dependencies automatically. `unzip` is required. `curl` and the ICU library are "recommended", which apt installs by default; they're only needed for tModLoader instances, which crash with `Aborted` without ICU. `polkitd` is also recommended: it lets members of the `terraria` group start and stop servers without `sudo` (see [Permissions](#permissions)).
|
|
|
|
Installing the package:
|
|
|
|
- creates the `terraria` system user and group;
|
|
- creates `/var/lib/terraria/`, `/var/lib/terraria/instances/` and `/var/lib/terraria/dist/`;
|
|
- installs the units, the helper script and the polkit rule.
|
|
|
|
It doesn't start or enable anything, including the daemon for managing the host from elsewhere.
|
|
|
|
If you use a firewall, open each instance's port for **TCP**. Terraria uses TCP only.
|
|
|
|
```bash
|
|
sudo ufw allow 7777/tcp # example with ufw
|
|
```
|
|
|
|
### Upgrading the package
|
|
|
|
```bash
|
|
sudo apt update && sudo apt upgrade # from the apt repository
|
|
sudo apt install ./terraria-server-manager_<new-version>_all.deb # or from a downloaded .deb
|
|
```
|
|
|
|
Running servers are not restarted. Each one picks up the new version the next time it starts. Your changes to `/etc/default/terractl` are kept; if the new package also changes that file, dpkg asks which version to keep.
|
|
|
|
### Removing the package
|
|
|
|
```bash
|
|
sudo apt remove terraria-server-manager # stops all instances (worlds are saved first)
|
|
sudo apt purge terraria-server-manager # also removes config and boot-time enablement
|
|
```
|
|
|
|
Neither command deletes `/var/lib/terraria`, the servers' journal or the `terraria` user, so your worlds survive even a purge. To delete everything:
|
|
|
|
```bash
|
|
sudo rm -rf /var/lib/terraria && sudo userdel terraria
|
|
```
|
|
|
|
### Upgrading from the `terraria-instance` package
|
|
|
|
Versions before 0.0.6 were packaged as `terraria-instance`, and the command had the same name. The new package declares that it replaces the old one, so installing it removes `terraria-instance` automatically:
|
|
|
|
```bash
|
|
sudo apt install ./terraria-server-manager_0.0.9_all.deb
|
|
```
|
|
|
|
Things to know:
|
|
|
|
- **Instances are stopped (and saved) during the switch**, because removing the old package stops them. Start them again afterwards with `sudo systemctl start terraria@<name>`. Instances, worlds, mods and the `terraria` user all carry over unchanged.
|
|
- **The command is now `terractl`.** Update any scripts or cron jobs that call `terraria-instance`.
|
|
|
|
**Clean up the old package straight after upgrading.** The old package is removed, but dpkg still keeps a record of it because its settings file is left behind. Purging that record, including with a general cleanup like `apt purge ~c` or `apt autoremove --purge`, runs the old package's purge step. Because the instances have the same unit names, that step deletes their start-at-boot links. So purge it deliberately, then re-enable the instances:
|
|
|
|
```bash
|
|
# 1. If you customised it, copy the old settings file across (purging deletes it)
|
|
sudo cp /etc/default/terraria-instance /etc/default/terractl
|
|
|
|
# 2. Note which instances start at boot, purge the old package, re-enable them
|
|
enabled=$(ls /etc/systemd/system/multi-user.target.wants/ | grep '^terraria@')
|
|
sudo apt purge terraria-instance
|
|
for unit in $enabled; do sudo systemctl enable "$unit"; done
|
|
```
|
|
|
|
### Building from source
|
|
|
|
From the git repository:
|
|
|
|
```bash
|
|
make check # syntax-check the scripts (and shellcheck, if installed)
|
|
make deb # builds build/terraria-server-manager_<version>_all.deb
|
|
sudo make install # or install directly without a package (PREFIX=/usr/local)
|
|
sudo make uninstall # removes a 'make install' (keeps settings and data)
|
|
```
|
|
|
|
`make install` with a prefix other than `/usr` puts the polkit rule in `/etc/polkit-1/rules.d/`, because polkit doesn't read `/usr/local`.
|
|
|
|
The version comes from the `VERSION` file. Set `Maintainer:` in `packaging/deb/control` to yourself before publishing packages.
|
|
|
|
### Changing locations
|
|
|
|
`/etc/default/terractl` can move the instances or zips directory. If you move the instances directory, the service also needs permission to write to the new location:
|
|
|
|
```bash
|
|
sudo systemctl edit terraria@.service
|
|
```
|
|
```ini
|
|
[Service]
|
|
ReadWritePaths=/your/new/path
|
|
```
|
|
|
|
---
|
|
|
|
## Migrating from the manual install
|
|
|
|
The earlier manual setup used `/opt/terraria`, `/terraria`, and a script called `terraria-instance` in `/usr/local/bin`. To move to the package:
|
|
|
|
```bash
|
|
# 1. Note which instances start at boot, then stop everything
|
|
ls /etc/systemd/system/multi-user.target.wants/ | grep terraria@
|
|
sudo systemctl stop 'terraria@*'
|
|
|
|
# 2. Remove the old files. These would override the package's versions:
|
|
# /etc/systemd/system takes precedence over /usr/lib/systemd/system, and
|
|
# /usr/local/bin comes before /usr/bin in PATH.
|
|
sudo rm /etc/systemd/system/terraria@.service /etc/systemd/system/terraria@.socket
|
|
sudo rm /usr/local/bin/terraria-instance
|
|
|
|
# 3. Install the package (keeps the existing 'terraria' user)
|
|
sudo apt install ./terraria-server-manager_0.0.9_all.deb
|
|
|
|
# 4. Move instances and zips
|
|
for d in /opt/terraria/*/; do
|
|
[ -f "$d/instance.conf" ] && sudo mv "$d" /var/lib/terraria/instances/
|
|
done
|
|
[ -d /opt/terraria/.deleted ] && sudo mv /opt/terraria/.deleted /var/lib/terraria/instances/
|
|
sudo mv /terraria/*.zip /var/lib/terraria/dist/
|
|
|
|
# 5. serverconfig.txt contains absolute paths; update them
|
|
sudo sed -i 's#/opt/terraria/#/var/lib/terraria/instances/#g' \
|
|
/var/lib/terraria/instances/*/serverconfig.txt
|
|
sudo chown -R terraria:terraria /var/lib/terraria/instances
|
|
|
|
# 6. Re-enable instances (the old boot links point at the deleted unit file)
|
|
sudo systemctl reenable terraria@calamity # repeat for each instance from step 1
|
|
sudo systemctl start terraria@calamity
|
|
```
|
|
|
|
Alternatively, you can move instances one at a time with `import`, which updates the paths in `serverconfig.txt` and sets ownership for you: `sudo tar -czf /tmp/calamity.tar.gz -C /opt/terraria calamity`, then `sudo terractl import calamity /tmp/calamity.tar.gz`.
|
|
|
|
Once everything works, remove the old directories: `sudo rm -rf /terraria /opt/terraria`. If you set `ZIP=` in an `instance.conf` to an absolute path under `/terraria`, change it to just the file name.
|
|
|
|
The existing `terraria` user keeps `/opt/terraria` as its home directory. That's harmless, because the script sets a per-instance home itself. To tidy it anyway, run `sudo usermod -d /var/lib/terraria terraria` after deleting `/opt/terraria`.
|
|
|
|
---
|
|
|
|
## Server zips
|
|
|
|
Put the official downloads in `/var/lib/terraria/dist`:
|
|
|
|
| Type | File name pattern | Where to get it |
|
|
|---|---|---|
|
|
| Vanilla | `terraria-server-*.zip` | terraria.org (the "PC Dedicated Server" download link) |
|
|
| tModLoader | `tModLoader*.zip` | tModLoader's GitHub releases page (`tModLoader.zip`) |
|
|
|
|
**Rename tModLoader zips to include the version**, for example `tModLoader-v2026.07.3.0.zip`. GitHub always calls the file `tModLoader.zip`, so renaming it lets you keep several versions side by side and pin instances to specific ones.
|
|
|
|
Zips are only read, never changed, and one zip can serve any number of instances.
|
|
|
|
**tModLoader zips don't include .NET.** Each tModLoader instance downloads the .NET runtime it needs into `server/dotnet/` on its first start, so that start needs internet access and `curl`. The download is kept when you upgrade or re-unpack the instance. tModLoader checks the .NET version on every start and fetches a different one itself if a new release needs it.
|
|
|
|
**The `terraria` user must be able to read the zips.** Files copied in normally are fine, but `scp -p` or `cp -p` of a private file keeps its `600` mode, and a server can't be installed from that. `terractl list` warns about such zips, and a server that fails to start because of one says so in its log. Fix them with `sudo chmod 644 /var/lib/terraria/dist/*.zip`.
|
|
|
|
Members of the `terraria` group can copy zips into `dist/` without `sudo`:
|
|
|
|
```bash
|
|
scp tModLoader-v2026.07.3.0.zip you@yourserver:/var/lib/terraria/dist/
|
|
```
|
|
|
|
---
|
|
|
|
## Creating an instance
|
|
|
|
```bash
|
|
sudo terractl new <name> [vanilla|tmodloader] [port]
|
|
```
|
|
|
|
Examples:
|
|
|
|
```bash
|
|
sudo terractl new survival # vanilla, next free port
|
|
sudo terractl new calamity tmodloader # tModLoader, next free port
|
|
sudo terractl new pvp vanilla 7800 # vanilla on port 7800
|
|
```
|
|
|
|
If you don't give a port, the instance gets the next number after the highest port already used, starting at 7777.
|
|
|
|
This creates `/var/lib/terraria/instances/<name>/` with `instance.conf` and `serverconfig.txt`, but **does not start the server**. Before the first start, review `serverconfig.txt` (world size, difficulty, password). For a tModLoader instance, also add your mods first (see [Mods](#mods-tmodloader)): the world is generated on first start, and it should be generated with the mods loaded so their world content is created.
|
|
|
|
Instance names may contain letters, digits, `_`, `-` and `.`, and must not start with `.`.
|
|
|
|
### Quick start: a new modded server
|
|
|
|
```bash
|
|
sudo terractl new calamity tmodloader
|
|
sudo terractl mods calamity add ~/tmod-export # your mods, before the first start
|
|
sudo nano /var/lib/terraria/instances/calamity/serverconfig.txt # optional: world size, difficulty
|
|
sudo terractl enable calamity --now
|
|
sudo terractl attach calamity # watch it load and generate the world
|
|
```
|
|
|
|
You can also skip `new` and just run `sudo systemctl start terraria@<name>`. That creates a **vanilla** instance with default settings; to switch it to tModLoader, change `TYPE` in its `instance.conf` and restart.
|
|
|
|
---
|
|
|
|
## Starting, stopping and boot
|
|
|
|
Instances are ordinary systemd services, which `terractl` starts and stops for you:
|
|
|
|
```bash
|
|
sudo terractl start calamity
|
|
sudo terractl stop calamity # saves the world, then exits
|
|
sudo terractl restart calamity
|
|
```
|
|
|
|
These are the same as `systemctl start|stop|restart terraria@<name>`, which work too; `systemctl status terraria@<name>` shows the details. `terractl start` refuses names that have no instance, whereas `systemctl start` creates one.
|
|
|
|
Whether a server starts at boot is set with `terractl`:
|
|
|
|
```bash
|
|
sudo terractl enable calamity # start at boot
|
|
sudo terractl enable calamity --now # start at boot, and start it now
|
|
sudo terractl disable calamity # don't start at boot (a running server keeps running)
|
|
sudo terractl disable calamity --now # don't start at boot, and stop it now (saves the world)
|
|
```
|
|
|
|
These do the same as `systemctl enable`/`disable terraria@<name>`, which also still work. `terractl enable` refuses names that have no instance, so a typo can't create a new instance at the next boot.
|
|
|
|
Members of the `terraria` group can `start`, `stop` and `restart` servers without `sudo` (see [Permissions](#permissions)). `enable` and `disable` always need `sudo`.
|
|
|
|
To see all instances with their type, port, whether they start at boot, and state:
|
|
|
|
```bash
|
|
sudo terractl list
|
|
```
|
|
|
|
```
|
|
INSTANCE TYPE PORT BOOT STATE
|
|
calamity tmodloader 7778 enabled active
|
|
survival vanilla 7777 disabled inactive
|
|
```
|
|
|
|
Behaviour to be aware of:
|
|
|
|
- **Crashes restart automatically** after 10 seconds.
|
|
- **A clean exit does not restart.** If you type `exit` in the server console, the server saves and stops, and stays stopped until you start it again.
|
|
- **The first start of a tModLoader instance is slow.** It downloads .NET, loads the mods and generates the world, which can take several minutes.
|
|
- **Stopping waits for the save.** The server gets up to 80 seconds to save and exit before systemd forces it to stop.
|
|
|
|
---
|
|
|
|
## The server console
|
|
|
|
Each server reads console commands (`save`, `say`, `kick`, `playing`, and so on) from its pipe, and its output goes to the servers' own journal.
|
|
|
|
### Interactive: `attach`
|
|
|
|
```bash
|
|
sudo terractl attach calamity
|
|
```
|
|
|
|
This shows the last 30 lines of output and then follows it live. Anything you type is sent to the server, with arrow-key editing and history. **Ctrl-D** or **Ctrl-C** detaches, and the server keeps running.
|
|
|
|
`attach` will not send `exit`, since that would stop the server. Use `terractl stop` to stop it.
|
|
|
|
The one visible difference from a real terminal: the server's `:` prompt only appears once a newline follows it.
|
|
|
|
### Read-only: `watch`
|
|
|
|
```bash
|
|
terractl watch calamity # last 30 lines, then follow
|
|
terractl watch calamity -n 200 # start with the last 200 lines
|
|
terractl watch calamity -q # without the lines about what it's doing
|
|
```
|
|
|
|
`watch` shows the server's output like `attach` does, but never sends anything to the server. That makes it safe to leave open, for instance on a second screen, with no risk of typing into the console. **Ctrl-C** quits and the server keeps running. It also works while the server is stopped: it shows the last output and picks up again when the server starts.
|
|
|
|
### One-off commands: `cmd`
|
|
|
|
```bash
|
|
sudo terractl cmd calamity save
|
|
sudo terractl cmd calamity say Restarting in 5 minutes
|
|
```
|
|
|
|
`cmd` prints the server's reply, then returns once the server has been quiet for a second (at most 5 seconds). For example, `terractl cmd calamity playing` lists who's online. That makes it handy in scripts and cron jobs too. If the server doesn't answer within 5 seconds (for instance while it's still loading), `cmd` says so; the command has still been sent.
|
|
|
|
Members of the `terraria` group can use `attach`, `watch` and `cmd` fully, output included, without `sudo`. See [Logs](#logs) for how that's arranged.
|
|
|
|
### Logs
|
|
|
|
The servers don't log into the main system journal. They have **their own journal**, a systemd *log namespace* called `terraria`. That keeps them separate for two reasons:
|
|
|
|
- **Access.** The `terraria` group is given read access to the servers' journal and nothing else, so moderators can follow the game servers without being able to read the rest of the system's logs (logins, `sudo` use, other services).
|
|
- **Space.** Chatty game servers can't push system logs out of a size-limited journal, or the other way round.
|
|
|
|
To read it with `journalctl`, add `--namespace=terraria`:
|
|
|
|
```bash
|
|
journalctl --namespace=terraria -u terraria@calamity -f # follow
|
|
journalctl --namespace=terraria -u terraria@calamity -n 100 # last 100 lines
|
|
journalctl --namespace=terraria -u terraria@calamity --since today
|
|
journalctl --namespace=+terraria -u terraria@calamity # also systemd's own start/stop messages
|
|
```
|
|
|
|
Plain `journalctl -u terraria@calamity`, without `--namespace`, only shows systemd's messages about the service (started, stopped, failed), not the server's output. With `+terraria`, both are shown together; that part needs root or the `systemd-journal` group, because systemd's messages are in the system journal.
|
|
|
|
**Size and retention** are set in `/etc/systemd/journald@terraria.conf`, which by default allows up to 256 MB on disk. After editing it, run `sudo systemctl restart systemd-journald@terraria`.
|
|
|
|
**How the group access works.** Before each server starts, a small root step (`terractl journal-access`) creates the servers' journal folder, `<machine-id>.terraria` under `/var/log/journal` (or `/run/log/journal` if the system doesn't keep logs across reboots). It gives that folder a read permission for the `terraria` group, which journald's files inherit, including after log rotation. It never creates `/var/log/journal` itself, because that would switch the whole system to persistent logging.
|
|
|
|
tModLoader also writes its own logs to `/var/lib/terraria/instances/<name>/server/tModLoader-Logs/`:
|
|
|
|
- `Launch.log` covers the .NET download and startup.
|
|
- `server.log` covers mod loading and gameplay.
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
Each instance has two config files for two different purposes.
|
|
|
|
### `serverconfig.txt`: Terraria's settings
|
|
|
|
This is Terraria's standard server config, read by the game itself. The generated default:
|
|
|
|
```ini
|
|
world=/var/lib/terraria/instances/<name>/data/Worlds/<name>.wld
|
|
worldpath=/var/lib/terraria/instances/<name>/data/Worlds
|
|
autocreate=2 # create world if missing: 1=small 2=medium 3=large
|
|
worldname=<name>
|
|
difficulty=0 # 0=classic 1=expert 2=master 3=journey
|
|
#seed=
|
|
port=7777
|
|
maxplayers=8
|
|
password=
|
|
motd=Welcome to <name>
|
|
secure=1
|
|
upnp=0
|
|
language=en-US
|
|
banlist=/var/lib/terraria/instances/<name>/data/banlist.txt
|
|
```
|
|
|
|
`autocreate`, `worldname`, `difficulty` and `seed` only matter when the world file doesn't exist yet. Once the world has been created, changing them has no effect.
|
|
|
|
Changes take effect on restart. Every instance must use a different `port`.
|
|
|
|
To use an existing world, copy the `.wld` file (plus `.twld` for tModLoader worlds) into `data/Worlds/` and point `world=` at it.
|
|
|
|
### `instance.conf`: how the instance runs
|
|
|
|
The game never reads this file. The helper script reads it at every start.
|
|
|
|
```bash
|
|
TYPE=tmodloader # vanilla | tmodloader
|
|
ZIP= # zip to use; blank = newest matching zip at first install
|
|
EXTRA_ARGS=() # extra server arguments, as a bash array
|
|
```
|
|
|
|
| Setting | Effect |
|
|
|---|---|
|
|
| `TYPE` | Which server software runs. Changing it re-unpacks `server/` from the other kind of zip on the next start. `data/` and `serverconfig.txt` are kept. |
|
|
| `ZIP` | Pins a specific zip from `/var/lib/terraria/dist` (a file name, or an absolute path). Changing it re-unpacks `server/` on the next start; `terractl upgrade` sets it for you (see [Upgrading](#upgrading-or-downgrading-an-instance)). If left blank, the newest matching zip is used at first install and **never upgraded automatically**. |
|
|
| `EXTRA_ARGS` | Extra command-line arguments, e.g. `EXTRA_ARGS=(-disableannouncementbox)`. |
|
|
|
|
This file also marks the directory as an instance: `list` ignores directories without one, and `delete` refuses to remove them.
|
|
|
|
---
|
|
|
|
## Mods (tModLoader)
|
|
|
|
Mods only work on tModLoader instances. Each instance has its own separate set of mods:
|
|
|
|
```
|
|
/var/lib/terraria/instances/<name>/data/Mods/
|
|
├── enabled.json ["CalamityMod", "MagicStorage"]
|
|
├── CalamityMod.tmod
|
|
└── MagicStorage.tmod
|
|
```
|
|
|
|
`enabled.json` lists which mods to load, using each file's name without `.tmod`. Every mod's dependencies must also be present and listed. Mod settings live in `data/ModConfigs/`.
|
|
|
|
You don't need to edit these by hand: `terractl mods` manages them and sets the file ownership correctly (see [Managing mods](#managing-mods)). The `Mods` folder is created automatically for tModLoader instances.
|
|
|
|
Players don't need to install mods themselves: tModLoader offers to download the server's mods when they join.
|
|
|
|
### Exporting mods from Windows
|
|
|
|
tModLoader on Windows keeps its files in these places:
|
|
|
|
| What | Where |
|
|
|---|---|
|
|
| Which mods are enabled | `Documents\My Games\Terraria\tModLoader\Mods\enabled.json` |
|
|
| Workshop mods | `<Steam library>\steamapps\workshop\content\1281930\<id>\` |
|
|
| Manually installed mods | `Documents\My Games\Terraria\tModLoader\Mods\` |
|
|
| Mod settings | `Documents\My Games\Terraria\tModLoader\ModConfigs\` |
|
|
|
|
Workshop mods often contain **several builds**, in folders named after the tModLoader version they target (`2024.3`, `2025.3`, ...). The game loads the build from the highest folder that isn't newer than the running tModLoader.
|
|
|
|
`export-tmod-mods.ps1` applies the same rule. It's installed on the server at `/usr/share/terraria-server-manager/`; copy it to your PC first. It collects every enabled mod, a matching `enabled.json` and your mod settings into `Desktop\tmod-export`, laid out like the server's `data/` folder. Run it in PowerShell:
|
|
|
|
```powershell
|
|
powershell -ExecutionPolicy Bypass -File .\export-tmod-mods.ps1 -MaxVersion 2026.7
|
|
```
|
|
|
|
Set `-MaxVersion` to your tModLoader version as year.month. The full version is shown at the bottom-left of tModLoader's main menu, e.g. `v2026.07.3.0` → `2026.7`. For the preview branch, add `-TmlDir "$HOME\Documents\My Games\Terraria\tModLoader-preview"`.
|
|
|
|
The script prints which file it chose for each mod, and warns about any it couldn't find. It leaves missing mods out of `enabled.json`, because one missing file would stop the server loading any mods.
|
|
|
|
### Copying mods to the server
|
|
|
|
Copy the export folder to the server. `scp` is built into Windows 10 and 11:
|
|
|
|
```powershell
|
|
scp -r "$HOME\Desktop\tmod-export" you@yourserver:
|
|
```
|
|
|
|
This puts it in your home directory on the server. Then install the mods into the instance:
|
|
|
|
```bash
|
|
sudo terractl mods calamity add ~/tmod-export
|
|
sudo systemctl restart terraria@calamity # only if it's already running
|
|
```
|
|
|
|
`add` copies the `.tmod` files and any `ModConfigs`, gives them to the `terraria` user, and adds them to `enabled.json`. For a new instance, do this **before the first start** so the world is generated with the mods loaded.
|
|
|
|
### Managing mods
|
|
|
|
```bash
|
|
terractl mods <name> # list installed mods and whether each is enabled
|
|
terractl mods <name> add <folder|file.tmod>... # install and enable
|
|
terractl mods <name> disable <Mod>... # keep installed, but don't load
|
|
terractl mods <name> enable <Mod>... # load again
|
|
terractl mods <name> remove <Mod>... # uninstall
|
|
```
|
|
|
|
`add` accepts `.tmod` files, a folder containing them, or an export folder with `Mods/` and `ModConfigs/` subfolders. Mod names are the file names without `.tmod`, e.g. `CalamityModMusic`.
|
|
|
|
Adding a mod that's already installed replaces it, which is how you update a mod. Changes take effect when the server restarts; the command reminds you if it's running.
|
|
|
|
The list also flags mods that are **enabled but missing**, because the server won't load any mods while one is missing.
|
|
|
|
`add` doesn't check dependencies, so remember to add them too. If one is missing, `server/tModLoader-Logs/server.log` says which.
|
|
|
|
---
|
|
|
|
## Versions and upgrades
|
|
|
|
### Players must match the server's version
|
|
|
|
- **Vanilla:** players need the same Terraria version as the server's zip. For example, `terraria-server-1458.zip` is Terraria 1.4.5.8.
|
|
- **tModLoader:** players need exactly the same tModLoader version, including the same branch (stable or preview). Steam updates players' tModLoader automatically, usually monthly, so a server that was matching can become mismatched after an update.
|
|
|
|
Stable tModLoader and the latest vanilla Terraria can be based on different Terraria versions. That's fine, because every instance runs independently.
|
|
|
|
### Upgrading (or downgrading) an instance
|
|
|
|
Put the new zip in `/var/lib/terraria/dist`, with the version in its name (see [Server zips](#server-zips)). To see which zips an instance can use and which one it runs:
|
|
|
|
```
|
|
$ terractl upgrade calamity
|
|
'calamity' (tmodloader) runs: tModLoader-v2026.06.1.0.zip
|
|
ZIP NOTE
|
|
tModLoader-v2026.06.1.0.zip installed, pinned
|
|
tModLoader-v2026.07.3.0.zip newest
|
|
Change version with: sudo terractl upgrade calamity <zip>|latest
|
|
```
|
|
|
|
Then switch to it, by name or with `latest` (the newest zip of the instance's type):
|
|
|
|
```bash
|
|
sudo terractl upgrade calamity tModLoader-v2026.07.3.0.zip
|
|
sudo terractl upgrade calamity latest
|
|
```
|
|
|
|
`upgrade` applies the change right away, in this order:
|
|
|
|
1. **Checks the zip** before changing anything: it must be in `dist/`, match the instance's type, be readable by the `terraria` user, contain the server, and pass `unzip -t`. A damaged or half-downloaded zip is refused here, not on the next start.
|
|
2. **Stops the server** if it's running. Stopping saves the world.
|
|
3. **Backs up** the worlds and configs (not `server/`) to `/var/lib/terraria/instances/.backups/<name>-<date>-<old zip>.tar.gz`, readable only by root. Use `--no-backup` to skip this.
|
|
4. **Pins `ZIP=`** in `instance.conf` to the new zip, so the version can't change by accident afterwards.
|
|
5. **Unpacks the new zip** into `server/`. For tModLoader, the .NET runtime and logs are kept, as always. If this fails, the old pin is put back and the old `server/` is still in place, so the instance is exactly as it was.
|
|
6. **Starts the server again** if it was running.
|
|
|
|
Nothing in `data/` is changed by `upgrade` itself. But once the new version saves a world, older versions can't open it.
|
|
|
|
**Downgrades need `--downgrade`.** `upgrade` compares the version in the zip names, and refuses to go to an older one without it:
|
|
|
|
```bash
|
|
sudo terractl upgrade calamity tModLoader-v2026.06.1.0.zip --downgrade
|
|
```
|
|
|
|
Downgrading only changes the server software. If worlds have already been saved by the newer version, restore them from the backup as well (see [Undoing an upgrade](#undoing-an-upgrade)).
|
|
|
|
**Pin `ZIP=` on modded instances.** `upgrade` always pins, and pinned instances never change version by accident. New tModLoader versions can break older mods, so only upgrade a modded server once its mods support the new version. After upgrading, check that the mods load with `terractl watch calamity`.
|
|
|
|
You can still change version by hand: set `ZIP=` in `instance.conf` and restart. That skips the checks and the backup, and a bad zip only shows up when the server fails to start.
|
|
|
|
### Undoing an upgrade
|
|
|
|
To go back to the old version *and* the worlds as they were before the upgrade, replace the instance with the backup `upgrade` made. The backup is in the same format as `export`, so `import` restores it, pinned to the version it was taken with:
|
|
|
|
```bash
|
|
sudo ls /var/lib/terraria/instances/.backups/
|
|
sudo terractl delete calamity -y # keeps its own backup in .deleted/, just in case
|
|
sudo terractl import calamity /var/lib/terraria/instances/.backups/calamity-20261002-213553-tModLoader-v2026.06.1.0.tar.gz
|
|
sudo terractl enable calamity --now
|
|
```
|
|
|
|
Anything played since the upgrade is lost this way. The backups are never deleted automatically; remove old ones with `sudo rm`.
|
|
|
|
### Re-unpacking the same version
|
|
|
|
If `server/` gets damaged or you want a clean copy:
|
|
|
|
```bash
|
|
sudo terractl reinstall calamity
|
|
sudo terractl restart calamity
|
|
```
|
|
|
|
`reinstall` unpacks the **same** zip again on the next start; it never changes version. If `ZIP=` is blank, it first pins the zip the instance runs, because otherwise the next start would pick the newest zip in `dist/`. If that zip is no longer in `dist/`, it says so and changes nothing: put the zip back, or use `upgrade`.
|
|
|
|
For tModLoader, the downloaded .NET runtime and `tModLoader-Logs/` are kept. If you suspect .NET itself is damaged, use `--clean` to delete them too, so .NET is downloaded again on the next start:
|
|
|
|
```bash
|
|
sudo terractl reinstall calamity --clean
|
|
sudo terractl restart calamity
|
|
```
|
|
|
|
---
|
|
|
|
## Deleting, renaming and restoring
|
|
|
|
```bash
|
|
sudo terractl delete calamity # asks you to type the name
|
|
sudo terractl delete calamity -y # no prompt
|
|
sudo terractl delete calamity -y --no-backup # permanently, no backup
|
|
```
|
|
|
|
This stops and disables the service, then removes the whole instance directory. Unless you pass `--no-backup`, it first archives the configs and `data/` to `/var/lib/terraria/instances/.deleted/<name>-<timestamp>.tar.gz`. `server/` is left out of the archive because it can be recreated from the zip.
|
|
|
|
To restore an instance, import its backup. You can give it back its old name, or a new one:
|
|
|
|
```bash
|
|
sudo terractl import calamity /var/lib/terraria/instances/.deleted/calamity-20260928-134422.tar.gz
|
|
sudo terractl enable calamity --now # server/ is unpacked again on start
|
|
```
|
|
|
|
`.deleted/` is never cleaned up automatically. Remove old archives yourself.
|
|
|
|
To give an instance another name, use `rename`. It stops the server if it's running, renames the folder, updates the paths in `serverconfig.txt`, and starts it again under the new name. The port, world, mods and unpacked server stay as they are, and if it started at boot, it still does:
|
|
|
|
```bash
|
|
sudo terractl rename calamity calamity-old
|
|
```
|
|
|
|
---
|
|
|
|
## Exporting and importing
|
|
|
|
`export` packs an instance into a single `.tar.gz` file, and `import` turns such a file back into an instance. Use them for backups, for moving a server to another machine, or for making a copy to experiment on.
|
|
|
|
```bash
|
|
sudo terractl export calamity # ./calamity-<date>-<time>.tar.gz
|
|
sudo terractl export calamity /root/backups/cal.tar.gz # choose the file
|
|
sudo terractl export calamity --with-server # also include server/ (see below)
|
|
|
|
sudo terractl import calamity2 calamity-20260928-1830.tar.gz
|
|
```
|
|
|
|
### What an export contains
|
|
|
|
It contains `instance.conf`, `serverconfig.txt` and all of `data/`: worlds (`.wld` and `.twld`), their `.bak` copies, mods, `enabled.json`, mod configs and the ban list. It also includes a small `terractl-export.txt`, which records where the export came from, the server type, and the server zip it was running.
|
|
|
|
`server/` is left out by default, because it can be recreated from the zip in `dist/`. `--with-server` includes it, together with the .NET runtime tModLoader downloaded. That makes the archive much larger, but an import can then start without a zip and without internet access.
|
|
|
|
**If the server is running**, `export` sends it `save` and waits until the world files have been written, which usually takes a few seconds. A server started by hand with `terractl run` can't be asked to save that way, so `export` refuses; stop it first.
|
|
|
|
**The archive is readable only by you** (mode 600), because `serverconfig.txt` can contain the server password. Keep that in mind before sharing an export.
|
|
|
|
### What an import does
|
|
|
|
`import <name> <file>` creates a new instance called `<name>`. It never overwrites an existing one, so pick a free name or delete the old instance first. Before extracting anything, it checks the archive: it must hold one instance folder, and nothing that could land outside it (no absolute or `../` paths, no links or special files).
|
|
|
|
After extracting, it adjusts the instance for its new home:
|
|
|
|
- **Paths.** The paths in `serverconfig.txt` (`world=`, `worldpath=`, `banlist=`) are updated for the new name and location.
|
|
- **Port.** If the port is already used by another instance, a free port is assigned and printed.
|
|
- **Server version.** If `ZIP=` in `instance.conf` is blank, it's set to the zip the world was exported with, so the server runs the same Terraria or tModLoader version as before. That matters for mods. Make sure that zip is in `/var/lib/terraria/dist`; `import` warns you if it isn't.
|
|
- **Ownership.** All files are given to the `terraria` user.
|
|
|
|
`import` also accepts:
|
|
|
|
- the backups that `delete` makes (see [Deleting, renaming and restoring](#deleting-renaming-and-restoring));
|
|
- a plain archive of an instance folder, including one from the old `/opt/terraria` manual setup, e.g. `tar -czf old.tar.gz -C /opt/terraria calamity`.
|
|
|
|
### Moving a server to another machine
|
|
|
|
If the new machine runs the daemon, `sudo terractl move calamity newhost:` does all of this in one step; see [Managing other hosts](#managing-other-hosts). By hand:
|
|
|
|
```bash
|
|
# old machine
|
|
sudo terractl export calamity /tmp/calamity.tar.gz
|
|
scp /tmp/calamity.tar.gz newhost:/tmp/
|
|
|
|
# new machine (with terraria-server-manager installed, and the same zip in dist/)
|
|
sudo terractl import calamity /tmp/calamity.tar.gz
|
|
sudo terractl enable calamity --now
|
|
```
|
|
|
|
---
|
|
|
|
## Managing other hosts
|
|
|
|
One computer can manage the Terraria servers of several hosts. A host that should be managed runs **the daemon**, `terractld`. The computers that manage it add it as a **remote**, as with LXD or Incus remotes. Then any command can be run there with `--host`, and instances can be copied and moved between hosts:
|
|
|
|
```bash
|
|
terractl --host nas list
|
|
terractl --host nas attach calamity
|
|
sudo terractl move calamity nas: # this host → nas
|
|
terractl copy nas:calamity pi:test # nas → pi, as 'test'
|
|
```
|
|
|
|
### Setting it up
|
|
|
|
On the host to be managed:
|
|
|
|
```bash
|
|
sudo terractl daemon enable
|
|
```
|
|
|
|
It prints a command with a one-time token. Run that command on the computer that should manage the host:
|
|
|
|
```bash
|
|
terractl remote add tctl1_eyJ2IjoxLCJmcCI6...
|
|
```
|
|
|
|
That's all: the host is now a remote called by its hostname (say `nas`), and `terractl --host nas list` works. To call it something else, put a name before the token: `terractl remote add nas tctl1_...`. The host, in turn, knows the computer by the computer's hostname.
|
|
|
|
For each further computer, run `sudo terractl daemon token` on the host for a new token. A token works **once**, **within 24 hours**.
|
|
|
|
### The daemon
|
|
|
|
`daemon enable` starts `terractld.service` and makes it start at boot. It listens on **TCP port 7776**, on all addresses. If a firewall is active, allow that port (for example `sudo ufw allow 7776/tcp`), ideally only from the computers that will manage the host. To listen on one address or on another port, set `TERRACTL_LISTEN` in `/etc/default/terractl` (e.g. `TERRACTL_LISTEN=10.0.10.77:7776`) and run `sudo systemctl restart terractld`.
|
|
|
|
`sudo terractl daemon disable` turns it off again. `terractl daemon status` shows whether it's running, where it listens and its certificate's fingerprint, and with `sudo`, which computers it trusts. `sudo terractl daemon revoke <name>` takes a computer's access away.
|
|
|
|
The daemon is an ordinary systemd service: systemd starts it at boot, restarts it if it crashes, and keeps its log. `journalctl -u terractld` lists every command it ran and for which computer.
|
|
|
|
### Remotes
|
|
|
|
The token contains the host's addresses, so usually `remote add` needs nothing else. If none of them works from the computer (behind NAT, for example), add one that does: `terractl remote add tctl1_... --address 203.0.113.5`, or `--address nas.example.com:7800` for another port.
|
|
|
|
Adding a host that's already a remote replaces that remote, keeping its name unless you give a new one. So if a remote stops working because its access was revoked, just get a new token and add it again.
|
|
|
|
Remotes are kept per user in `~/.config/terractl/remotes.json`, readable only by you. Under `sudo`, the remotes of the person who ran sudo are used.
|
|
|
|
```bash
|
|
terractl remote list # your remotes
|
|
terractl remote remove nas # forget one
|
|
```
|
|
|
|
### Running commands on a remote: `--host`
|
|
|
|
Put `--host <remote>` in front of any command:
|
|
|
|
```bash
|
|
terractl --host nas list
|
|
terractl --host nas new calamity tmodloader
|
|
terractl --host nas mods calamity add ~/tmod-export # sends the files
|
|
terractl --host nas enable calamity --now
|
|
terractl --host nas attach calamity
|
|
terractl --host nas export calamity # fetches ./calamity-<date>-<time>.tar.gz
|
|
terractl --host nas import calamity2 calamity.tar.gz # sends the archive
|
|
```
|
|
|
|
`<remote>` is a remote's name, or the address it was added with (`--host 10.0.10.77`). No `sudo` is needed on either side: the daemon runs each command as root on its host, as if it had been typed there with `sudo`. File arguments (`mods add`, `import`, `export`) are files on *your* computer; they're sent to the host or fetched from it. Messages that suggest a command show it as `terractl --host nas ...`.
|
|
|
|
Not available with `--host`:
|
|
|
|
- `remote`, `daemon`, `copy` and `move` run on your computer (copy and move take `<remote>:<name>` instead).
|
|
- The service's own commands (`prepare`, `run`, `service-stop`, `journal-access`).
|
|
- Editing `serverconfig.txt` still needs a shell on that host.
|
|
|
|
### Copying and moving instances
|
|
|
|
```bash
|
|
terractl copy calamity nas: # to nas, same name
|
|
sudo terractl copy nas:calamity cal-test # from nas to this host, as cal-test
|
|
terractl copy nas:calamity pi: # between two remotes
|
|
sudo terractl move calamity nas: # to nas; removed here
|
|
terractl move nas:calamity pi: # between two remotes
|
|
sudo terractl move calamity survival2 # rename, on this host (same as rename)
|
|
```
|
|
|
|
Each side is `[<remote>:]<name>`, and without a remote it's this host. Leave out the destination name (`nas:`) to keep the name.
|
|
|
|
A copy is an [export](#exporting-and-importing) on the source, a transfer, and an import on the destination, so everything import does applies. Paths are set to the **destination's** folders, whatever its `TERRARIA_BASE` is: they're asked from the destination, never assumed. A port that's taken there is replaced with a free one, and the server version is pinned.
|
|
|
|
- **The server zip comes along if needed.** If the destination's `dist/` doesn't have the zip the instance runs, it's copied from the source's `dist/`. If the source doesn't have it either, nothing is changed and you're told so; put the zip on the destination first, or use `--with-server` to take the unpacked server along.
|
|
- **Copy leaves the source running.** A running server is asked to save first, so the copy has the world as of that save. Anything that happens in the game after it isn't included. The copy isn't started.
|
|
- **Move within one host is a rename** (`sudo terractl move calamity calamity-old`, or `nas:a nas:b`): nothing is copied, and the port and unpacked server stay the same. See [Deleting, renaming and restoring](#deleting-renaming-and-restoring).
|
|
- **Move stops the source first**, which saves the world, and removes it from the source only after the import worked. `delete`'s usual backup is kept in the source's `.deleted/`. If the source was running, the moved instance is started on the destination, and if it started at boot, it does there too. If something fails before the import has worked, the source is started again and nothing else changes.
|
|
- **Transfers go through the computer you run the command on.** Between two remotes, the archive streams from one to the other through your computer without being stored on it.
|
|
|
|
On this host, copying *from* it works for members of the `terraria` group. Copying *to* it, and moving away from it, needs `sudo`, like `import` and `delete` do.
|
|
|
|
### Security
|
|
|
|
- **Encrypted and pinned.** Traffic is HTTPS (TLS 1.2 or newer). The daemon's certificate is self-signed, the token carries its fingerprint, and every connection checks it, so nothing in between can pose as the daemon. If the fingerprint ever changes, `terractl` refuses to connect and says so.
|
|
- **Keys, not passwords.** Joining trades the token for a random key for that computer. The daemon keeps only hashes of keys and tokens, in `/var/lib/terractl/trust.json` (root only).
|
|
- **A trusted computer is an admin of the host's servers.** It can do what `sudo terractl` can: create, delete, import, start and stop instances, and install mods or server files, which run as the `terraria` user. It can't change the daemon or run anything other than `terractl`. Give tokens only to computers you'd trust with `sudo terractl`.
|
|
- **Revoking.** `sudo terractl daemon status` shows the trusted computers, and `sudo terractl daemon revoke laptop` takes one's access away immediately.
|
|
- **Firewall.** The daemon doesn't need to be reachable from the internet. Limit port 7776 to your LAN or VPN, or set `TERRACTL_LISTEN` to a LAN or VPN address.
|
|
|
|
The daemon runs as root, because it creates and removes instances and starts services, but with systemd's protections it can't write to `/usr`, `/boot`, `/etc` or home folders. Its certificate, key and trust list are in `/var/lib/terractl/`. Files in transit are kept in `/var/lib/terractl/transfers/`, and removed once used, after 6 hours unused, or when the daemon restarts.
|
|
|
|
---
|
|
|
|
## Backups
|
|
|
|
Everything worth keeping is in `data/`, `instance.conf` and `serverconfig.txt`, which is exactly what `export` saves. A nightly backup kept for 14 days, as a root cron job (`sudo crontab -e`):
|
|
|
|
```cron
|
|
30 4 * * * /usr/bin/terractl export calamity /var/backups/terraria/calamity-$(date +\%F).tar.gz && find /var/backups/terraria -name 'calamity-*.tar.gz' -mtime +14 -delete
|
|
```
|
|
|
|
Create the folder first with `sudo mkdir -p /var/backups/terraria`. Separately, `upgrade` backs up an instance's worlds and configs before every version change (see [Undoing an upgrade](#undoing-an-upgrade)). In a crontab, `%` has a special meaning and must be written as `\%`, as above. Terraria also keeps a `.bak` copy of each world next to the world file, which is useful if the latest save is damaged.
|
|
|
|
---
|
|
|
|
## Permissions
|
|
|
|
The servers always run as the `terraria` user. Administration is simplest with `sudo`.
|
|
|
|
| Command | Who can run it | Notes |
|
|
|---|---|---|
|
|
| `new` | root (or `terraria`) | Run as root, it sets ownership of the new instance to `terraria` itself. |
|
|
| `list`, `info` | root, or members of the `terraria` group | `/var/lib/terraria` is not readable by other users. |
|
|
| `start`, `stop`, `restart` | root, or members of the `terraria` group | Through systemd, where the polkit rule allows the group (see `systemctl start/stop/restart` below). |
|
|
| `enable`, `disable` | root only | Checked by the script. systemd can't restrict these to Terraria servers, so the group can't be allowed them. |
|
|
| Adding zips to `dist/` | root, or members of the `terraria` group | |
|
|
| `attach` | root, or members of the `terraria` group | Writes to the console pipe and reads the servers' journal. |
|
|
| `cmd` | root, or members of the `terraria` group | Same as `attach`. |
|
|
| `watch` | root, or members of the `terraria` group | Reads the servers' journal only. |
|
|
| `upgrade <name>` (list) | root, or members of the `terraria` group | |
|
|
| `upgrade <name> <zip>` | root only | Checked by the script. Stops and starts the server, and unpacks the zip as `terraria`. |
|
|
| `reinstall` | root (or `terraria`) | |
|
|
| `fix-perms` | root only | Checked by the script. |
|
|
| `mods <name>` (list) | root, or members of the `terraria` group | |
|
|
| `mods add/enable/disable/remove` | root (or `terraria`) | Run as root, it sets ownership of the files it installs to `terraria`. |
|
|
| `delete`, `rename` | root only | Checked by the script. |
|
|
| `export` | root, or members of the `terraria` group | Reads the instance, and asks a running server to save. |
|
|
| `import` | root (or `terraria`) | Run as root, it gives the new instance to `terraria`. |
|
|
| `prepare`, `run` | Normally only the service | Run as root, they automatically switch to the `terraria` user, so no files end up owned by root. Any other user is refused. |
|
|
| `service-stop` | Only the service | Needs the server's process ID from systemd. |
|
|
| `daemon enable`, `disable`, `token`, `revoke` | root only | |
|
|
| `daemon status` | anyone | Shows the trusted computers only with `sudo`. |
|
|
| `remote ...`, `--host ...` | anyone | Uses your own remotes. On the remote, the daemon runs the command as root. |
|
|
| `copy`, `move` | depends on this host's part | Copying from this host: the group is enough. Copying to it, or moving away from it: root. |
|
|
| `systemctl start/stop/restart terraria@...` | root, or members of the `terraria` group | Allowed by the polkit rule `50-terractl.rules`. Needs `polkitd` 0.106 or newer, so not on Ubuntu 22.04, where it needs `sudo`. Starting a name that has no instance creates a vanilla one with default settings. |
|
|
| `systemctl enable/disable terraria@...` | root | Same as `terractl enable`/`disable`. |
|
|
| `journalctl --namespace=terraria ...` | root, or members of the `terraria` group | The servers' output only; see [Logs](#logs). |
|
|
| `journalctl -u terraria@...` (system journal) | root, or members of `systemd-journal` or `adm` | systemd's start/stop messages. |
|
|
|
|
To run the everyday commands without `sudo`:
|
|
|
|
```bash
|
|
sudo usermod -aG terraria youruser # then log out and back in
|
|
```
|
|
|
|
Members of the `terraria` group can read server files (useful for backups) but cannot change them. Files the service creates are readable, not writable, by the group.
|
|
|
|
### Avoiding root-owned files
|
|
|
|
The helper commands don't leave root-owned files behind, and `mods add` handles mods and ModConfigs. The remaining risk is **copying or creating other files in an instance directory as root**, such as worlds. The server can't write to root-owned files, so saving fails or tModLoader errors. After copying anything in, run:
|
|
|
|
```bash
|
|
sudo terractl fix-perms <name>
|
|
```
|
|
|
|
This gives every file of the instance back to the `terraria` user and resets the modes to what the service creates: the `terraria` group can read everything but change nothing, and other users get no access. Files that were executable stay executable (and the server binaries are made executable again); setuid bits are removed. Symbolic links are left alone, so nothing outside the instance is changed. It's safe while the server is running.
|
|
|
|
Alternatively, copy files as the `terraria` user to begin with:
|
|
|
|
```bash
|
|
sudo -u terraria cp MyWorld.wld /var/lib/terraria/instances/calamity/data/Worlds/
|
|
```
|
|
|
|
Editing an existing file with `sudo nano` keeps its owner, so it's fine.
|
|
|
|
---
|
|
|
|
## Running by hand for debugging
|
|
|
|
Stop the service first. `terractl` won't let two copies of the same server run at once: if the service is running, `run` refuses and tells you to stop it.
|
|
|
|
```bash
|
|
sudo terractl stop calamity
|
|
sudo terractl run calamity # automatically runs as 'terraria'
|
|
```
|
|
|
|
The server runs in your terminal and you can type commands into it directly. Type `exit` to stop it, then start the service again:
|
|
|
|
```bash
|
|
sudo terractl start calamity
|
|
```
|
|
|
|
### How running servers are protected
|
|
|
|
While a server runs, whether systemd started it or you did, it holds a lock on the file `<instance>/.lock`. The lock is released automatically when the server exits, even if it crashes, so it can never be left behind. While the lock is held:
|
|
|
|
- **A second `run` is refused**, and so is starting the service while a copy started by hand is running. In that case the service keeps retrying every 10 seconds and starts by itself once the other copy exits.
|
|
- **`prepare` is refused**, so `server/` can't be replaced under the running server.
|
|
- **`reinstall --clean` is refused**, so `.NET` can't be deleted under it. Plain `reinstall` is allowed, because it only takes effect at the next start.
|
|
- **`upgrade` stops the service first**, and refuses if a copy started by hand is running.
|
|
- **`delete` stops the service**, but refuses to go on if a copy started by hand is still running.
|
|
- **`list` shows a copy started by hand** as `running (outside systemd)`.
|
|
|
|
`terractl stop` only stops the service. A copy started by hand is stopped by typing `exit` in its terminal.
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
**tModLoader stops with `Aborted` right after "Launched Using Local Dotnet".**
|
|
The ICU library is missing, so .NET aborts at startup. Fix it with `sudo apt install libicu-dev`, then restart the instance. To confirm, check that `ldconfig -p | grep -c libicu` prints more than 0.
|
|
|
|
**`ls: cannot access '.../dotnet/shared/Microsoft.NETCore.App'` on the first start.**
|
|
Harmless. tModLoader looks for old .NET versions to clean up before any have been installed.
|
|
|
|
**The first tModLoader start fails while downloading .NET.**
|
|
The server needs internet access and `curl` on its first start. See `server/tModLoader-Logs/Launch.log` for the exact error.
|
|
|
|
If the host has no internet access, you can copy the `dotnet` folder from another tModLoader instance that runs the same tModLoader version. Unpack the new instance first, then copy the folder in before starting it:
|
|
```bash
|
|
sudo terractl prepare <name> # unpacks server/ without starting
|
|
sudo cp -a /var/lib/terraria/instances/other/server/dotnet /var/lib/terraria/instances/<name>/server/
|
|
sudo systemctl start terraria@<name>
|
|
```
|
|
|
|
**The server can't save, or you see "Permission denied" errors.**
|
|
Some files are owned by root, or have the wrong modes. Run `sudo terractl fix-perms <name>`.
|
|
|
|
**Players can't join a tModLoader server with a version-mismatch message.**
|
|
The player's tModLoader version differs from the server's. See [Versions and upgrades](#versions-and-upgrades).
|
|
|
|
**Mods don't load.**
|
|
Run `terractl mods <name>` to check for mods that are enabled but missing. Then check `server/tModLoader-Logs/server.log` for the cause. Common ones:
|
|
- a dependency is missing from `data/Mods/`;
|
|
- a mod name in `enabled.json` doesn't match its `.tmod` file name;
|
|
- a mod build was made for a newer tModLoader than the server (re-export with `-MaxVersion`);
|
|
- the files are owned by root.
|
|
|
|
**The server fails with "address already in use".**
|
|
Two instances share a port. Check the ports with `sudo terractl list`. A second copy of the *same* instance can't be the cause, because `terractl` prevents it.
|
|
|
|
**"already running outside systemd" or "is active; stop it first".**
|
|
The instance's server is already running, so the command was refused to protect it. Stop the server first. If it was started with `terractl run` in another terminal, type `exit` there; otherwise use `terractl stop <name>`.
|
|
|
|
**The service starts but you can't connect.**
|
|
Check the port in `serverconfig.txt`, your firewall (TCP), and router port forwarding.
|
|
|
|
**`attach` or `cmd` sends commands but shows no output.**
|
|
Both commands print a note explaining why. If you were just added to the `terraria` group, log out and back in. If you're already in it, the servers' journal hasn't been set up for the group yet; that happens automatically when a server starts, or run `sudo terractl journal-access`. Servers that were running before upgrading to 0.0.11 keep logging to the system journal until they're restarted.
|
|
|
|
**The server isn't restarting after it stopped.**
|
|
It exited cleanly, probably via `exit` in the console, so systemd leaves it stopped. Start it again with `terractl start`.
|
|
|
|
**The world was created without mod content.**
|
|
It was generated before the mods were added. Delete the world files in `data/Worlds/` (or point `world=` at a new name) and restart to generate a new one.
|
|
|
|
**`can't reach nas at 10.0.10.77:7776`.**
|
|
The daemon isn't running on that host (check with `terractl daemon status` there), a firewall blocks the port, or the host's address changed. After an address change, remove the remote and add it again with a new token.
|
|
|
|
**`isn't the one 'nas' was added with: its certificate changed`.**
|
|
The daemon has a different certificate than when you added it. That's expected if `/var/lib/terractl` was deleted there, or the package was purged and installed again; remove the remote and add it again. If nothing like that happened, don't work around it: something may be intercepting the connection.
|
|
|
|
**`doesn't trust this computer (any more)`.**
|
|
Its access was revoked with `daemon revoke`, or the host's `/var/lib/terractl` was reset. Run `sudo terractl daemon token` there and `terractl remote add <token>` here; that replaces the old remote.
|
|
|
|
---
|
|
|
|
## Tab completion
|
|
|
|
`terractl` comes with bash completion. Press Tab to complete:
|
|
|
|
- command names;
|
|
- instance names;
|
|
- `vanilla`/`tmodloader` for `new`;
|
|
- common server console commands for `cmd` (`save`, `say`, `playing`, `kick`...);
|
|
- the options of `enable`, `disable`, `watch`, `upgrade`, `reinstall`, `delete`, `export`, `copy` and `move`;
|
|
- server zips for `upgrade`, only those of the instance's type (on a remote, all of its zips);
|
|
- remote names after `--host`, and `<remote>:<name>` for `copy` and `move`. Instance names on a remote are asked from its daemon, so they can take a moment;
|
|
- `.tar.gz` files for `export` and `import`;
|
|
- `mods` actions. For `mods add` it completes `.tmod` files and folders; for `mods enable` and `mods remove`, the installed mods; for `mods disable`, the enabled ones.
|
|
|
|
It works with `sudo terractl` too. It needs the `bash-completion` package, which most Debian and Ubuntu systems already have (`sudo apt install bash-completion`). It takes effect in new terminal sessions.
|
|
|
|
Completion runs as *you*, even when you type `sudo`. Instance and mod names therefore only complete if you can read `/var/lib/terraria`, which means being root or a member of the `terraria` group (see [Permissions](#permissions)). Otherwise, it falls back to the names of servers systemd currently knows about, and mod names don't complete.
|
|
|
|
---
|
|
|
|
## Command reference
|
|
|
|
```
|
|
terractl new <name> [vanilla|tmodloader] [port] Create an instance (not started)
|
|
terractl list Show instances, type, port, boot, state
|
|
terractl info [<name>] This host's paths and zips, or an instance's details
|
|
terractl start|stop|restart <name> Start, stop (saves the world) or restart
|
|
terractl enable <name> [--now] Start at boot (--now: and start now)
|
|
terractl disable <name> [--now] Don't start at boot (--now: and stop now)
|
|
terractl attach <name> Interactive console (Ctrl-D detaches)
|
|
terractl watch <name> [-n <lines>] [-q] Follow the output, read-only (Ctrl-C quits)
|
|
terractl cmd <name> <command...> Send one console command
|
|
terractl upgrade <name> Show the zips it can use, and which it runs
|
|
terractl upgrade <name> <zip>|latest [--downgrade] [--no-backup]
|
|
Change server version now (backs up first)
|
|
terractl reinstall <name> [--clean] Re-unpack the same version on next start
|
|
(--clean also re-downloads .NET)
|
|
terractl fix-perms <name> Reset the owner and modes of all its files
|
|
terractl mods <name> [list] Show installed mods
|
|
terractl mods <name> add <folder|file.tmod>... Install and enable mods (and ModConfigs)
|
|
terractl mods <name> enable|disable <Mod>... Change which mods load
|
|
terractl mods <name> remove <Mod>... Uninstall mods
|
|
terractl delete <name> [-y] [--no-backup] Stop, disable, back up and remove
|
|
terractl rename <name> <new name> Rename (restarted if it was running)
|
|
terractl export <name> [file.tar.gz] [--with-server]
|
|
Archive an instance (configs, worlds, mods)
|
|
terractl import <name> <file.tar.gz> Create an instance from an archive
|
|
terractl --version Show the installed version
|
|
|
|
Other hosts:
|
|
terractl --host <remote> <command> [args] Run a command on a remote
|
|
terractl copy [<remote>:]<name> [<remote>:][<new name>] [--with-server]
|
|
Copy an instance, here or between hosts
|
|
terractl move [<remote>:]<name> [<remote>:][<new name>] [--with-server]
|
|
Move or rename an instance
|
|
terractl remote add [<name>] <token> [--address <host>[:<port>]]
|
|
Add (or replace) a remote
|
|
terractl remote list | remove <name> Show or forget remotes
|
|
terractl daemon enable|disable|status Let other hosts manage this one
|
|
terractl daemon token A one-time token for another computer
|
|
terractl daemon revoke <name> Take a computer's access away
|
|
|
|
Used by the service:
|
|
terractl prepare <name> Create dirs/configs, unpack zip if needed
|
|
terractl run <name> Run the server
|
|
terractl service-stop <name> <pid> Save and shut down via the console (service only)
|
|
terractl daemon serve Run the daemon (terractld.service)
|
|
```
|
|
|
|
```
|
|
systemctl start|stop|restart|status terraria@<name>
|
|
systemctl enable|disable [--now] terraria@<name>
|
|
journalctl --namespace=terraria -u terraria@<name> [-f] [-n N]
|
|
```
|