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
+201 -21

No files matched your search

+105 -5
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."
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"
echo "sent. Output: journalctl -u terraria@$name -n 20"
# 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."
# Server output comes from the journal; -o cat drops the timestamp prefixes.
journalctl -u "terraria@$name" -f -n 30 -o cat &
ATTACH_PID=$! # killed by cleanup() however attach ends
if can_read_journal; then
# Server output comes from the journal; -o cat drops the timestamp prefixes.
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"