use journal namespace

This commit is contained in:
adam committed 2026-09-30 15:06:48 +00:00
1 parent ae4299a570
commit 4f0886b6cd
11 files changed
+199 -19

No files matched your search

+14
View File
@@ -1,5 +1,19 @@
# Changelog
## 0.0.11 - 2026-09-30
- The servers now log to their own journal (systemd log namespace `terraria`), which members of the `terraria` group can read. `attach` and `cmd` therefore show output to `terraria` group members without `sudo` or the `systemd-journal` group, and without access to the rest of the system's logs. Read it directly with `journalctl --namespace=terraria -u terraria@<name>`.
- 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.
- 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".
- Upgrading: running servers keep logging to the system journal until restarted; the package lists the ones to restart.
## 0.0.10 - 2026-09-30
- `cmd` now prints the server's reply to the command, instead of only saying where to find it. It waits until the server has been quiet for a second, up to 5 seconds.
- `attach` and `cmd` now explain when you can send commands but not see the server's output. That happens for members of the `terraria` group who aren't root or in `systemd-journal`. Previously `attach` showed nothing, without saying why.
- Replaced a `${var:+...}` expansion with an explicit `if` in `import`'s summary message (ShellCheck SC2016). The output is unchanged.
## 0.0.9 - 2026-09-28
- New `export` command: `terractl export <name> [file.tar.gz] [--with-server]` archives an instance's `instance.conf`, `serverconfig.txt` and `data/` (worlds, mods, mod configs). If the server is running under systemd, it is asked to save first. Archives are created readable only by their owner, since `serverconfig.txt` can contain the server password. `--with-server` also includes the unpacked server software (and tModLoader's .NET), for hosts without internet access.
+9 -2
View File
@@ -43,7 +43,7 @@ check:
install:
install -d $(DESTDIR)$(BINDIR) $(DESTDIR)$(UNITDIR) $(DESTDIR)$(SYSUSERSDIR) \
$(DESTDIR)$(TMPFILESDIR) $(DESTDIR)$(DOCDIR) $(DESTDIR)$(DATADIR) \
$(DESTDIR)$(COMPLETIONDIR) $(DESTDIR)$(SYSCONFDIR)/default
$(DESTDIR)$(COMPLETIONDIR) $(DESTDIR)$(SYSCONFDIR)/default $(DESTDIR)$(SYSCONFDIR)/systemd
$(SUBST) bin/terractl > $(DESTDIR)$(BINDIR)/terractl
chmod 755 $(DESTDIR)$(BINDIR)/terractl
$(SUBST) systemd/terraria@.service > $(DESTDIR)$(UNITDIR)/terraria@.service
@@ -59,11 +59,17 @@ install:
else \
install -m 644 config/default/terractl $(DESTDIR)$(SYSCONFDIR)/default/terractl; \
fi
@if [ -e $(DESTDIR)$(SYSCONFDIR)/systemd/journald@terraria.conf ]; then \
echo "keeping existing $(DESTDIR)$(SYSCONFDIR)/systemd/journald@terraria.conf"; \
else \
install -m 644 config/journald/journald@terraria.conf $(DESTDIR)$(SYSCONFDIR)/systemd/journald@terraria.conf; \
fi
@# On a real install (not staging for a package), set up the user and directories
@if [ -z "$(DESTDIR)" ]; then \
systemd-sysusers terractl.conf && \
systemd-tmpfiles --create terractl.conf && \
if [ -d /run/systemd/system ]; then systemctl daemon-reload; fi && \
$(BINDIR)/terractl journal-access && \
echo "Installed terractl $(VERSION). See $(DOCDIR)/README.md"; \
fi
@@ -74,7 +80,8 @@ uninstall:
$(DESTDIR)$(COMPLETIONDIR)/terractl
rm -rf $(DESTDIR)$(DOCDIR) $(DESTDIR)$(DATADIR)
@if [ -z "$(DESTDIR)" ] && [ -d /run/systemd/system ]; then systemctl daemon-reload; fi
@echo "Removed. Kept: $(SYSCONFDIR)/default/terractl, /var/lib/terraria and the terraria user."
@echo "Removed. Kept: $(SYSCONFDIR)/default/terractl, $(SYSCONFDIR)/systemd/journald@terraria.conf,"
@echo "/var/lib/terraria, the servers' journal and the terraria user."
deb: check
rm -rf $(STAGE)
+1
View File
@@ -20,6 +20,7 @@ sudo terractl attach calamity
- **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.
- **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.
- **Helper for Windows.** A PowerShell script exports your enabled tModLoader mods for the server.
## Requirements
+1 -1
View File
@@ -1 +1 @@
0.0.9
0.0.11
+103 -3
View File
@@ -31,6 +31,7 @@ DIST_DIR="${TERRARIA_DIST:-/var/lib/terraria/dist}"
RUN_DIR="/run/terraria"
SVC_USER="${TERRARIA_USER:-terraria}"
STOP_TIMEOUT="${TERRARIA_STOP_TIMEOUT:-80}" # keep below TimeoutStopSec
JOURNAL_NS=terraria # LogNamespace= in terraria@.service
TERRACTL_VERSION="@VERSION@" # filled in by 'make install' / 'make deb'
die() { echo "terractl: $*" >&2; exit 1; }
@@ -59,6 +60,7 @@ Admin commands:
(worlds/configs archived to $BASE_DIR/.deleted/)
Used by terraria@.service:
journal-access let the terraria group read the servers' journal
prepare <name> create dirs/configs, unpack server if needed
run <name> exec the server
stop <name> <mainpid> graceful shutdown via console "exit" (service only;
@@ -312,13 +314,106 @@ cmd_stop() {
log "server did not exit within ${STOP_TIMEOUT}s; leaving it to systemd"
}
# The servers log to their own journal (LogNamespace=terraria), which the
# 'terraria' group can read; see cmd_journal_access. It lives in
# <machine-id>.terraria under /var/log/journal (or /run/log/journal).
journal_dirs() {
local mid base
mid=$(cat /etc/machine-id 2>/dev/null) || return 0
[[ -n $mid ]] || return 0
for base in /var/log/journal /run/log/journal; do
if [[ -d $base/$mid.$JOURNAL_NS ]]; then printf '%s\n' "$base/$mid.$JOURNAL_NS"; fi
done
}
# Checks the actual permissions rather than guessing from group names. The
# folder itself is world-readable, so the journal file inside is what counts.
# No folder or file yet means nothing has been logged, so nothing to hide.
can_read_journal() {
if [[ $EUID -eq 0 ]]; then return 0; fi
local d
while IFS= read -r d; do
if [[ ! -r $d || ! -x $d ]]; then return 1; fi
if [[ -e $d/system.journal && ! -r $d/system.journal ]]; then return 1; fi
done < <(journal_dirs)
return 0
}
journal_hint() {
{
echo "Note: commands are sent, but you can't see the server's output."
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 "yet. That happens automatically when a server (re)starts, or run once:"
echo " sudo terractl journal-access"
else
echo "Reading it needs root or the '$SVC_USER' group. If you were just added to it,"
echo "log out and back in. Or use sudo."
fi
} >&2
}
# Run as root before every server start (ExecStartPre=+ in terraria@.service).
# Creates the servers' journal folder so it can be given to the terraria group
# before journald writes to it, then applies the access rules from tmpfiles.d
# (which also repair files journald created earlier). Only creates the folder
# where journald already keeps journals: creating /var/log/journal itself would
# switch the whole system to persistent logging. Never blocks a server start.
cmd_journal_access() {
[[ $EUID -eq 0 ]] || die "journal-access must run as root"
local mid base
mid=$(cat /etc/machine-id 2>/dev/null) || return 0
[[ -n $mid ]] || return 0
for base in /var/log/journal /run/log/journal; do
if [[ -d $base && ! -d $base/$mid.$JOURNAL_NS ]]; then
# inherits journald's group (systemd-journal) through the setgid parent
mkdir -m 0755 "$base/$mid.$JOURNAL_NS" 2>/dev/null ||
log "warning: could not create $base/$mid.$JOURNAL_NS"
fi
done
systemd-tmpfiles --create --prefix=/var/log/journal --prefix=/run/log/journal \
terractl.conf 2>/dev/null || log "warning: could not set journal access for group '$SVC_USER'"
}
cmd_cmd() {
local name=$1; shift
[[ $# -gt 0 ]] || die "no console command given"
local fifo="$RUN_DIR/$name.stdin"
local fifo="$RUN_DIR/$name.stdin" unit="terraria@$name" cursor="" out="" prev=-1 quiet=0 i
[[ -p $fifo ]] || die "$fifo not found - is terraria@$name running?"
[[ -w $fifo ]] || die "no write access to $fifo (run with sudo or join group '$SVC_USER')"
if ! can_read_journal; then
printf '%s\n' "$*" > "$fifo"
echo "sent. Output: journalctl -u terraria@$name -n 20"
echo "sent."
journal_hint
return 0
fi
# Note the journal position first, so only the reply to this command is shown
cursor=$(journalctl --namespace="$JOURNAL_NS" -u "$unit" -n 1 --show-cursor -q -o cat 2>/dev/null |
sed -n 's/^-- cursor: //p') || true
local -a after=(--since=-2s)
if [[ -n $cursor ]]; then after=(--after-cursor="$cursor"); fi
printf '%s\n' "$*" > "$fifo"
# Show the server's output until it has been quiet for 1s (at most 5s)
for ((i = 0; i < 50; i++)); do
sleep 0.1
out=$(journalctl --namespace="$JOURNAL_NS" -u "$unit" "${after[@]}" -q -o cat 2>/dev/null) || true
if [[ -n $out && ${#out} -eq $prev ]]; then
quiet=$((quiet + 1))
if [[ $quiet -ge 10 ]]; then break; fi
else
quiet=0
fi
prev=${#out}
done
if [[ -n $out ]]; then
printf '%s\n' "$out"
else
echo "sent; no output from the server within 5s (it may be busy or still starting)."
echo "Watch it with: terractl attach $name"
fi
}
cmd_attach() {
@@ -327,9 +422,13 @@ cmd_attach() {
[[ -w $fifo ]] || die "no write access to $fifo (run with sudo or join group '$SVC_USER')"
echo "Attached to terraria@$name. Ctrl-D or Ctrl-C detaches; the server keeps running."
if can_read_journal; then
# Server output comes from the journal; -o cat drops the timestamp prefixes.
journalctl -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
else
journal_hint
fi
trap 'echo; exit 0' INT
# -e gives line editing and history while typing commands
@@ -805,6 +904,7 @@ case $command in
else echo "terractl $TERRACTL_VERSION"; fi
exit 0 ;;
list) cmd_list; exit ;;
journal-access) cmd_journal_access; exit ;;
esac
[[ $# -ge 1 ]] || usage
valid_name "$1"
+11
View File
@@ -0,0 +1,11 @@
# Settings for the Terraria servers' own journal (LogNamespace=terraria).
# Read it with: journalctl --namespace=terraria -u terraria@<name>
# After changing this file: sudo systemctl restart systemd-journald@terraria
# All options: man journald.conf
[Journal]
# Disk space the servers' logs may use, kept separate from the system journal
SystemMaxUse=256M
RuntimeMaxUse=64M
# Uncomment to delete entries older than this
#MaxRetentionSec=3month
+7
View File
@@ -3,3 +3,10 @@ d /var/lib/terraria 0750 terraria terraria -
d /var/lib/terraria/instances 0750 terraria terraria -
# setgid + group-writable: members of the 'terraria' group can upload zips
d /var/lib/terraria/dist 2775 root terraria -
# The servers' journal (LogNamespace=terraria): readable by the terraria group,
# including files journald creates later (default ACL). 'A+' only acts on paths
# that exist and never creates them; 'terractl journal-access' creates the
# folders, and only where journald already keeps journals.
A+ /var/log/journal/%m.terraria - - - - group:terraria:r-x,default:group:terraria:r-x
A+ /run/log/journal/%m.terraria - - - - group:terraria:r-x,default:group:terraria:r-x
+34 -13
View File
@@ -50,6 +50,7 @@ Everything is installed by the `terraria-server-manager` package:
| `/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. |
| `/etc/default/terractl` | Optional overrides (locations, stop timeout). |
| `/etc/systemd/journald@terraria.conf` | Size and retention of the servers' own journal (see [Logs](#logs)). |
| `/usr/lib/sysusers.d/terractl.conf` | Defines the `terraria` system user. |
| `/usr/lib/tmpfiles.d/terractl.conf` | Creates `/var/lib/terraria` and its subdirectories. |
| `/usr/share/terraria-server-manager/export-tmod-mods.ps1` | Optional. Copy to your Windows PC to collect your enabled tModLoader mods. |
@@ -58,7 +59,7 @@ What happens when an instance starts:
1. systemd creates the console pipe `/run/terraria/<name>.stdin` (from the socket unit).
2. `terractl prepare <name>` creates the instance directory and default configs if they're missing, and unpacks the server zip if it hasn't been unpacked yet.
3. `terractl run <name>` starts the vanilla or tModLoader server, reading commands from the pipe and logging to the journal.
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.
@@ -130,7 +131,7 @@ sudo apt remove terraria-server-manager # stops all instances (worlds are s
sudo apt purge terraria-server-manager # also removes config and boot-time enablement
```
Neither command deletes `/var/lib/terraria` or the `terraria` user, so your worlds survive even a purge. To delete everything:
Neither command deletes `/var/lib/terraria`, the servers' journal or the `terraria` user, so your worlds survive even a purge. To delete everything:
```bash
sudo rm -rf /var/lib/terraria && sudo userdel terraria
@@ -319,7 +320,7 @@ Behaviour to be aware of:
## The server console
Each server reads console commands (`save`, `say`, `kick`, `playing`, and so on) from its pipe, and its output goes to the systemd journal.
Each server reads console commands (`save`, `say`, `kick`, `playing`, and so on) from its pipe, and its output goes to the servers' own journal.
### Interactive: `attach`
@@ -340,16 +341,32 @@ sudo terractl cmd calamity save
sudo terractl cmd calamity say Restarting in 5 minutes
```
This is useful in scripts and cron jobs. The command's output appears in the journal.
`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.
### Logs only
Members of the `terraria` group can use `attach` and `cmd` fully, output included, without `sudo`. See [Logs](#logs) for how that's arranged.
### Logs
The servers don't log into the main system journal. They have **their own journal**, a systemd *log namespace* called `terraria`. That keeps them separate for two reasons:
- **Access.** The `terraria` group is given read access to the servers' journal and nothing else, so moderators can follow the game servers without being able to read the rest of the system's logs (logins, `sudo` use, other services).
- **Space.** Chatty game servers can't push system logs out of a size-limited journal, or the other way round.
To read it with `journalctl`, add `--namespace=terraria`:
```bash
journalctl -u terraria@calamity -f # follow
journalctl -u terraria@calamity -n 100 # last 100 lines
journalctl -u terraria@calamity --since today
journalctl --namespace=terraria -u terraria@calamity -f # follow
journalctl --namespace=terraria -u terraria@calamity -n 100 # last 100 lines
journalctl --namespace=terraria -u terraria@calamity --since today
journalctl --namespace=+terraria -u terraria@calamity # also systemd's own start/stop messages
```
Plain `journalctl -u terraria@calamity`, without `--namespace`, only shows systemd's messages about the service (started, stopped, failed), not the server's output. With `+terraria`, both are shown together; that part needs root or the `systemd-journal` group, because systemd's messages are in the system journal.
**Size and retention** are set in `/etc/systemd/journald@terraria.conf`, which by default allows up to 256 MB on disk. After editing it, run `sudo systemctl restart systemd-journald@terraria`.
**How the group access works.** Before each server starts, a small root step (`terractl journal-access`) creates the servers' journal folder, `<machine-id>.terraria` under `/var/log/journal` (or `/run/log/journal` if the system doesn't keep logs across reboots). It gives that folder a read permission for the `terraria` group, which journald's files inherit, including after log rotation. It never creates `/var/log/journal` itself, because that would switch the whole system to persistent logging.
tModLoader also writes its own logs to `/var/lib/terraria/instances/<name>/server/tModLoader-Logs/`:
- `Launch.log` covers the .NET download and startup.
@@ -620,8 +637,8 @@ The servers always run as the `terraria` user. Administration is simplest with `
| `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. |
| Adding zips to `dist/` | root, or members of the `terraria` group | |
| `attach` | root, or members of **both** `terraria` and `systemd-journal` | Needs to write to the console pipe and read the journal. |
| `cmd` | root, or members of the `terraria` group | Writes to the console pipe. |
| `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`. |
| `reinstall` | root (or `terraria`) | |
| `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`. |
@@ -631,12 +648,13 @@ The servers always run as the `terraria` user. Administration is simplest with `
| `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. |
| `systemctl start/stop/enable` | root | |
| `journalctl -u terraria@...` | root, or members of `systemd-journal` or `adm` | |
| `journalctl --namespace=terraria ...` | root, or members of the `terraria` group | The servers' output only; see [Logs](#logs). |
| `journalctl -u terraria@...` (system journal) | root, or members of `systemd-journal` or `adm` | systemd's start/stop messages. |
To run the everyday commands without `sudo`:
```bash
sudo usermod -aG terraria,systemd-journal youruser # then log out and back in
sudo usermod -aG terraria youruser # then log out and back in
```
Members of the `terraria` group can read server files (useful for backups) but cannot change them. Files the service creates are readable, not writable, by the group.
@@ -728,6 +746,9 @@ The instance's server is already running, so the command was refused to protect
**The service starts but you can't connect.**
Check the port in `serverconfig.txt`, your firewall (TCP), and router port forwarding.
**`attach` or `cmd` sends commands but shows no output.**
Both commands print a note explaining why. If you were just added to the `terraria` group, log out and back in. If you're already in it, the servers' journal hasn't been set up for the group yet; that happens automatically when a server starts, or run `sudo terractl journal-access`. Servers that were running before upgrading to 0.0.11 keep logging to the system journal until they're restarted.
**The server isn't restarting after it stopped.**
It exited cleanly, probably via `exit` in the console, so systemd leaves it stopped. Start it again with `systemctl start`.
@@ -782,5 +803,5 @@ terractl stop <name> <pid> Save and shut down via the con
```
systemctl start|stop|restart|status terraria@<name>
systemctl enable|disable [--now] terraria@<name>
journalctl -u terraria@<name> [-f] [-n N]
journalctl --namespace=terraria -u terraria@<name> [-f] [-n N]
```
+1
View File
@@ -1 +1,2 @@
/etc/default/terractl
/etc/systemd/journald@terraria.conf
+11
View File
@@ -8,6 +8,17 @@ case "$1" in
if [ -d /run/systemd/system ]; then
systemctl daemon-reload || true
fi
# Let the terraria group read the servers' journal (LogNamespace=terraria)
terractl journal-access || true
# Servers running from before 0.0.11 still log to the system journal until
# restarted, so 'terractl attach' and 'cmd' can't show their output yet.
if [ -n "$2" ] && dpkg --compare-versions "$2" lt 0.0.11 && [ -d /run/systemd/system ]; then
running=$(systemctl list-units --plain --no-legend --state=active 'terraria@*.service' | awk '{print $1}')
if [ -n "$running" ]; then
echo "Restart running Terraria servers so they use their new journal:"
for unit in $running; do echo " sudo systemctl restart $unit"; done
fi
fi
# Running instances are not restarted; they pick up changes on their next start.
;;
esac
+7
View File
@@ -11,6 +11,8 @@ Type=simple
User=terraria
Group=terraria
# Runs as root ('+'): makes the servers' journal readable by the terraria group
ExecStartPre=+@BINDIR@/terractl journal-access
# Creates the instance directory and default configs, unpacks the server zip if needed
ExecStartPre=@BINDIR@/terractl prepare %i
# Execs the vanilla or tModLoader server depending on instance.conf
@@ -22,6 +24,11 @@ StandardInput=socket
StandardOutput=journal
StandardError=journal
SyslogIdentifier=terraria-%i
# Servers log to their own journal, so the terraria group can be given access to
# it without seeing the rest of the system's logs. Read it with:
# journalctl --namespace=terraria -u terraria@<name>
# Settings (size limits etc.): /etc/systemd/journald@terraria.conf
LogNamespace=terraria
TimeoutStartSec=10min
TimeoutStopSec=90