remotes
This commit is contained in:
1 parent
4f0886b6cd
commit
02017d470f
16 files changed
+2665
-84
No files matched your search
@@ -6,6 +6,24 @@
|
|||||||
- New `/etc/systemd/journald@terraria.conf` for the servers' journal size and retention (default: up to 256 MB).
|
- New `/etc/systemd/journald@terraria.conf` for the servers' journal size and retention (default: up to 256 MB).
|
||||||
- New internal `terractl journal-access`, run as root before each server start: creates the servers' journal folder (never `/var/log/journal` itself) and gives the `terraria` group read access, repairing existing journal files too.
|
- New internal `terractl journal-access`, run as root before each server start: creates the servers' journal folder (never `/var/log/journal` itself) and gives the `terraria` group read access, repairing existing journal files too.
|
||||||
- The note shown by `attach` and `cmd` when output can't be read now distinguishes "not in the `terraria` group" from "in it, but the journal isn't set up yet".
|
- The note shown by `attach` and `cmd` when output can't be read now distinguishes "not in the `terraria` group" from "in it, but the journal isn't set up yet".
|
||||||
|
- New polkit rule (`/usr/share/polkit-1/rules.d/50-terractl.rules`): members of the `terraria` group can `systemctl start`, `stop` and `restart` `terraria@<name>` without `sudo`. `enable`, `disable` and all other units still need root. Needs `polkitd` 0.106 or newer, now a recommended package; Ubuntu 22.04's polkit can't run the rule.
|
||||||
|
- New `enable` and `disable` commands: `terractl enable <name> [--now]` makes a server start at boot (`--now` also starts it), `terractl disable <name> [--now]` stops that (`--now` also stops it, saving the world). They need root, like `systemctl enable`. `enable` refuses names without an instance, so a typo can't create a new instance at boot. `new` and `import` now suggest `terractl enable <name> --now`.
|
||||||
|
- `list` has a new `BOOT` column showing whether each server starts at boot (`enabled`, `disabled`, or e.g. `masked`).
|
||||||
|
- Manage other hosts, like LXD/Incus remotes:
|
||||||
|
- `sudo terractl daemon enable` starts `terractld.service`, a daemon other hosts connect to (HTTPS, TCP port 7776; `TERRACTL_LISTEN` in `/etc/default/terractl` changes it). It's off until enabled. `daemon disable` and `daemon status` turn it off and show its state.
|
||||||
|
- `sudo terractl daemon trust add <name>` creates a one-time token for a computer, valid for 24 hours; `daemon trust list` and `daemon trust remove <name>` show and revoke access. On that computer, `terractl remote add <remote> <token>` adds the host; `remote list` and `remote remove` manage remotes, which are stored per user.
|
||||||
|
- `terractl --host <remote> <command>` runs any command on a remote, including `attach`, `watch` and `export`/`import`/`mods add` with files from your computer. The daemon runs it as root there, with messages adjusted to `terractl --host <remote> ...`.
|
||||||
|
- `terractl copy` and `terractl move` copy, move or rename instances, on one host or between hosts (`[<remote>:]<name>`). Paths and ports follow the destination's settings, and a missing server zip is copied along. `move` stops the source first, starts it again if the transfer fails, and starts the moved instance where it was running before.
|
||||||
|
- Security: the token carries the daemon's certificate fingerprint, which the client checks on every connection; each computer gets its own key, and the daemon stores only hashes. The daemon runs as root with systemd sandboxing.
|
||||||
|
- Written in Python (standard library only), in `/usr/lib/terraria-server-manager/terractl-remote`. The package now depends on `python3` and `openssl`.
|
||||||
|
- New `start`, `stop` and `restart` commands, the same as `systemctl start|stop|restart terraria@<name>`, for this host and for `--host`. `start` refuses names that have no instance. The service's own stop step is now `terractl service-stop`; units that still call `terractl stop <name> <pid>` keep working.
|
||||||
|
- New `rename` command: `terractl rename <name> <new name>` renames the folder, updates `serverconfig.txt`, and carries over the service (stopped and started again if it was running, and still starting at boot). The port and unpacked server are kept. `move` within one host uses it.
|
||||||
|
- New `info` command: this host's folders, zips and instances, or an instance's type, port, server zip and state, as `key=value` lines.
|
||||||
|
- `import` replaces a `ZIP=` pin that's an absolute path not present on this host with the zip of the same name in this host's `dist/`.
|
||||||
|
- Messages that suggest a command now suggest `terractl` commands (`terractl stop <name>` rather than `systemctl stop terraria@<name>`), with `sudo` only when it's needed: not in a root shell, and not for `terraria` group members starting, stopping or restarting a server.
|
||||||
|
- New `watch` command: `terractl watch <name> [-n <lines>] [-q]` follows the server's output like `attach`, but never sends input, so it's safe to leave open. Works for the `terraria` group without `sudo`, and while the server is stopped. `-q` leaves out the lines about what it's doing.
|
||||||
|
- Bash completion for the new commands, remote names after `--host`, and `<remote>:<name>` for `copy` and `move`.
|
||||||
|
- Clearer messages when `run` or `prepare` is typed without `sudo` (the old one, "must run as root or 'terraria'", read as if the `terraria` group were enough), and when `stop` is typed by hand.
|
||||||
- Upgrading: running servers keep logging to the system journal until restarted; the package lists the ones to restart.
|
- Upgrading: running servers keep logging to the system journal until restarted; the package lists the ones to restart.
|
||||||
|
|
||||||
## 0.0.10 - 2026-09-30
|
## 0.0.10 - 2026-09-30
|
||||||
|
|||||||
@@ -17,10 +17,18 @@ SYSUSERSDIR := $(PREFIX)/lib/sysusers.d
|
|||||||
TMPFILESDIR := $(PREFIX)/lib/tmpfiles.d
|
TMPFILESDIR := $(PREFIX)/lib/tmpfiles.d
|
||||||
DOCDIR := $(PREFIX)/share/doc/$(NAME)
|
DOCDIR := $(PREFIX)/share/doc/$(NAME)
|
||||||
DATADIR := $(PREFIX)/share/$(NAME)
|
DATADIR := $(PREFIX)/share/$(NAME)
|
||||||
|
PKGLIBDIR := $(PREFIX)/lib/$(NAME)
|
||||||
COMPLETIONDIR := $(PREFIX)/share/bash-completion/completions
|
COMPLETIONDIR := $(PREFIX)/share/bash-completion/completions
|
||||||
|
# polkit only reads /usr/share/polkit-1/rules.d and /etc/polkit-1/rules.d
|
||||||
|
ifeq ($(PREFIX),/usr)
|
||||||
|
POLKITDIR ?= /usr/share/polkit-1/rules.d
|
||||||
|
else
|
||||||
|
POLKITDIR ?= $(SYSCONFDIR)/polkit-1/rules.d
|
||||||
|
endif
|
||||||
|
|
||||||
# Fill in version and install paths
|
# Fill in version and install paths
|
||||||
SUBST = sed -e 's|@VERSION@|$(VERSION)|g' -e 's|@BINDIR@|$(BINDIR)|g' -e 's|@DOCDIR@|$(DOCDIR)|g'
|
SUBST = sed -e 's|@VERSION@|$(VERSION)|g' -e 's|@BINDIR@|$(BINDIR)|g' -e 's|@DOCDIR@|$(DOCDIR)|g' \
|
||||||
|
-e 's|@PKGLIBDIR@|$(PKGLIBDIR)|g'
|
||||||
|
|
||||||
MAINT_SCRIPTS := packaging/deb/postinst packaging/deb/prerm packaging/deb/postrm
|
MAINT_SCRIPTS := packaging/deb/postinst packaging/deb/prerm packaging/deb/postrm
|
||||||
STAGE := build/deb-root
|
STAGE := build/deb-root
|
||||||
@@ -34,22 +42,33 @@ check:
|
|||||||
bash -n bin/terractl
|
bash -n bin/terractl
|
||||||
bash -n completions/bash/terractl
|
bash -n completions/bash/terractl
|
||||||
for f in $(MAINT_SCRIPTS); do sh -n $$f; done
|
for f in $(MAINT_SCRIPTS); do sh -n $$f; done
|
||||||
|
python3 -c 'import ast, sys; ast.parse(open(sys.argv[1]).read(), sys.argv[1])' lib/terractl-remote
|
||||||
@if command -v shellcheck >/dev/null; then \
|
@if command -v shellcheck >/dev/null; then \
|
||||||
shellcheck bin/terractl completions/bash/terractl $(MAINT_SCRIPTS); \
|
shellcheck bin/terractl completions/bash/terractl $(MAINT_SCRIPTS); \
|
||||||
else \
|
else \
|
||||||
echo "shellcheck not installed; skipped (apt install shellcheck)"; \
|
echo "shellcheck not installed; skipped (apt install shellcheck)"; \
|
||||||
fi
|
fi
|
||||||
|
@if python3 -m pyflakes --version >/dev/null 2>&1; then \
|
||||||
|
python3 -m pyflakes lib/terractl-remote; \
|
||||||
|
else \
|
||||||
|
echo "pyflakes not installed; skipped (apt install python3-pyflakes)"; \
|
||||||
|
fi
|
||||||
|
|
||||||
install:
|
install:
|
||||||
install -d $(DESTDIR)$(BINDIR) $(DESTDIR)$(UNITDIR) $(DESTDIR)$(SYSUSERSDIR) \
|
install -d $(DESTDIR)$(BINDIR) $(DESTDIR)$(UNITDIR) $(DESTDIR)$(SYSUSERSDIR) \
|
||||||
$(DESTDIR)$(TMPFILESDIR) $(DESTDIR)$(DOCDIR) $(DESTDIR)$(DATADIR) \
|
$(DESTDIR)$(TMPFILESDIR) $(DESTDIR)$(DOCDIR) $(DESTDIR)$(DATADIR) $(DESTDIR)$(PKGLIBDIR) \
|
||||||
$(DESTDIR)$(COMPLETIONDIR) $(DESTDIR)$(SYSCONFDIR)/default $(DESTDIR)$(SYSCONFDIR)/systemd
|
$(DESTDIR)$(COMPLETIONDIR) $(DESTDIR)$(SYSCONFDIR)/default $(DESTDIR)$(SYSCONFDIR)/systemd \
|
||||||
|
$(DESTDIR)$(POLKITDIR)
|
||||||
$(SUBST) bin/terractl > $(DESTDIR)$(BINDIR)/terractl
|
$(SUBST) bin/terractl > $(DESTDIR)$(BINDIR)/terractl
|
||||||
chmod 755 $(DESTDIR)$(BINDIR)/terractl
|
chmod 755 $(DESTDIR)$(BINDIR)/terractl
|
||||||
|
install -m 755 lib/terractl-remote $(DESTDIR)$(PKGLIBDIR)/terractl-remote
|
||||||
$(SUBST) systemd/terraria@.service > $(DESTDIR)$(UNITDIR)/terraria@.service
|
$(SUBST) systemd/terraria@.service > $(DESTDIR)$(UNITDIR)/terraria@.service
|
||||||
|
$(SUBST) systemd/terractld.service > $(DESTDIR)$(UNITDIR)/terractld.service
|
||||||
|
chmod 644 $(DESTDIR)$(UNITDIR)/terraria@.service $(DESTDIR)$(UNITDIR)/terractld.service
|
||||||
install -m 644 systemd/terraria@.socket $(DESTDIR)$(UNITDIR)/
|
install -m 644 systemd/terraria@.socket $(DESTDIR)$(UNITDIR)/
|
||||||
install -m 644 config/sysusers.d/terractl.conf $(DESTDIR)$(SYSUSERSDIR)/
|
install -m 644 config/sysusers.d/terractl.conf $(DESTDIR)$(SYSUSERSDIR)/
|
||||||
install -m 644 config/tmpfiles.d/terractl.conf $(DESTDIR)$(TMPFILESDIR)/
|
install -m 644 config/tmpfiles.d/terractl.conf $(DESTDIR)$(TMPFILESDIR)/
|
||||||
|
install -m 644 config/polkit/terractl.rules $(DESTDIR)$(POLKITDIR)/50-terractl.rules
|
||||||
install -m 644 docs/guide.md $(DESTDIR)$(DOCDIR)/README.md
|
install -m 644 docs/guide.md $(DESTDIR)$(DOCDIR)/README.md
|
||||||
install -m 644 tools/windows/export-tmod-mods.ps1 $(DESTDIR)$(DATADIR)/
|
install -m 644 tools/windows/export-tmod-mods.ps1 $(DESTDIR)$(DATADIR)/
|
||||||
install -m 644 completions/bash/terractl $(DESTDIR)$(COMPLETIONDIR)/terractl
|
install -m 644 completions/bash/terractl $(DESTDIR)$(COMPLETIONDIR)/terractl
|
||||||
@@ -68,20 +87,27 @@ install:
|
|||||||
@if [ -z "$(DESTDIR)" ]; then \
|
@if [ -z "$(DESTDIR)" ]; then \
|
||||||
systemd-sysusers terractl.conf && \
|
systemd-sysusers terractl.conf && \
|
||||||
systemd-tmpfiles --create terractl.conf && \
|
systemd-tmpfiles --create terractl.conf && \
|
||||||
if [ -d /run/systemd/system ]; then systemctl daemon-reload; fi && \
|
if [ -d /run/systemd/system ]; then \
|
||||||
|
systemctl daemon-reload && systemctl try-restart terractld.service; \
|
||||||
|
fi && \
|
||||||
$(BINDIR)/terractl journal-access && \
|
$(BINDIR)/terractl journal-access && \
|
||||||
echo "Installed terractl $(VERSION). See $(DOCDIR)/README.md"; \
|
echo "Installed terractl $(VERSION). See $(DOCDIR)/README.md"; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
|
@if [ -z "$(DESTDIR)" ] && [ -d /run/systemd/system ]; then \
|
||||||
|
systemctl disable --now terractld.service 2>/dev/null || true; \
|
||||||
|
fi
|
||||||
rm -f $(DESTDIR)$(BINDIR)/terractl \
|
rm -f $(DESTDIR)$(BINDIR)/terractl \
|
||||||
$(DESTDIR)$(UNITDIR)/terraria@.service $(DESTDIR)$(UNITDIR)/terraria@.socket \
|
$(DESTDIR)$(UNITDIR)/terraria@.service $(DESTDIR)$(UNITDIR)/terraria@.socket \
|
||||||
|
$(DESTDIR)$(UNITDIR)/terractld.service \
|
||||||
$(DESTDIR)$(SYSUSERSDIR)/terractl.conf $(DESTDIR)$(TMPFILESDIR)/terractl.conf \
|
$(DESTDIR)$(SYSUSERSDIR)/terractl.conf $(DESTDIR)$(TMPFILESDIR)/terractl.conf \
|
||||||
$(DESTDIR)$(COMPLETIONDIR)/terractl
|
$(DESTDIR)$(COMPLETIONDIR)/terractl $(DESTDIR)$(POLKITDIR)/50-terractl.rules
|
||||||
rm -rf $(DESTDIR)$(DOCDIR) $(DESTDIR)$(DATADIR)
|
rm -rf $(DESTDIR)$(DOCDIR) $(DESTDIR)$(DATADIR) $(DESTDIR)$(PKGLIBDIR)
|
||||||
@if [ -z "$(DESTDIR)" ] && [ -d /run/systemd/system ]; then systemctl daemon-reload; fi
|
@if [ -z "$(DESTDIR)" ] && [ -d /run/systemd/system ]; then systemctl daemon-reload; fi
|
||||||
@echo "Removed. Kept: $(SYSCONFDIR)/default/terractl, $(SYSCONFDIR)/systemd/journald@terraria.conf,"
|
@echo "Removed. Kept: $(SYSCONFDIR)/default/terractl, $(SYSCONFDIR)/systemd/journald@terraria.conf,"
|
||||||
@echo "/var/lib/terraria, the servers' journal and the terraria user."
|
@echo "/var/lib/terraria, /var/lib/terractl (the daemon's certificate and trusted"
|
||||||
|
@echo "computers), the servers' journal and the terraria user."
|
||||||
|
|
||||||
deb: check
|
deb: check
|
||||||
rm -rf $(STAGE)
|
rm -rf $(STAGE)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Run any number of vanilla **Terraria** and **tModLoader** dedicated servers on o
|
|||||||
```bash
|
```bash
|
||||||
sudo terractl new calamity tmodloader
|
sudo terractl new calamity tmodloader
|
||||||
sudo terractl mods calamity add ~/tmod-export
|
sudo terractl mods calamity add ~/tmod-export
|
||||||
sudo systemctl enable --now terraria@calamity
|
sudo terractl enable calamity --now
|
||||||
sudo terractl attach calamity
|
sudo terractl attach calamity
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -15,18 +15,20 @@ sudo terractl attach calamity
|
|||||||
- **Vanilla and tModLoader side by side**, chosen per instance.
|
- **Vanilla and tModLoader side by side**, chosen per instance.
|
||||||
- **No manual installs.** Drop the official server zips into `/var/lib/terraria/dist`; they're unpacked on demand, and each instance can be pinned to a specific version.
|
- **No manual installs.** Drop the official server zips into `/var/lib/terraria/dist`; they're unpacked on demand, and each instance can be pinned to a specific version.
|
||||||
- **Safe shutdowns.** Stopping a server saves the world first.
|
- **Safe shutdowns.** Stopping a server saves the world first.
|
||||||
- **A live console.** `terractl attach` shows the server's output and lets you type commands into it.
|
- **A live console.** `terractl attach` shows the server's output and lets you type commands into it; `terractl watch` just shows the output.
|
||||||
- **Mod management.** `terractl mods` installs, enables and removes mods, with correct file ownership.
|
- **Mod management.** `terractl mods` installs, enables and removes mods, with correct file ownership.
|
||||||
- **Tab completion** for commands, instance names, mods and console commands.
|
- **Tab completion** for commands, instance names, mods and console commands.
|
||||||
- **Export and import.** Pack an instance into one `.tar.gz` for backups or moving to another machine, and import it anywhere, with paths, ports and ownership adjusted.
|
- **Export and import.** Pack an instance into one `.tar.gz` for backups or moving to another machine, and import it anywhere, with paths, ports and ownership adjusted.
|
||||||
|
- **Manage other hosts.** Run any command on another host with `terractl --host nas ...`, and copy or move instances between hosts with `terractl copy` and `terractl move`, like LXD/Incus remotes. Hosts opt in by running the `terractld` daemon; access is per computer, over pinned TLS.
|
||||||
- **Hardened.** Servers run as an unprivileged `terraria` user and can only write to their own data.
|
- **Hardened.** Servers run as an unprivileged `terraria` user and can only write to their own data.
|
||||||
- **Access for moderators.** Members of the `terraria` group can use the console and read the servers' own logs, without `sudo` and without seeing the rest of the system's logs.
|
- **Access for moderators.** Members of the `terraria` group can start, stop and restart servers, use the console and read the servers' own logs, without `sudo` and without seeing the rest of the system's logs.
|
||||||
- **Helper for Windows.** A PowerShell script exports your enabled tModLoader mods for the server.
|
- **Helper for Windows.** A PowerShell script exports your enabled tModLoader mods for the server.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Debian 12+ or Ubuntu 22.04+ (other systemd-based distributions should work with `make install`)
|
- Debian 12+ or Ubuntu 22.04+ (other systemd-based distributions should work with `make install`)
|
||||||
- `unzip`, `systemd`; for tModLoader, also `curl` and ICU (`libicu`)
|
- `unzip`, `systemd`; for tModLoader, also `curl` and ICU (`libicu`)
|
||||||
|
- `python3` and `openssl`, for managing other hosts
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
|||||||
+282
-25
@@ -10,6 +10,9 @@
|
|||||||
# serverconfig.txt normal Terraria server config (port, world, ...)
|
# serverconfig.txt normal Terraria server config (port, world, ...)
|
||||||
# server/ unpacked server binaries (replaceable)
|
# server/ unpacked server binaries (replaceable)
|
||||||
# data/ worlds, mods, player data (kept across reinstalls)
|
# data/ worlds, mods, player data (kept across reinstalls)
|
||||||
|
#
|
||||||
|
# Managing other hosts (--host, remote, daemon, copy, move) is done by
|
||||||
|
# terractl-remote, in Python; see run_remote_helper.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Work from / so nothing depends on the caller's directory (which the 'terraria'
|
# Work from / so nothing depends on the caller's directory (which the 'terraria'
|
||||||
@@ -33,10 +36,32 @@ SVC_USER="${TERRARIA_USER:-terraria}"
|
|||||||
STOP_TIMEOUT="${TERRARIA_STOP_TIMEOUT:-80}" # keep below TimeoutStopSec
|
STOP_TIMEOUT="${TERRARIA_STOP_TIMEOUT:-80}" # keep below TimeoutStopSec
|
||||||
JOURNAL_NS=terraria # LogNamespace= in terraria@.service
|
JOURNAL_NS=terraria # LogNamespace= in terraria@.service
|
||||||
TERRACTL_VERSION="@VERSION@" # filled in by 'make install' / 'make deb'
|
TERRACTL_VERSION="@VERSION@" # filled in by 'make install' / 'make deb'
|
||||||
|
REMOTE_HELPER="@PKGLIBDIR@/terractl-remote" # likewise
|
||||||
|
|
||||||
die() { echo "terractl: $*" >&2; exit 1; }
|
die() { echo "terractl: $*" >&2; exit 1; }
|
||||||
log() { echo "terractl: $*" >&2; }
|
log() { echo "terractl: $*" >&2; }
|
||||||
|
|
||||||
|
# Is <user> (default: whoever runs this) in the terraria group?
|
||||||
|
in_group() {
|
||||||
|
[[ " $(id -nG "${1:-$(id -un)}" 2>/dev/null) " == *" $SVC_USER "* ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
# A terractl command to suggest: hint root|group <command> [args...]. 'root'
|
||||||
|
# commands need sudo; 'group' ones only for people outside the terraria group
|
||||||
|
# (start/stop/restart are allowed to the group by the polkit rule). Under sudo,
|
||||||
|
# it advises the person who ran sudo. When terractld runs a command for another
|
||||||
|
# host, TERRACTL_VIA says how that host reaches this one (e.g. "--host nas").
|
||||||
|
hint() {
|
||||||
|
local need=$1 who=${SUDO_USER:-$(id -un)}; shift
|
||||||
|
if [[ -n ${TERRACTL_VIA:-} ]]; then
|
||||||
|
echo "terractl $TERRACTL_VIA $*"
|
||||||
|
elif [[ $who == root ]] || { [[ $need == group ]] && in_group "$who"; }; then
|
||||||
|
echo "terractl $*"
|
||||||
|
else
|
||||||
|
echo "sudo terractl $*"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Usage: terractl <command> <instance> [args]
|
Usage: terractl <command> <instance> [args]
|
||||||
@@ -44,8 +69,13 @@ Usage: terractl <command> <instance> [args]
|
|||||||
|
|
||||||
Admin commands:
|
Admin commands:
|
||||||
new <name> [vanilla|tmodloader] [port] create an instance (run as root)
|
new <name> [vanilla|tmodloader] [port] create an instance (run as root)
|
||||||
list show instances, type, port, state
|
list show instances, type, port, boot, state
|
||||||
|
info [<name>] paths and zips of this host, or details of an instance
|
||||||
|
start|stop|restart <name> start, stop (saves the world) or restart a server
|
||||||
|
enable <name> [--now] start at boot (--now: also start it now)
|
||||||
|
disable <name> [--now] don't start at boot (--now: also stop it now)
|
||||||
attach <name> interactive console (Ctrl-D to detach)
|
attach <name> interactive console (Ctrl-D to detach)
|
||||||
|
watch <name> [-n <lines>] [-q] follow the server's output, read-only (Ctrl-C to quit)
|
||||||
cmd <name> <console command...> send a console command (e.g. save, say hi)
|
cmd <name> <console command...> send a console command (e.g. save, say hi)
|
||||||
reinstall <name> [--clean] re-unpack server on next start
|
reinstall <name> [--clean] re-unpack server on next start
|
||||||
(--clean also re-downloads tModLoader's .NET)
|
(--clean also re-downloads tModLoader's .NET)
|
||||||
@@ -54,17 +84,32 @@ Admin commands:
|
|||||||
mods <name> enable|disable <mod>... change which installed mods load
|
mods <name> enable|disable <mod>... change which installed mods load
|
||||||
mods <name> remove <mod>... uninstall mods
|
mods <name> remove <mod>... uninstall mods
|
||||||
delete <name> [-y] [--no-backup] stop, disable and remove an instance
|
delete <name> [-y] [--no-backup] stop, disable and remove an instance
|
||||||
|
rename <name> <new name> rename an instance (restarted if it was running)
|
||||||
export <name> [file.tar.gz] [--with-server]
|
export <name> [file.tar.gz] [--with-server]
|
||||||
archive an instance (configs, worlds, mods)
|
archive an instance (configs, worlds, mods)
|
||||||
import <name> <file.tar.gz> create instance <name> from an archive
|
import <name> <file.tar.gz> create instance <name> from an archive
|
||||||
(worlds/configs archived to $BASE_DIR/.deleted/)
|
(worlds/configs archived to $BASE_DIR/.deleted/)
|
||||||
|
|
||||||
|
Other hosts:
|
||||||
|
--host <remote> <command> [args] run a command on another host (e.g. --host nas list)
|
||||||
|
copy [<remote>:]<name> [<remote>:][<new name>] [--with-server]
|
||||||
|
copy an instance, on this host or between hosts
|
||||||
|
move [<remote>:]<name> [<remote>:][<new name>] [--with-server]
|
||||||
|
move (or rename) an instance; stops it while moving
|
||||||
|
remote add <name> <token> add a host to manage (token from 'daemon trust add')
|
||||||
|
remote add <name> <address> [<token>] ... when the token's addresses don't work from here
|
||||||
|
remote list | remote remove <name> show or forget remotes
|
||||||
|
daemon enable|disable|status let other hosts manage this one (terractld.service)
|
||||||
|
daemon trust add <client> create a one-time token for a computer to join
|
||||||
|
daemon trust list | trust remove <client>
|
||||||
|
show or revoke computers that may manage this host
|
||||||
|
|
||||||
Used by terraria@.service:
|
Used by terraria@.service:
|
||||||
journal-access let the terraria group read the servers' journal
|
journal-access let the terraria group read the servers' journal
|
||||||
prepare <name> create dirs/configs, unpack server if needed
|
prepare <name> create dirs/configs, unpack server if needed
|
||||||
run <name> exec the server
|
run <name> exec the server
|
||||||
stop <name> <mainpid> graceful shutdown via console "exit" (service only;
|
service-stop <name> <mainpid> graceful shutdown via console "exit" (service only;
|
||||||
to stop a server use: systemctl stop terraria@<name>)
|
to stop a server use: terractl stop <name>)
|
||||||
EOF
|
EOF
|
||||||
exit "${1:-1}"
|
exit "${1:-1}"
|
||||||
}
|
}
|
||||||
@@ -239,7 +284,7 @@ take_lock() {
|
|||||||
state=$(systemctl show -p ActiveState --value "terraria@$name" 2>/dev/null || true)
|
state=$(systemctl show -p ActiveState --value "terraria@$name" 2>/dev/null || true)
|
||||||
case $state in
|
case $state in
|
||||||
active|activating|deactivating|reloading)
|
active|activating|deactivating|reloading)
|
||||||
die "terraria@$name is $state; stop it first: systemctl stop terraria@$name" ;;
|
die "terraria@$name is $state; stop it first: $(hint group stop "$name")" ;;
|
||||||
*)
|
*)
|
||||||
die "instance '$name' is already running outside systemd (e.g. 'terractl run' in another terminal); stop that first" ;;
|
die "instance '$name' is already running outside systemd (e.g. 'terractl run' in another terminal); stop that first" ;;
|
||||||
esac
|
esac
|
||||||
@@ -293,11 +338,12 @@ cmd_run() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_stop() {
|
# ExecStop of terraria@.service
|
||||||
|
cmd_service_stop() {
|
||||||
local name=$1 pid=${2:-} fifo="$RUN_DIR/$1.stdin" i
|
local name=$1 pid=${2:-} fifo="$RUN_DIR/$1.stdin" i
|
||||||
# systemd sets INVOCATION_ID for every process it runs for a service
|
# systemd sets INVOCATION_ID for every process it runs for a service
|
||||||
if [[ -z ${INVOCATION_ID:-} ]]; then
|
if [[ -z ${INVOCATION_ID:-} ]]; then
|
||||||
die "'stop' is only for terraria@.service. To stop the server: systemctl stop terraria@$name"
|
die "'service-stop' is only called by terraria@.service. To stop the server: $(hint group stop "$name")"
|
||||||
fi
|
fi
|
||||||
if [[ -n $pid && ! $pid =~ ^[0-9]+$ ]]; then die "invalid process ID '$pid'"; fi
|
if [[ -n $pid && ! $pid =~ ^[0-9]+$ ]]; then die "invalid process ID '$pid'"; fi
|
||||||
if [[ -z $pid ]] || ! kill -0 "$pid" 2>/dev/null; then exit 0; fi
|
if [[ -z $pid ]] || ! kill -0 "$pid" 2>/dev/null; then exit 0; fi
|
||||||
@@ -314,6 +360,42 @@ cmd_stop() {
|
|||||||
log "server did not exit within ${STOP_TIMEOUT}s; leaving it to systemd"
|
log "server did not exit within ${STOP_TIMEOUT}s; leaving it to systemd"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# start/stop/restart through systemd: root, or the terraria group through the
|
||||||
|
# polkit rule (50-terractl.rules)
|
||||||
|
cmd_svc() {
|
||||||
|
local action=$1 name=$2 unit="terraria@$2" state; shift 2
|
||||||
|
[[ $# -eq 0 ]] || die "usage: terractl $action <name>"
|
||||||
|
# Only checkable with read access to the instances (root or the group)
|
||||||
|
if [[ -x $BASE_DIR && ! -f $(inst_dir "$name")/instance.conf ]]; then
|
||||||
|
die "no instance '$name' (create it with: $(hint root new "$name"))"
|
||||||
|
fi
|
||||||
|
if [[ $EUID -ne 0 ]] && ! in_group; then
|
||||||
|
die "'$action' needs root or the '$SVC_USER' group: sudo terractl $action $name"
|
||||||
|
fi
|
||||||
|
state=$(systemctl is-active "$unit" 2>/dev/null || true)
|
||||||
|
case $action in
|
||||||
|
start)
|
||||||
|
if [[ $state == active ]]; then echo "$unit is already running"; return 0; fi
|
||||||
|
systemctl start "$unit"
|
||||||
|
echo "$unit started" ;;
|
||||||
|
stop)
|
||||||
|
if [[ $state == inactive || $state == failed ]]; then
|
||||||
|
if instance_running "$name"; then
|
||||||
|
die "'$name' is running outside systemd (e.g. 'terractl run' in another terminal); type 'exit' in its console"
|
||||||
|
fi
|
||||||
|
echo "$unit isn't running"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "stopping $unit (saves the world first)..."
|
||||||
|
systemctl stop "$unit"
|
||||||
|
echo "$unit stopped" ;;
|
||||||
|
restart)
|
||||||
|
echo "restarting $unit (saves the world first)..."
|
||||||
|
systemctl restart "$unit"
|
||||||
|
echo "$unit restarted" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
# The servers log to their own journal (LogNamespace=terraria), which the
|
# The servers log to their own journal (LogNamespace=terraria), which the
|
||||||
# 'terraria' group can read; see cmd_journal_access. It lives in
|
# 'terraria' group can read; see cmd_journal_access. It lives in
|
||||||
# <machine-id>.terraria under /var/log/journal (or /run/log/journal).
|
# <machine-id>.terraria under /var/log/journal (or /run/log/journal).
|
||||||
@@ -341,7 +423,7 @@ can_read_journal() {
|
|||||||
|
|
||||||
journal_hint() {
|
journal_hint() {
|
||||||
{
|
{
|
||||||
echo "Note: commands are sent, but you can't see the server's output."
|
echo "$1"
|
||||||
if [[ " $(id -Gn) " == *" $SVC_USER "* ]]; then
|
if [[ " $(id -Gn) " == *" $SVC_USER "* ]]; then
|
||||||
echo "You're in the '$SVC_USER' group, but the servers' journal isn't set up for it"
|
echo "You're in the '$SVC_USER' group, but the servers' journal isn't set up for it"
|
||||||
echo "yet. That happens automatically when a server (re)starts, or run once:"
|
echo "yet. That happens automatically when a server (re)starts, or run once:"
|
||||||
@@ -385,7 +467,7 @@ cmd_cmd() {
|
|||||||
if ! can_read_journal; then
|
if ! can_read_journal; then
|
||||||
printf '%s\n' "$*" > "$fifo"
|
printf '%s\n' "$*" > "$fifo"
|
||||||
echo "sent."
|
echo "sent."
|
||||||
journal_hint
|
journal_hint "Note: commands are sent, but you can't see the server's output."
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -412,7 +494,7 @@ cmd_cmd() {
|
|||||||
printf '%s\n' "$out"
|
printf '%s\n' "$out"
|
||||||
else
|
else
|
||||||
echo "sent; no output from the server within 5s (it may be busy or still starting)."
|
echo "sent; no output from the server within 5s (it may be busy or still starting)."
|
||||||
echo "Watch it with: terractl attach $name"
|
echo "Watch it with: $(hint group attach "$name")"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,7 +509,7 @@ cmd_attach() {
|
|||||||
journalctl --namespace="$JOURNAL_NS" -u "terraria@$name" -f -n 30 -o cat &
|
journalctl --namespace="$JOURNAL_NS" -u "terraria@$name" -f -n 30 -o cat &
|
||||||
ATTACH_PID=$! # killed by cleanup() however attach ends
|
ATTACH_PID=$! # killed by cleanup() however attach ends
|
||||||
else
|
else
|
||||||
journal_hint
|
journal_hint "Note: commands are sent, but you can't see the server's output."
|
||||||
fi
|
fi
|
||||||
trap 'echo; exit 0' INT
|
trap 'echo; exit 0' INT
|
||||||
|
|
||||||
@@ -436,7 +518,7 @@ cmd_attach() {
|
|||||||
[[ -z $line ]] && continue
|
[[ -z $line ]] && continue
|
||||||
if [[ $line == exit ]]; then
|
if [[ $line == exit ]]; then
|
||||||
echo "(not sent: 'exit' would shut the server down. Use Ctrl-D to detach," \
|
echo "(not sent: 'exit' would shut the server down. Use Ctrl-D to detach," \
|
||||||
"or 'systemctl stop terraria@$name' to stop it.)"
|
"or '$(hint group stop "$name")' to stop it.)"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
printf '%s\n' "$line" > "$fifo"
|
printf '%s\n' "$line" > "$fifo"
|
||||||
@@ -444,6 +526,37 @@ cmd_attach() {
|
|||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Like attach, but only shows output: nothing typed is sent to the server, so
|
||||||
|
# it's safe to leave running. Works whether or not the server is running.
|
||||||
|
cmd_watch() {
|
||||||
|
local name=$1 lines=30 quiet=0; shift
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-n|--lines)
|
||||||
|
[[ ${2:-} =~ ^[0-9]+$ ]] || die "$1 needs a number of lines"
|
||||||
|
lines=$2; shift ;;
|
||||||
|
-q|--quiet) quiet=1 ;;
|
||||||
|
*) die "unknown option '$1'" ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
# Only checkable with read access to the instances (root or the group)
|
||||||
|
if [[ -x $BASE_DIR && ! -f $(inst_dir "$name")/instance.conf ]]; then
|
||||||
|
die "no instance '$name'"
|
||||||
|
fi
|
||||||
|
if ! can_read_journal; then
|
||||||
|
journal_hint "Can't show the server's output: no read access to the servers' journal."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ $quiet -eq 0 ]]; then
|
||||||
|
if ! systemctl is-active --quiet "terraria@$name" 2>/dev/null; then
|
||||||
|
echo "terraria@$name isn't running; output will appear here once it starts."
|
||||||
|
fi
|
||||||
|
echo "Watching terraria@$name (read-only). Ctrl-C quits; the server keeps running."
|
||||||
|
fi
|
||||||
|
exec journalctl --namespace="$JOURNAL_NS" -u "terraria@$name" -f -n "$lines" -o cat
|
||||||
|
}
|
||||||
|
|
||||||
cmd_delete() {
|
cmd_delete() {
|
||||||
local name=$1; shift
|
local name=$1; shift
|
||||||
local assume_yes=0 backup=1 dir archive answer
|
local assume_yes=0 backup=1 dir archive answer
|
||||||
@@ -497,6 +610,39 @@ cmd_delete() {
|
|||||||
echo "deleted instance '$name'"
|
echo "deleted instance '$name'"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Gives an instance a new name: its folder, the paths in serverconfig.txt, and
|
||||||
|
# its service (stopped and started again, and still starting at boot if it did).
|
||||||
|
cmd_rename() {
|
||||||
|
local name=$1 new=${2:-} dir newdir state enabled
|
||||||
|
[[ $# -eq 2 ]] || die "usage: terractl rename <name> <new name>"
|
||||||
|
valid_name "$new"
|
||||||
|
[[ $EUID -eq 0 ]] || die "rename must be run as root: sudo terractl rename $name $new"
|
||||||
|
dir=$(inst_dir "$name"); newdir=$(inst_dir "$new")
|
||||||
|
[[ -f $dir/instance.conf ]] || die "no instance '$name'"
|
||||||
|
[[ ! -e $newdir ]] || die "instance '$new' already exists"
|
||||||
|
state=$(systemctl is-active "terraria@$name" 2>/dev/null || true)
|
||||||
|
enabled=$(systemctl is-enabled "terraria@$name" 2>/dev/null || true)
|
||||||
|
if [[ $state == inactive || $state == failed ]] && instance_running "$name"; then
|
||||||
|
die "'$name' is running outside systemd (e.g. 'terractl run' in another terminal); stop it first"
|
||||||
|
fi
|
||||||
|
if [[ $state == active || $state == activating || $state == reloading ]]; then
|
||||||
|
echo "stopping terraria@$name (saves the world first)..."
|
||||||
|
systemctl stop "terraria@$name"
|
||||||
|
fi
|
||||||
|
if [[ $enabled == enabled ]]; then systemctl disable -q "terraria@$name"; fi
|
||||||
|
systemctl reset-failed "terraria@$name.service" "terraria@$name.socket" 2>/dev/null || true
|
||||||
|
mv "$dir" "$newdir"
|
||||||
|
if [[ -f $newdir/serverconfig.txt ]]; then
|
||||||
|
sed -i -E "s#^(world|worldpath|banlist)=.*/data/#\1=$newdir/data/#" "$newdir/serverconfig.txt"
|
||||||
|
fi
|
||||||
|
echo "renamed '$name' to '$new'"
|
||||||
|
if [[ $enabled == enabled ]]; then systemctl enable -q "terraria@$new"; echo "terraria@$new starts at boot"; fi
|
||||||
|
if [[ $state == active || $state == activating || $state == reloading ]]; then
|
||||||
|
systemctl start "terraria@$new"
|
||||||
|
echo "terraria@$new started"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------- export/import ----
|
# ------------------------------------------------------- export/import ----
|
||||||
# Archive layout: terractl-export.txt (info about the export; optional)
|
# Archive layout: terractl-export.txt (info about the export; optional)
|
||||||
# <instance>/... (instance.conf, serverconfig.txt, data/, [server/])
|
# <instance>/... (instance.conf, serverconfig.txt, data/, [server/])
|
||||||
@@ -653,6 +799,13 @@ cmd_import() {
|
|||||||
|
|
||||||
# Keep the server version the world was exported with, unless one is pinned already
|
# Keep the server version the world was exported with, unless one is pinned already
|
||||||
load_conf "$name"
|
load_conf "$name"
|
||||||
|
# A pin to an absolute path from another host usually doesn't exist here: use
|
||||||
|
# the zip of that name in this host's dist folder instead
|
||||||
|
if [[ $ZIP == /* && ! -r $ZIP && $(basename "$ZIP") =~ ^[A-Za-z0-9._+-]+\.zip$ ]]; then
|
||||||
|
log "ZIP=$ZIP isn't on this host; now ZIP=$(basename "$ZIP") (from $DIST_DIR)"
|
||||||
|
ZIP=$(basename "$ZIP")
|
||||||
|
sed -i "s/^ZIP=.*/ZIP=$ZIP/" "$dir/instance.conf"
|
||||||
|
fi
|
||||||
if [[ -z $ZIP && $meta_zip =~ ^[A-Za-z0-9._+-]+\.zip$ ]]; then
|
if [[ -z $ZIP && $meta_zip =~ ^[A-Za-z0-9._+-]+\.zip$ ]]; then
|
||||||
sed -i "s/^ZIP=.*/ZIP=$meta_zip/" "$dir/instance.conf"
|
sed -i "s/^ZIP=.*/ZIP=$meta_zip/" "$dir/instance.conf"
|
||||||
ZIP=$meta_zip
|
ZIP=$meta_zip
|
||||||
@@ -671,7 +824,7 @@ cmd_import() {
|
|||||||
else
|
else
|
||||||
echo "imported '$name' into $dir"
|
echo "imported '$name' into $dir"
|
||||||
fi
|
fi
|
||||||
echo "Review $dir/serverconfig.txt, then: systemctl enable --now terraria@$name"
|
echo "Review $dir/serverconfig.txt, then: $(hint root enable "$name" --now)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ---------------------------------------------------------------- mods ----
|
# ---------------------------------------------------------------- mods ----
|
||||||
@@ -699,7 +852,7 @@ write_enabled() { # write_enabled <mods dir> <name>...
|
|||||||
|
|
||||||
mods_restart_hint() {
|
mods_restart_hint() {
|
||||||
if systemctl is-active --quiet "terraria@$name" 2>/dev/null; then
|
if systemctl is-active --quiet "terraria@$name" 2>/dev/null; then
|
||||||
echo "Restart to apply: sudo systemctl restart terraria@$name"
|
echo "Restart to apply: $(hint group restart "$name")"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,7 +865,7 @@ mods_list() {
|
|||||||
done
|
done
|
||||||
if [[ ${#files[@]} -eq 0 ]]; then
|
if [[ ${#files[@]} -eq 0 ]]; then
|
||||||
echo "No mods installed in $mdir"
|
echo "No mods installed in $mdir"
|
||||||
echo "Add some with: terractl mods $name add <folder or .tmod files>"
|
echo "Add some with: $(hint root mods "$name" add "<folder or .tmod files>")"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
printf '%-32s %s\n' MOD STATE
|
printf '%-32s %s\n' MOD STATE
|
||||||
@@ -820,7 +973,7 @@ cmd_mods() {
|
|||||||
if [[ $# -gt 0 ]]; then shift; fi
|
if [[ $# -gt 0 ]]; then shift; fi
|
||||||
local dir mdir
|
local dir mdir
|
||||||
dir=$(inst_dir "$name")
|
dir=$(inst_dir "$name")
|
||||||
[[ -f $dir/instance.conf ]] || die "no instance '$name' (create it with: terractl new $name tmodloader)"
|
[[ -f $dir/instance.conf ]] || die "no instance '$name' (create it with: $(hint root new "$name" tmodloader))"
|
||||||
load_conf "$name"
|
load_conf "$name"
|
||||||
[[ $TYPE == tmodloader ]] || die "'$name' is a $TYPE instance; mods need TYPE=tmodloader"
|
[[ $TYPE == tmodloader ]] || die "'$name' is a $TYPE instance; mods need TYPE=tmodloader"
|
||||||
mdir="$dir/data/Mods"
|
mdir="$dir/data/Mods"
|
||||||
@@ -850,26 +1003,67 @@ Created $type instance '$name' in $dir
|
|||||||
Review $dir/serverconfig.txt, then:
|
Review $dir/serverconfig.txt, then:
|
||||||
EOF
|
EOF
|
||||||
if [[ $type == tmodloader ]]; then
|
if [[ $type == tmodloader ]]; then
|
||||||
echo " terractl mods $name add <folder or .tmod files> (before first start)"
|
echo " $(hint root mods "$name" add "<folder or .tmod files>") (before first start)"
|
||||||
fi
|
fi
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
systemctl enable --now terraria@$name
|
$(hint root enable "$name" --now)
|
||||||
terractl attach $name
|
$(hint group attach "$name")
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Whether the server starts at boot. Needs root: systemd doesn't let polkit
|
||||||
|
# rules limit enabling to our units, so the terraria group can't be allowed it.
|
||||||
|
cmd_enable() {
|
||||||
|
local action=$1 name=$2 now=0 unit="terraria@$2"; shift 2
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--now) now=1 ;;
|
||||||
|
*) die "unknown option '$1'" ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
[[ $EUID -eq 0 ]] || die "'$action' needs root: sudo terractl $action $name$(if [[ $now -eq 1 ]]; then echo " --now"; fi)"
|
||||||
|
if [[ $action == enable ]]; then
|
||||||
|
# Enabling a name without an instance would create a default one at boot
|
||||||
|
[[ -f $(inst_dir "$name")/instance.conf ]] ||
|
||||||
|
die "no instance '$name' (create it with: $(hint root new "$name"))"
|
||||||
|
systemctl enable -q "$unit"
|
||||||
|
if [[ $now -eq 1 ]]; then
|
||||||
|
systemctl start "$unit"
|
||||||
|
echo "$unit started and will start at boot"
|
||||||
|
elif systemctl is-active --quiet "$unit"; then
|
||||||
|
echo "$unit will start at boot"
|
||||||
|
else
|
||||||
|
echo "$unit will start at boot. To start it now: $(hint group start "$name")"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
systemctl disable -q "$unit"
|
||||||
|
if [[ $now -eq 1 ]]; then
|
||||||
|
echo "stopping $unit (saves the world first)..."
|
||||||
|
systemctl stop "$unit"
|
||||||
|
echo "$unit stopped and won't start at boot"
|
||||||
|
elif systemctl is-active --quiet "$unit"; then
|
||||||
|
echo "$unit won't start at boot; it is still running. To stop it: $(hint group stop "$name")"
|
||||||
|
else
|
||||||
|
echo "$unit won't start at boot"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
cmd_list() {
|
cmd_list() {
|
||||||
local d n port state
|
local d n port boot state
|
||||||
printf '%-20s %-11s %-6s %s\n' INSTANCE TYPE PORT STATE
|
printf '%-20s %-11s %-6s %-9s %s\n' INSTANCE TYPE PORT BOOT STATE
|
||||||
for d in "$BASE_DIR"/*/; do
|
for d in "$BASE_DIR"/*/; do
|
||||||
[[ -f $d/instance.conf ]] || continue
|
[[ -f $d/instance.conf ]] || continue
|
||||||
n=$(basename "$d")
|
n=$(basename "$d")
|
||||||
(
|
(
|
||||||
load_conf "$n"
|
load_conf "$n"
|
||||||
port=$(sed -n 's/^port=\([0-9][0-9]*\).*/\1/p' "$d/serverconfig.txt" 2>/dev/null | tail -n1)
|
port=$(sed -n 's/^port=\([0-9][0-9]*\).*/\1/p' "$d/serverconfig.txt" 2>/dev/null | tail -n1)
|
||||||
|
# enabled, disabled, or e.g. masked
|
||||||
|
boot=$(systemctl is-enabled "terraria@$n" 2>/dev/null || true)
|
||||||
state=$(systemctl is-active "terraria@$n" 2>/dev/null || true)
|
state=$(systemctl is-active "terraria@$n" 2>/dev/null || true)
|
||||||
if [[ $state != active ]] && instance_running "$n"; then state="running (outside systemd)"; fi
|
if [[ $state != active ]] && instance_running "$n"; then state="running (outside systemd)"; fi
|
||||||
printf '%-20s %-11s %-6s %s\n' "$n" "$TYPE" "${port:--}" "$state"
|
printf '%-20s %-11s %-6s %-9s %s\n' "$n" "$TYPE" "${port:--}" "${boot:--}" "$state"
|
||||||
) 2>/dev/null || printf '%-20s %s\n' "$n" "(cannot read config - try sudo)"
|
) 2>/dev/null || printf '%-20s %s\n' "$n" "(cannot read config - try sudo)"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
@@ -884,17 +1078,69 @@ cmd_reinstall() {
|
|||||||
esac
|
esac
|
||||||
[[ -f $dir/instance.conf ]] || die "no instance '$name'"
|
[[ -f $dir/instance.conf ]] || die "no instance '$name'"
|
||||||
if [[ $clean -eq 1 ]] && instance_running "$name"; then
|
if [[ $clean -eq 1 ]] && instance_running "$name"; then
|
||||||
die "'$name' is running; --clean would delete .NET under it. Stop it first: systemctl stop terraria@$name"
|
die "'$name' is running; --clean would delete .NET under it. Stop it first: $(hint group stop "$name")"
|
||||||
fi
|
fi
|
||||||
rm -f "$dir/server/.installed"
|
rm -f "$dir/server/.installed"
|
||||||
if [[ $clean -eq 1 ]]; then
|
if [[ $clean -eq 1 ]]; then
|
||||||
rm -rf "$dir/server/dotnet" "$dir/server/tModLoader-Logs"
|
rm -rf "$dir/server/dotnet" "$dir/server/tModLoader-Logs"
|
||||||
echo "removed the downloaded .NET runtime and logs; .NET will be downloaded again"
|
echo "removed the downloaded .NET runtime and logs; .NET will be downloaded again"
|
||||||
fi
|
fi
|
||||||
echo "server/ will be re-unpacked on next start: systemctl restart terraria@$name"
|
echo "server/ will be re-unpacked on next start: $(hint group restart "$name")"
|
||||||
|
}
|
||||||
|
|
||||||
|
# key=value facts, one per line, for people and for terractl-remote (copy, move,
|
||||||
|
# tab completion). Without a name: this host. Repeated keys are lists.
|
||||||
|
cmd_info() {
|
||||||
|
local name=${1:-} dir f ver installed="" port
|
||||||
|
if [[ -z $name ]]; then
|
||||||
|
ver=$TERRACTL_VERSION; if [[ $ver == *@* ]]; then ver=development; fi
|
||||||
|
echo "version=$ver"
|
||||||
|
echo "host=$(hostname 2>/dev/null || echo unknown)"
|
||||||
|
echo "base_dir=$BASE_DIR"
|
||||||
|
echo "dist_dir=$DIST_DIR"
|
||||||
|
echo "run_dir=$RUN_DIR"
|
||||||
|
for f in "$DIST_DIR"/*.zip; do
|
||||||
|
if [[ -f $f ]]; then echo "dist_zip=$(basename "$f")"; fi
|
||||||
|
done
|
||||||
|
for f in "$BASE_DIR"/*/instance.conf; do
|
||||||
|
if [[ -f $f ]]; then echo "instance=$(basename "$(dirname "$f")")"; fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
dir=$(inst_dir "$name")
|
||||||
|
echo "instance=$name"
|
||||||
|
if [[ ! -f $dir/instance.conf ]]; then echo "exists=no"; return 0; fi
|
||||||
|
load_conf "$name"
|
||||||
|
if [[ -r $dir/server/.installed ]]; then read -r _ installed < "$dir/server/.installed" || true; fi
|
||||||
|
port=$(sed -n 's/^port=\([0-9][0-9]*\).*/\1/p' "$dir/serverconfig.txt" 2>/dev/null | tail -n1) || true
|
||||||
|
echo "exists=yes"
|
||||||
|
echo "dir=$dir"
|
||||||
|
echo "type=$TYPE"
|
||||||
|
echo "port=$port"
|
||||||
|
echo "pinned_zip=$ZIP"
|
||||||
|
echo "installed_zip=$installed"
|
||||||
|
echo "state=$(systemctl is-active "terraria@$name" 2>/dev/null || true)"
|
||||||
|
echo "boot=$(systemctl is-enabled "terraria@$name" 2>/dev/null || true)"
|
||||||
|
echo "running=$(if instance_running "$name"; then echo yes; else echo no; fi)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Other hosts are handled by terractl-remote (Python). It runs this script for
|
||||||
|
# anything it does on this host, and passes on where it was called from.
|
||||||
|
run_remote_helper() {
|
||||||
|
local helper=$REMOTE_HELPER ver=$TERRACTL_VERSION self
|
||||||
|
self=$(readlink -f "$0")
|
||||||
|
# A development copy uses the helper from its source tree
|
||||||
|
if [[ $helper == *@* ]]; then helper="$(dirname "$self")/../lib/terractl-remote"; fi
|
||||||
|
if [[ $ver == *@* ]]; then ver=development; fi
|
||||||
|
command -v python3 >/dev/null || die "this needs python3 (sudo apt install python3)"
|
||||||
|
TERRACTL_BIN=$self TERRACTL_VERSION=$ver TERRACTL_CALLER_PWD=$CALLER_PWD \
|
||||||
|
TERRACTL_LISTEN=${TERRACTL_LISTEN:-} exec python3 "$helper" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
[[ $# -ge 1 ]] || usage
|
[[ $# -ge 1 ]] || usage
|
||||||
|
case $1 in
|
||||||
|
--host|--host=*|-H|remote|daemon|copy|move) run_remote_helper "$@" ;;
|
||||||
|
esac
|
||||||
command=$1; shift
|
command=$1; shift
|
||||||
case $command in
|
case $command in
|
||||||
-h|--help|help) usage 0 ;;
|
-h|--help|help) usage 0 ;;
|
||||||
@@ -904,6 +1150,7 @@ case $command in
|
|||||||
else echo "terractl $TERRACTL_VERSION"; fi
|
else echo "terractl $TERRACTL_VERSION"; fi
|
||||||
exit 0 ;;
|
exit 0 ;;
|
||||||
list) cmd_list; exit ;;
|
list) cmd_list; exit ;;
|
||||||
|
info) if [[ $# -eq 0 ]]; then cmd_info; exit; fi ;;
|
||||||
journal-access) cmd_journal_access; exit ;;
|
journal-access) cmd_journal_access; exit ;;
|
||||||
esac
|
esac
|
||||||
[[ $# -ge 1 ]] || usage
|
[[ $# -ge 1 ]] || usage
|
||||||
@@ -917,20 +1164,30 @@ if [[ $command == prepare || $command == run ]]; then
|
|||||||
exec runuser -u "$SVC_USER" -- env HOME="$(inst_dir "$1")" \
|
exec runuser -u "$SVC_USER" -- env HOME="$(inst_dir "$1")" \
|
||||||
"$(readlink -f "$0")" "$command" "$@"
|
"$(readlink -f "$0")" "$command" "$@"
|
||||||
elif [[ $(id -un) != "$SVC_USER" ]]; then
|
elif [[ $(id -un) != "$SVC_USER" ]]; then
|
||||||
die "$command must run as root or '$SVC_USER'"
|
# Being in the group isn't enough: files would end up owned by the caller
|
||||||
|
die "'$command' needs root (it switches to the '$SVC_USER' user by itself): sudo terractl $command $1"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
case $command in
|
case $command in
|
||||||
prepare) cmd_prepare "$1" ;;
|
prepare) cmd_prepare "$1" ;;
|
||||||
run) cmd_run "$1" ;;
|
run) cmd_run "$1" ;;
|
||||||
stop) cmd_stop "$@" ;;
|
service-stop) cmd_service_stop "$@" ;;
|
||||||
|
stop)
|
||||||
|
# Before 0.0.11 the service stopped servers with 'terractl stop <name> <pid>';
|
||||||
|
# a unit loaded before an upgrade may still do that
|
||||||
|
if [[ -n ${INVOCATION_ID:-} && $# -eq 2 ]]; then cmd_service_stop "$@"; else cmd_svc stop "$@"; fi ;;
|
||||||
|
start|restart) cmd_svc "$command" "$@" ;;
|
||||||
|
info) cmd_info "$1" ;;
|
||||||
cmd) cmd_cmd "$@" ;;
|
cmd) cmd_cmd "$@" ;;
|
||||||
attach) cmd_attach "$1" ;;
|
attach) cmd_attach "$1" ;;
|
||||||
|
watch) cmd_watch "$@" ;;
|
||||||
|
enable|disable) cmd_enable "$command" "$@" ;;
|
||||||
mods) cmd_mods "$@" ;;
|
mods) cmd_mods "$@" ;;
|
||||||
new) cmd_new "$@" ;;
|
new) cmd_new "$@" ;;
|
||||||
reinstall) cmd_reinstall "$@" ;;
|
reinstall) cmd_reinstall "$@" ;;
|
||||||
delete) cmd_delete "$@" ;;
|
delete) cmd_delete "$@" ;;
|
||||||
|
rename) cmd_rename "$@" ;;
|
||||||
export) cmd_export "$@" ;;
|
export) cmd_export "$@" ;;
|
||||||
import) cmd_import "$@" ;;
|
import) cmd_import "$@" ;;
|
||||||
*) usage ;;
|
*) usage ;;
|
||||||
|
|||||||
@@ -32,6 +32,33 @@ _terractl_instances() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Remotes this user added with 'terractl remote add'
|
||||||
|
_terractl_remotes() {
|
||||||
|
local f=${TERRACTL_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/terractl}/remotes.json
|
||||||
|
[[ -r $f ]] || return 0
|
||||||
|
python3 -c 'import json, sys; print("\n".join(json.load(open(sys.argv[1]))["remotes"]))' "$f" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Instance names on a remote. This asks its daemon, so it can take a moment.
|
||||||
|
_terractl_remote_instances() {
|
||||||
|
timeout 5 terractl --host "$1" info 2>/dev/null </dev/null | sed -n 's/^instance=//p'
|
||||||
|
}
|
||||||
|
|
||||||
|
# [<remote>:]<name> for copy and move
|
||||||
|
_terractl_places() {
|
||||||
|
if [[ $cur == -* ]]; then
|
||||||
|
_terractl_reply "--with-server"
|
||||||
|
elif [[ $cur == *:* ]]; then
|
||||||
|
mapfile -t COMPREPLY < <(compgen -P "${cur%:*}:" -W "$(_terractl_remote_instances "${cur%:*}")" -- "${cur##*:}")
|
||||||
|
else
|
||||||
|
mapfile -t COMPREPLY < <(compgen -W "$(_terractl_instances) $(_terractl_remotes | sed 's/$/:/')" -- "$cur")
|
||||||
|
# After "<remote>:" comes an instance name, not a space
|
||||||
|
if [[ ${#COMPREPLY[@]} -eq 1 && ${COMPREPLY[0]} == *: ]]; then compopt -o nospace 2>/dev/null; fi
|
||||||
|
fi
|
||||||
|
# Readline sees only the part after the last ':'
|
||||||
|
if declare -F __ltrim_colon_completions >/dev/null 2>&1; then __ltrim_colon_completions "$cur"; fi
|
||||||
|
}
|
||||||
|
|
||||||
# Mods installed in an instance (*.tmod files), or enabled ones (enabled.json)
|
# Mods installed in an instance (*.tmod files), or enabled ones (enabled.json)
|
||||||
_terractl_mods() {
|
_terractl_mods() {
|
||||||
local mdir f
|
local mdir f
|
||||||
@@ -70,16 +97,33 @@ _terractl() {
|
|||||||
if declare -F _init_completion >/dev/null 2>&1; then
|
if declare -F _init_completion >/dev/null 2>&1; then
|
||||||
# shellcheck disable=SC2034 # prev is set by _init_completion but not needed here
|
# shellcheck disable=SC2034 # prev is set by _init_completion but not needed here
|
||||||
local prev
|
local prev
|
||||||
_init_completion || return
|
# -n : keeps "<remote>:<name>" (copy, move) in one word
|
||||||
|
_init_completion -n : || return
|
||||||
else
|
else
|
||||||
cur=${COMP_WORDS[COMP_CWORD]}
|
cur=${COMP_WORDS[COMP_CWORD]}
|
||||||
words=("${COMP_WORDS[@]}")
|
words=("${COMP_WORDS[@]}")
|
||||||
cword=$COMP_CWORD
|
cword=$COMP_CWORD
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local commands="new list attach cmd mods reinstall delete export import prepare run --help --version"
|
local commands="new list info start stop restart enable disable attach watch cmd mods reinstall delete rename export import"
|
||||||
local console="save say playing kick ban motd password time dawn noon dusk midnight settle seed version maxplayers port clear help"
|
local console="save say playing kick ban motd password time dawn noon dusk midnight settle seed version maxplayers port clear help"
|
||||||
|
|
||||||
|
# terractl --host <remote> <command> ...: the same as on this host, except
|
||||||
|
# that instance names come from the remote and mod names aren't offered
|
||||||
|
local host=""
|
||||||
|
if [[ ${words[1]:-} == --host || ${words[1]:-} == -H ]]; then
|
||||||
|
if (( cword == 2 )); then
|
||||||
|
_terractl_reply "$(_terractl_remotes)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
host=${words[2]}
|
||||||
|
words=("${words[0]}" "${words[@]:3}")
|
||||||
|
cword=$((cword - 2))
|
||||||
|
commands+=" --help --version"
|
||||||
|
else
|
||||||
|
commands+=" copy move remote daemon prepare run --host --help --version"
|
||||||
|
fi
|
||||||
|
|
||||||
if (( cword == 1 )); then
|
if (( cword == 1 )); then
|
||||||
_terractl_reply "$commands"
|
_terractl_reply "$commands"
|
||||||
return
|
return
|
||||||
@@ -97,10 +141,31 @@ _terractl() {
|
|||||||
# import <new name> <file.tar.gz>: the name is new, so nothing to offer
|
# import <new name> <file.tar.gz>: the name is new, so nothing to offer
|
||||||
if (( cword == 3 )); then _terractl_files tar.gz; fi
|
if (( cword == 3 )); then _terractl_files tar.gz; fi
|
||||||
return ;;
|
return ;;
|
||||||
|
copy|move)
|
||||||
|
_terractl_places
|
||||||
|
return ;;
|
||||||
|
remote)
|
||||||
|
if (( cword == 2 )); then
|
||||||
|
_terractl_reply "add list remove"
|
||||||
|
elif (( cword == 3 )) && [[ ${words[2]} == remove ]]; then
|
||||||
|
_terractl_reply "$(_terractl_remotes)"
|
||||||
|
fi
|
||||||
|
return ;;
|
||||||
|
daemon)
|
||||||
|
if (( cword == 2 )); then
|
||||||
|
_terractl_reply "enable disable status trust"
|
||||||
|
elif (( cword == 3 )) && [[ ${words[2]} == trust ]]; then
|
||||||
|
_terractl_reply "add list remove"
|
||||||
|
fi
|
||||||
|
return ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
if (( cword == 2 )); then
|
if (( cword == 2 )); then
|
||||||
_terractl_reply "$(_terractl_instances)"
|
if [[ -n $host ]]; then
|
||||||
|
_terractl_reply "$(_terractl_remote_instances "$host")"
|
||||||
|
else
|
||||||
|
_terractl_reply "$(_terractl_instances)"
|
||||||
|
fi
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -110,6 +175,11 @@ _terractl() {
|
|||||||
if (( cword == 3 )); then _terractl_reply "$console"; fi ;;
|
if (( cword == 3 )); then _terractl_reply "$console"; fi ;;
|
||||||
reinstall)
|
reinstall)
|
||||||
if (( cword == 3 )); then _terractl_reply "--clean"; fi ;;
|
if (( cword == 3 )); then _terractl_reply "--clean"; fi ;;
|
||||||
|
enable|disable)
|
||||||
|
if (( cword == 3 )); then _terractl_reply "--now"; fi ;;
|
||||||
|
watch)
|
||||||
|
# the number after -n is free-form
|
||||||
|
if [[ $cur == -* ]]; then _terractl_reply "-n -q"; fi ;;
|
||||||
delete)
|
delete)
|
||||||
_terractl_reply "-y --yes --no-backup" ;;
|
_terractl_reply "-y --yes --no-backup" ;;
|
||||||
export)
|
export)
|
||||||
@@ -124,9 +194,9 @@ _terractl() {
|
|||||||
# .tmod files and folders (e.g. the Windows export folder)
|
# .tmod files and folders (e.g. the Windows export folder)
|
||||||
_terractl_files tmod ;;
|
_terractl_files tmod ;;
|
||||||
enable|remove)
|
enable|remove)
|
||||||
_terractl_reply "$(_terractl_mods "$instance" installed)" ;;
|
if [[ -z $host ]]; then _terractl_reply "$(_terractl_mods "$instance" installed)"; fi ;;
|
||||||
disable)
|
disable)
|
||||||
_terractl_reply "$(_terractl_mods "$instance" enabled)" ;;
|
if [[ -z $host ]]; then _terractl_reply "$(_terractl_mods "$instance" enabled)"; fi ;;
|
||||||
esac ;;
|
esac ;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,3 +12,8 @@
|
|||||||
|
|
||||||
# Seconds to wait for a server to save and exit on stop (keep below 90)
|
# Seconds to wait for a server to save and exit on stop (keep below 90)
|
||||||
#TERRARIA_STOP_TIMEOUT=80
|
#TERRARIA_STOP_TIMEOUT=80
|
||||||
|
|
||||||
|
# Where terractld (terractl daemon) listens for other hosts: an address and/or
|
||||||
|
# port. Default: all addresses, port 7776. After changing it:
|
||||||
|
# sudo systemctl restart terractld
|
||||||
|
#TERRACTL_LISTEN=10.0.10.77:7776
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Members of the 'terraria' group may start, stop and restart Terraria servers
|
||||||
|
// (terraria@<name>.service) without sudo. Nothing else: enable/disable and all
|
||||||
|
// other units still need root.
|
||||||
|
//
|
||||||
|
// Starting a name that has no instance yet creates a vanilla instance with
|
||||||
|
// default settings, as it does for root.
|
||||||
|
//
|
||||||
|
// Needs polkit 0.106 or newer (JavaScript rules); Ubuntu 22.04's 0.105 ignores it.
|
||||||
|
polkit.addRule(function(action, subject) {
|
||||||
|
if (action.id != "org.freedesktop.systemd1.manage-units" ||
|
||||||
|
!subject.isInGroup("terraria")) {
|
||||||
|
return polkit.Result.NOT_HANDLED;
|
||||||
|
}
|
||||||
|
// Same names terractl accepts (valid_name)
|
||||||
|
var unit = action.lookup("unit") || "";
|
||||||
|
var verb = action.lookup("verb") || "";
|
||||||
|
var verbs = ["start", "stop", "restart", "try-restart", "reset-failed"];
|
||||||
|
if (/^terraria@[A-Za-z0-9_][A-Za-z0-9_.-]*\.service$/.test(unit) &&
|
||||||
|
verbs.indexOf(verb) >= 0) {
|
||||||
|
return polkit.Result.YES;
|
||||||
|
}
|
||||||
|
return polkit.Result.NOT_HANDLED;
|
||||||
|
});
|
||||||
+226
-40
@@ -16,14 +16,15 @@ Run any number of vanilla Terraria and tModLoader dedicated servers on one Linux
|
|||||||
10. [Configuration](#configuration)
|
10. [Configuration](#configuration)
|
||||||
11. [Mods (tModLoader)](#mods-tmodloader)
|
11. [Mods (tModLoader)](#mods-tmodloader)
|
||||||
12. [Versions and upgrades](#versions-and-upgrades)
|
12. [Versions and upgrades](#versions-and-upgrades)
|
||||||
13. [Deleting and restoring](#deleting-and-restoring)
|
13. [Deleting, renaming and restoring](#deleting-renaming-and-restoring)
|
||||||
14. [Exporting and importing](#exporting-and-importing)
|
14. [Exporting and importing](#exporting-and-importing)
|
||||||
15. [Backups](#backups)
|
15. [Managing other hosts](#managing-other-hosts)
|
||||||
16. [Permissions](#permissions)
|
16. [Backups](#backups)
|
||||||
17. [Running by hand for debugging](#running-by-hand-for-debugging)
|
17. [Permissions](#permissions)
|
||||||
18. [Troubleshooting](#troubleshooting)
|
18. [Running by hand for debugging](#running-by-hand-for-debugging)
|
||||||
19. [Tab completion](#tab-completion)
|
19. [Troubleshooting](#troubleshooting)
|
||||||
20. [Command reference](#command-reference)
|
20. [Tab completion](#tab-completion)
|
||||||
|
21. [Command reference](#command-reference)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -49,10 +50,13 @@ Everything is installed by the `terraria-server-manager` package:
|
|||||||
| `/usr/lib/systemd/system/terraria@.service` | Template service. One copy runs per instance. |
|
| `/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/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/bin/terractl` | Helper script. The service calls it to set up, run and stop servers; you use it for everyday management. |
|
||||||
| `/etc/default/terractl` | Optional overrides (locations, stop timeout). |
|
| `/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)). |
|
| `/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/sysusers.d/terractl.conf` | Defines the `terraria` system user. |
|
||||||
| `/usr/lib/tmpfiles.d/terractl.conf` | Creates `/var/lib/terraria` and its subdirectories. |
|
| `/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. |
|
| `/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:
|
What happens when an instance starts:
|
||||||
@@ -61,7 +65,7 @@ What happens when an instance starts:
|
|||||||
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.
|
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)).
|
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 stop` sends `exit` through the pipe so the world is saved before the server shuts down.
|
On stop, `terractl service-stop` sends `exit` through the pipe so the world is saved before the server shuts down.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -86,6 +90,8 @@ On stop, `terractl stop` sends `exit` through the pipe so the world is saved bef
|
|||||||
└── .deleted/ Backups made by 'terractl delete'
|
└── .deleted/ Backups made by 'terractl delete'
|
||||||
|
|
||||||
/run/terraria/<name>.stdin Console pipe (exists only while running)
|
/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.
|
The server can write only inside `instances/`. It reads `dist/` but can never change it.
|
||||||
@@ -100,15 +106,15 @@ The package works on Debian 12 or newer and Ubuntu 22.04 or newer.
|
|||||||
sudo apt install ./terraria-server-manager_0.0.9_all.deb
|
sudo apt install ./terraria-server-manager_0.0.9_all.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
Using `apt install ./file.deb` (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.
|
Using `apt install ./file.deb` (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:
|
Installing the package:
|
||||||
|
|
||||||
- creates the `terraria` system user and group;
|
- creates the `terraria` system user and group;
|
||||||
- creates `/var/lib/terraria/`, `/var/lib/terraria/instances/` and `/var/lib/terraria/dist/`;
|
- creates `/var/lib/terraria/`, `/var/lib/terraria/instances/` and `/var/lib/terraria/dist/`;
|
||||||
- installs the units and helper script.
|
- installs the units, the helper script and the polkit rule.
|
||||||
|
|
||||||
It doesn't start or enable anything.
|
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.
|
If you use a firewall, open each instance's port for **TCP**. Terraria uses TCP only.
|
||||||
|
|
||||||
@@ -173,6 +179,8 @@ sudo make install # or install directly without a package (PREFIX=/usr/
|
|||||||
sudo make uninstall # removes a 'make install' (keeps settings and data)
|
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.
|
The version comes from the `VERSION` file. Set `Maintainer:` in `packaging/deb/control` to yourself before publishing packages.
|
||||||
|
|
||||||
### Changing locations
|
### Changing locations
|
||||||
@@ -281,7 +289,7 @@ Instance names may contain letters, digits, `_`, `-` and `.`, and must not start
|
|||||||
sudo terractl new calamity tmodloader
|
sudo terractl new calamity tmodloader
|
||||||
sudo terractl mods calamity add ~/tmod-export # your mods, before the first start
|
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 nano /var/lib/terraria/instances/calamity/serverconfig.txt # optional: world size, difficulty
|
||||||
sudo systemctl enable --now terraria@calamity
|
sudo terractl enable calamity --now
|
||||||
sudo terractl attach calamity # watch it load and generate the world
|
sudo terractl attach calamity # watch it load and generate the world
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -291,24 +299,41 @@ You can also skip `new` and just run `sudo systemctl start terraria@<name>`. Tha
|
|||||||
|
|
||||||
## Starting, stopping and boot
|
## Starting, stopping and boot
|
||||||
|
|
||||||
Instances are ordinary systemd services:
|
Instances are ordinary systemd services, which `terractl` starts and stops for you:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl start terraria@calamity
|
sudo terractl start calamity
|
||||||
sudo systemctl stop terraria@calamity # saves the world, then exits
|
sudo terractl stop calamity # saves the world, then exits
|
||||||
sudo systemctl restart terraria@calamity
|
sudo terractl restart calamity
|
||||||
sudo systemctl enable terraria@calamity # start at boot
|
|
||||||
sudo systemctl disable terraria@calamity # don't start at boot
|
|
||||||
sudo systemctl enable --now terraria@calamity # both at once
|
|
||||||
systemctl status terraria@calamity
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To see all instances with their type, port and state:
|
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
|
```bash
|
||||||
sudo terractl list
|
sudo terractl list
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
INSTANCE TYPE PORT BOOT STATE
|
||||||
|
calamity tmodloader 7778 enabled active
|
||||||
|
survival vanilla 7777 disabled inactive
|
||||||
|
```
|
||||||
|
|
||||||
Behaviour to be aware of:
|
Behaviour to be aware of:
|
||||||
|
|
||||||
- **Crashes restart automatically** after 10 seconds.
|
- **Crashes restart automatically** after 10 seconds.
|
||||||
@@ -330,10 +355,20 @@ 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.
|
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 `systemctl stop` to stop it.
|
`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.
|
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`
|
### One-off commands: `cmd`
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -343,7 +378,7 @@ 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.
|
`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` and `cmd` fully, output included, without `sudo`. See [Logs](#logs) for how that's arranged.
|
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
|
### Logs
|
||||||
|
|
||||||
@@ -543,7 +578,7 @@ sudo systemctl restart terraria@calamity
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Deleting and restoring
|
## Deleting, renaming and restoring
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo terractl delete calamity # asks you to type the name
|
sudo terractl delete calamity # asks you to type the name
|
||||||
@@ -557,11 +592,17 @@ To restore an instance, import its backup. You can give it back its old name, or
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo terractl import calamity /var/lib/terraria/instances/.deleted/calamity-20260928-134422.tar.gz
|
sudo terractl import calamity /var/lib/terraria/instances/.deleted/calamity-20260928-134422.tar.gz
|
||||||
sudo systemctl enable --now terraria@calamity # server/ is unpacked again on start
|
sudo terractl enable calamity --now # server/ is unpacked again on start
|
||||||
```
|
```
|
||||||
|
|
||||||
`.deleted/` is never cleaned up automatically. Remove old archives yourself.
|
`.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
|
## Exporting and importing
|
||||||
@@ -599,11 +640,13 @@ After extracting, it adjusts the instance for its new home:
|
|||||||
|
|
||||||
`import` also accepts:
|
`import` also accepts:
|
||||||
|
|
||||||
- the backups that `delete` makes (see [Deleting and restoring](#deleting-and-restoring));
|
- 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`.
|
- 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
|
### 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
|
```bash
|
||||||
# old machine
|
# old machine
|
||||||
sudo terractl export calamity /tmp/calamity.tar.gz
|
sudo terractl export calamity /tmp/calamity.tar.gz
|
||||||
@@ -611,11 +654,116 @@ scp /tmp/calamity.tar.gz newhost:/tmp/
|
|||||||
|
|
||||||
# new machine (with terraria-server-manager installed, and the same zip in dist/)
|
# new machine (with terraria-server-manager installed, and the same zip in dist/)
|
||||||
sudo terractl import calamity /tmp/calamity.tar.gz
|
sudo terractl import calamity /tmp/calamity.tar.gz
|
||||||
sudo systemctl enable --now terraria@calamity
|
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'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Turning on the daemon
|
||||||
|
|
||||||
|
On the host to be managed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo terractl daemon enable
|
||||||
|
```
|
||||||
|
|
||||||
|
This 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Adding a remote
|
||||||
|
|
||||||
|
Each computer that should manage the host needs a one-time token. On the host with the daemon:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo terractl daemon trust add laptop
|
||||||
|
```
|
||||||
|
|
||||||
|
`laptop` is a name for the computer, used in the host's log and to revoke it later. The command prints what to run on that computer:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
terractl remote add nas tctl1_eyJ2IjoxLCJmcCI6...
|
||||||
|
```
|
||||||
|
|
||||||
|
`nas` is the name you'll use for the host there; choose any. The token contains the host's addresses, so usually nothing else is needed. If none of them works from the computer (behind NAT, for example), add the address: `terractl remote add nas 203.0.113.5 tctl1_...`, or `nas.example.com:7800` for another port.
|
||||||
|
|
||||||
|
A token works **once**, **within 24 hours**. 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 trust list` shows the trusted computers, and `sudo terractl daemon trust remove 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
|
## 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`):
|
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`):
|
||||||
@@ -635,19 +783,27 @@ The servers always run as the `terraria` user. Administration is simplest with `
|
|||||||
| Command | Who can run it | Notes |
|
| Command | Who can run it | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `new` | root (or `terraria`) | Run as root, it sets ownership of the new instance to `terraria` itself. |
|
| `new` | root (or `terraria`) | Run as root, it sets ownership of the new instance to `terraria` itself. |
|
||||||
| `list` | root, or members of the `terraria` group | `/var/lib/terraria` is not readable by other users. |
|
| `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 | |
|
| 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. |
|
| `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`. |
|
| `cmd` | root, or members of the `terraria` group | Same as `attach`. |
|
||||||
|
| `watch` | root, or members of the `terraria` group | Reads the servers' journal only. |
|
||||||
| `reinstall` | root (or `terraria`) | |
|
| `reinstall` | root (or `terraria`) | |
|
||||||
| `mods <name>` (list) | root, or members of the `terraria` group | |
|
| `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`. |
|
| `mods add/enable/disable/remove` | root (or `terraria`) | Run as root, it sets ownership of the files it installs to `terraria`. |
|
||||||
| `delete` | root only | Checked by the script. |
|
| `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. |
|
| `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`. |
|
| `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. |
|
| `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. |
|
||||||
| `stop` | Only the service | Needs the server's process ID from systemd. |
|
| `service-stop` | Only the service | Needs the server's process ID from systemd. |
|
||||||
| `systemctl start/stop/enable` | root | |
|
| `daemon enable`, `daemon disable`, `daemon trust ...` | 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 --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. |
|
| `journalctl -u terraria@...` (system journal) | root, or members of `systemd-journal` or `adm` | systemd's start/stop messages. |
|
||||||
|
|
||||||
@@ -682,14 +838,14 @@ Editing an existing file with `sudo nano` keeps its owner, so it's fine.
|
|||||||
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.
|
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
|
```bash
|
||||||
sudo systemctl stop terraria@calamity
|
sudo terractl stop calamity
|
||||||
sudo terractl run calamity # automatically runs as 'terraria'
|
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:
|
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
|
```bash
|
||||||
sudo systemctl start terraria@calamity
|
sudo terractl start calamity
|
||||||
```
|
```
|
||||||
|
|
||||||
### How running servers are protected
|
### How running servers are protected
|
||||||
@@ -702,7 +858,7 @@ While a server runs, whether systemd started it or you did, it holds a lock on t
|
|||||||
- **`delete` stops the service**, but refuses to go on if a copy started by hand is still 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)`.
|
- **`list` shows a copy started by hand** as `running (outside systemd)`.
|
||||||
|
|
||||||
`terractl stop` is only for the service's own use. Typed by hand, it tells you to use `systemctl stop terraria@<name>` instead.
|
`terractl stop` only stops the service. A copy started by hand is stopped by typing `exit` in its terminal.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -741,7 +897,7 @@ Run `terractl mods <name>` to check for mods that are enabled but missing. Then
|
|||||||
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.
|
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".**
|
**"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 `sudo systemctl stop terraria@<name>`.
|
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.**
|
**The service starts but you can't connect.**
|
||||||
Check the port in `serverconfig.txt`, your firewall (TCP), and router port forwarding.
|
Check the port in `serverconfig.txt`, your firewall (TCP), and router port forwarding.
|
||||||
@@ -750,11 +906,20 @@ Check the port in `serverconfig.txt`, your firewall (TCP), and router port forwa
|
|||||||
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.
|
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.**
|
**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 `systemctl start`.
|
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.**
|
**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.
|
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 trust remove`, or that computer was added again under the same name elsewhere. Get a new token.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Tab completion
|
## Tab completion
|
||||||
@@ -765,7 +930,8 @@ It was generated before the mods were added. Delete the world files in `data/Wor
|
|||||||
- instance names;
|
- instance names;
|
||||||
- `vanilla`/`tmodloader` for `new`;
|
- `vanilla`/`tmodloader` for `new`;
|
||||||
- common server console commands for `cmd` (`save`, `say`, `playing`, `kick`...);
|
- common server console commands for `cmd` (`save`, `say`, `playing`, `kick`...);
|
||||||
- the options of `reinstall`, `delete` and `export`;
|
- the options of `enable`, `disable`, `watch`, `reinstall`, `delete`, `export`, `copy` and `move`;
|
||||||
|
- 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`;
|
- `.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.
|
- `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.
|
||||||
|
|
||||||
@@ -779,8 +945,13 @@ Completion runs as *you*, even when you type `sudo`. Instance and mod names ther
|
|||||||
|
|
||||||
```
|
```
|
||||||
terractl new <name> [vanilla|tmodloader] [port] Create an instance (not started)
|
terractl new <name> [vanilla|tmodloader] [port] Create an instance (not started)
|
||||||
terractl list Show instances, type, port, state
|
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 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 cmd <name> <command...> Send one console command
|
||||||
terractl reinstall <name> [--clean] Re-unpack server/ on next start
|
terractl reinstall <name> [--clean] Re-unpack server/ on next start
|
||||||
(--clean also re-downloads .NET)
|
(--clean also re-downloads .NET)
|
||||||
@@ -789,15 +960,30 @@ terractl mods <name> add <folder|file.tmod>... Install and enable mods (and M
|
|||||||
terractl mods <name> enable|disable <Mod>... Change which mods load
|
terractl mods <name> enable|disable <Mod>... Change which mods load
|
||||||
terractl mods <name> remove <Mod>... Uninstall mods
|
terractl mods <name> remove <Mod>... Uninstall mods
|
||||||
terractl delete <name> [-y] [--no-backup] Stop, disable, back up and remove
|
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]
|
terractl export <name> [file.tar.gz] [--with-server]
|
||||||
Archive an instance (configs, worlds, mods)
|
Archive an instance (configs, worlds, mods)
|
||||||
terractl import <name> <file.tar.gz> Create an instance from an archive
|
terractl import <name> <file.tar.gz> Create an instance from an archive
|
||||||
terractl --version Show the installed version
|
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> Add a remote (token from 'daemon trust add')
|
||||||
|
terractl remote add <name> <address> [<token>] ... at an address the token doesn't list
|
||||||
|
terractl remote list | remove <name> Show or forget remotes
|
||||||
|
terractl daemon enable|disable|status Let other hosts manage this one
|
||||||
|
terractl daemon trust add|remove <name> Create a token for a computer, or revoke one
|
||||||
|
terractl daemon trust list Show trusted computers and unused tokens
|
||||||
|
|
||||||
Used by the service:
|
Used by the service:
|
||||||
terractl prepare <name> Create dirs/configs, unpack zip if needed
|
terractl prepare <name> Create dirs/configs, unpack zip if needed
|
||||||
terractl run <name> Run the server
|
terractl run <name> Run the server
|
||||||
terractl stop <name> <pid> Save and shut down via the console (service only)
|
terractl service-stop <name> <pid> Save and shut down via the console (service only)
|
||||||
|
terractl daemon serve Run the daemon (terractld.service)
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
Binary file not shown.
Executable
+1949
File diff suppressed because it is too large.
Load diff
@@ -5,8 +5,8 @@ Priority: optional
|
|||||||
Architecture: all
|
Architecture: all
|
||||||
Installed-Size: @INSTALLED_SIZE@
|
Installed-Size: @INSTALLED_SIZE@
|
||||||
Maintainer: Your Name <you@example.com>
|
Maintainer: Your Name <you@example.com>
|
||||||
Depends: unzip, systemd
|
Depends: unzip, systemd, python3, openssl
|
||||||
Recommends: curl, libicu76 | libicu74 | libicu72 | libicu70 | libicu-dev
|
Recommends: curl, libicu76 | libicu74 | libicu72 | libicu70 | libicu-dev, polkitd
|
||||||
Conflicts: terraria-instance
|
Conflicts: terraria-instance
|
||||||
Replaces: terraria-instance
|
Replaces: terraria-instance
|
||||||
Suggests: bash-completion
|
Suggests: bash-completion
|
||||||
@@ -18,6 +18,11 @@ Description: run multiple Terraria and tModLoader dedicated servers with systemd
|
|||||||
Server software is not included. Place the official server zips in
|
Server software is not included. Place the official server zips in
|
||||||
/var/lib/terraria/dist and they are unpacked into each instance on demand.
|
/var/lib/terraria/dist and they are unpacked into each instance on demand.
|
||||||
.
|
.
|
||||||
curl and ICU are only needed for tModLoader instances.
|
.
|
||||||
|
Hosts can also be managed from other hosts (terractl --host, copy, move)
|
||||||
|
through the optional terractld daemon.
|
||||||
|
.
|
||||||
|
curl and ICU are only needed for tModLoader instances. polkitd lets members
|
||||||
|
of the terraria group start, stop and restart servers without sudo.
|
||||||
.
|
.
|
||||||
This package replaces terraria-instance (the same tool under its old name).
|
This package replaces terraria-instance (the same tool under its old name).
|
||||||
@@ -19,6 +19,11 @@ case "$1" in
|
|||||||
for unit in $running; do echo " sudo systemctl restart $unit"; done
|
for unit in $running; do echo " sudo systemctl restart $unit"; done
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
# The daemon (if it's on) runs the new version right away; it only serves
|
||||||
|
# other hosts, so restarting it doesn't affect the game servers
|
||||||
|
if [ -d /run/systemd/system ]; then
|
||||||
|
systemctl try-restart terractld.service || true
|
||||||
|
fi
|
||||||
# Running instances are not restarted; they pick up changes on their next start.
|
# Running instances are not restarted; they pick up changes on their next start.
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ case "$1" in
|
|||||||
# Forget which instances were enabled at boot
|
# Forget which instances were enabled at boot
|
||||||
rm -f /etc/systemd/system/multi-user.target.wants/terraria@*.service
|
rm -f /etc/systemd/system/multi-user.target.wants/terraria@*.service
|
||||||
rm -rf /etc/systemd/system/terraria@.service.d
|
rm -rf /etc/systemd/system/terraria@.service.d
|
||||||
|
# The daemon: whether it's on, its certificate and the computers it trusts
|
||||||
|
rm -f /etc/systemd/system/multi-user.target.wants/terractld.service
|
||||||
|
rm -rf /etc/systemd/system/terractld.service.d /var/lib/terractl
|
||||||
cat >&2 <<MSG
|
cat >&2 <<MSG
|
||||||
terraria-server-manager purged. Worlds and instances in /var/lib/terraria and the
|
terraria-server-manager purged. Worlds and instances in /var/lib/terraria and the
|
||||||
'terraria' user were kept. Remove them manually if they are no longer needed:
|
'terraria' user were kept. Remove them manually if they are no longer needed:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ set -e
|
|||||||
case "$1" in
|
case "$1" in
|
||||||
remove|deconfigure)
|
remove|deconfigure)
|
||||||
if [ -d /run/systemd/system ]; then
|
if [ -d /run/systemd/system ]; then
|
||||||
|
systemctl stop terractld.service || true
|
||||||
units=$(systemctl list-units --plain --no-legend --all 'terraria@*.service' | awk '{print $1}')
|
units=$(systemctl list-units --plain --no-legend --all 'terraria@*.service' | awk '{print $1}')
|
||||||
if [ -n "$units" ]; then
|
if [ -n "$units" ]; then
|
||||||
echo "Stopping Terraria instances: $units"
|
echo "Stopping Terraria instances: $units"
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=terractl daemon: lets other hosts manage this host's Terraria servers
|
||||||
|
Documentation=file://@DOCDIR@/README.md
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
# Listens on TERRACTL_LISTEN from /etc/default/terractl (default: all addresses,
|
||||||
|
# port 7776). Its certificate and the list of trusted computers are kept in
|
||||||
|
# /var/lib/terractl. Manage it with: terractl daemon enable|disable|status|trust
|
||||||
|
ExecStart=@BINDIR@/terractl daemon serve
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
StateDirectory=terractl
|
||||||
|
StateDirectoryMode=0755
|
||||||
|
|
||||||
|
# Runs as root: it creates, deletes, starts and stops instances for trusted
|
||||||
|
# computers, like sudo terractl does. Everything else is locked down.
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectSystem=full
|
||||||
|
ProtectHome=true
|
||||||
|
PrivateTmp=true
|
||||||
|
PrivateDevices=true
|
||||||
|
ProtectKernelTunables=true
|
||||||
|
ProtectKernelModules=true
|
||||||
|
ProtectControlGroups=true
|
||||||
|
LockPersonality=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -18,7 +18,7 @@ ExecStartPre=@BINDIR@/terractl prepare %i
|
|||||||
# Execs the vanilla or tModLoader server depending on instance.conf
|
# Execs the vanilla or tModLoader server depending on instance.conf
|
||||||
ExecStart=@BINDIR@/terractl run %i
|
ExecStart=@BINDIR@/terractl run %i
|
||||||
# Sends "exit" to the console (saves the world) and waits for shutdown
|
# Sends "exit" to the console (saves the world) and waits for shutdown
|
||||||
ExecStop=@BINDIR@/terractl stop %i $MAINPID
|
ExecStop=@BINDIR@/terractl service-stop %i $MAINPID
|
||||||
|
|
||||||
StandardInput=socket
|
StandardInput=socket
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
|
|||||||
Reference in new issue
Block a user