From 8a509f139873cb870eb78f8753eb40da824de4fe Mon Sep 17 00:00:00 2001 From: adam Date: Fri, 2 Oct 2026 22:03:43 +0000 Subject: [PATCH] check the terraria user can run terractl before switching to it upgrade (before stopping anything) and prepare/run started as root now refuse with an explanation when terractl is somewhere the terraria user can't open, such as a copy under /root, instead of failing with "env: Permission denied". upgrade also says when it started the server again after a failure. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + bin/terractl | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd64662..2c924c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - `list`, `new`, `import` and `enable` warn when `TERRARIA_BASE` or `TERRARIA_DIST` is somewhere the services' sandbox doesn't allow (the instances folder not in `ReadWritePaths=`, or either folder under `/home`), and print the exact drop-in to add. The daemon (`terractld.service`) is checked too when it's on. A server that can't create its folder now says why in its log, instead of only "Read-only file system". - The guide's "Changing locations" now covers bind mounts (the simplest way to use another disk) as well as drop-ins, and a new section explains keeping instances or zips under `/home`. - `journal-access` is offered by tab completion and listed in the guide's command reference. +- `upgrade`, and `prepare`/`run` started as root, check first that the `terraria` user can run the terractl being used. A copy under `/root` is refused with an explanation, before `upgrade` stops the server, instead of failing with `env: Permission denied`. - `terractl` run by a relative path (e.g. `sudo ./bin/terractl prepare ` from a source tree) no longer fails to switch to the `terraria` user. - New `fix-perms` command: `sudo terractl fix-perms ` gives every file of an instance back to the `terraria` user and reapplies the usual modes (group can read, others nothing; server binaries executable), e.g. after copying worlds in as root. Symlinks are not followed. Works with `--host` too. - `list` warns about zips in `dist/` that the `terraria` user can't read (e.g. copied in with `scp -p` from a private file), and `info` lists them as `unreadable_zip=`. A server that fails to install because of one now says how to fix it (`sudo chmod 644 `) instead of just "cannot read", and a missing zip is reported as not found. diff --git a/bin/terractl b/bin/terractl index 41907c3..1812790 100755 --- a/bin/terractl +++ b/bin/terractl @@ -1074,6 +1074,15 @@ svc_can_read() { else (( 8#$mode & 0004 )); fi } +# Check that the service user can run this script, which prepare/run (as root) +# and upgrade switch to. Not so for a copy in a private folder, such as a +# development copy under /root. +need_svc_can_run_self() { + command -v runuser >/dev/null || die "runuser not found; run as '$SVC_USER' instead" + runuser -u "$SVC_USER" -- test -x "$SELF" 2>/dev/null || + die "$1 runs $SELF as the '$SVC_USER' user, who can't open it (it's in a folder only root can open, e.g. /root). Use the installed terractl, or a copy somewhere '$SVC_USER' can read." +} + # Zips in dist/ the servers can't unpack, e.g. copied in with 'scp -p' from a # private file. Instances fail on their first start with such a zip. unreadable_zips() { @@ -1373,6 +1382,7 @@ cmd_upgrade() { fi command -v unzip >/dev/null || die "unzip is not installed" + need_svc_can_run_self "upgrade" listing=$(unzip -Z1 "$f" 2>/dev/null) || die "$f isn't a readable zip (damaged, or not fully downloaded?)" case $TYPE in vanilla) grep -q '/Linux/TerrariaServer\.bin\.x86_64$' <<< "$listing" || @@ -1402,7 +1412,7 @@ cmd_upgrade() { backup_file+=.tar.gz # In a subshell, so a failure comes back here instead of exiting if ! ( trap cleanup EXIT; cmd_export "$name" "$backup_file" ) >/dev/null; then - if [[ $was_running -eq 1 ]]; then systemctl start "$unit"; fi + if [[ $was_running -eq 1 ]]; then systemctl start "$unit"; echo "$unit started again"; fi die "backing up '$name' failed, so its version wasn't changed" fi echo "backup of worlds and configs: $backup_file" @@ -1414,7 +1424,7 @@ cmd_upgrade() { # new server/ is complete, the old one is left in place. if ! runuser -u "$SVC_USER" -- env HOME="$dir" "$SELF" prepare "$name"; then set_zip_pin "$name" "$old_pin" - if [[ $was_running -eq 1 ]]; then systemctl start "$unit"; fi + if [[ $was_running -eq 1 ]]; then systemctl start "$unit"; echo "$unit started again"; fi die "unpacking $zip failed (see above); '$name' still runs ${installed:-its old version}" fi @@ -1529,7 +1539,7 @@ valid_name "$1" # debugging), switch to the service user so nothing ends up owned by root. if [[ $command == prepare || $command == run ]]; then if [[ $EUID -eq 0 ]]; then - command -v runuser >/dev/null || die "runuser not found; run as '$SVC_USER' instead" + need_svc_can_run_self "'$command'" exec runuser -u "$SVC_USER" -- env HOME="$(inst_dir "$1")" \ "$SELF" "$command" "$@" elif [[ $(id -un) != "$SVC_USER" ]]; then