From 675f5308e64eecaee3a2be6c24af9c436480ddc2 Mon Sep 17 00:00:00 2001 From: adam Date: Mon, 28 Sep 2026 17:18:28 +0000 Subject: [PATCH] bash autocompletions --- CHANGELOG.md | 8 +++ Makefile | 10 +++- README.md | 2 + VERSION | 2 +- completions/bash/terractl | 119 ++++++++++++++++++++++++++++++++++++++ docs/guide.md | 20 ++++++- packaging/deb/control | 1 + 7 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 completions/bash/terractl diff --git a/CHANGELOG.md b/CHANGELOG.md index 92db403..51b22a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 2.1.0 - 2026-09-28 + +- Bash completion for `terractl`: commands, instance names, `new` types, console commands for `cmd`, `reinstall`/`delete` options, and `mods` actions and mod names. Installed to `share/bash-completion/completions/terractl` by both `make install` and the package. + +## 2.0.1 - 2026-09-28 + +- Fixed ShellCheck warnings that made `make check` (and therefore `make deb`) fail when ShellCheck is installed: simplified the development-copy check in `--version` (SC2140), and marked the intentional unquoted unit list in `prerm` (SC2086). + ## 2.0.0 - 2026-09-28 - Renamed the command from `terraria-instance` to `terractl`, and the package from `terraria-instance` to `terraria-server-manager`. The new package replaces the old one automatically; see "Upgrading from the terraria-instance package" in the guide. diff --git a/Makefile b/Makefile index 0198b23..9baa815 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ SYSUSERSDIR := $(PREFIX)/lib/sysusers.d TMPFILESDIR := $(PREFIX)/lib/tmpfiles.d DOCDIR := $(PREFIX)/share/doc/$(NAME) DATADIR := $(PREFIX)/share/$(NAME) +COMPLETIONDIR := $(PREFIX)/share/bash-completion/completions # Fill in version and install paths SUBST = sed -e 's|@VERSION@|$(VERSION)|g' -e 's|@BINDIR@|$(BINDIR)|g' -e 's|@DOCDIR@|$(DOCDIR)|g' @@ -31,9 +32,10 @@ all: check check: bash -n bin/terractl + bash -n completions/bash/terractl for f in $(MAINT_SCRIPTS); do sh -n $$f; done @if command -v shellcheck >/dev/null; then \ - shellcheck bin/terractl $(MAINT_SCRIPTS); \ + shellcheck bin/terractl completions/bash/terractl $(MAINT_SCRIPTS); \ else \ echo "shellcheck not installed; skipped (apt install shellcheck)"; \ fi @@ -41,7 +43,7 @@ check: install: install -d $(DESTDIR)$(BINDIR) $(DESTDIR)$(UNITDIR) $(DESTDIR)$(SYSUSERSDIR) \ $(DESTDIR)$(TMPFILESDIR) $(DESTDIR)$(DOCDIR) $(DESTDIR)$(DATADIR) \ - $(DESTDIR)$(SYSCONFDIR)/default + $(DESTDIR)$(COMPLETIONDIR) $(DESTDIR)$(SYSCONFDIR)/default $(SUBST) bin/terractl > $(DESTDIR)$(BINDIR)/terractl chmod 755 $(DESTDIR)$(BINDIR)/terractl $(SUBST) systemd/terraria@.service > $(DESTDIR)$(UNITDIR)/terraria@.service @@ -50,6 +52,7 @@ install: install -m 644 config/tmpfiles.d/terractl.conf $(DESTDIR)$(TMPFILESDIR)/ install -m 644 docs/guide.md $(DESTDIR)$(DOCDIR)/README.md install -m 644 tools/windows/export-tmod-mods.ps1 $(DESTDIR)$(DATADIR)/ + install -m 644 completions/bash/terractl $(DESTDIR)$(COMPLETIONDIR)/terractl @# Never overwrite local settings @if [ -e $(DESTDIR)$(SYSCONFDIR)/default/terractl ]; then \ echo "keeping existing $(DESTDIR)$(SYSCONFDIR)/default/terractl"; \ @@ -67,7 +70,8 @@ install: uninstall: rm -f $(DESTDIR)$(BINDIR)/terractl \ $(DESTDIR)$(UNITDIR)/terraria@.service $(DESTDIR)$(UNITDIR)/terraria@.socket \ - $(DESTDIR)$(SYSUSERSDIR)/terractl.conf $(DESTDIR)$(TMPFILESDIR)/terractl.conf + $(DESTDIR)$(SYSUSERSDIR)/terractl.conf $(DESTDIR)$(TMPFILESDIR)/terractl.conf \ + $(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." diff --git a/README.md b/README.md index c388385..842b029 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ sudo terractl attach calamity - **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. - **Mod management.** `terractl mods` installs, enables and removes mods, with correct file ownership. +- **Tab completion** for commands, instance names, mods and console commands. - **Hardened.** Servers run as an unprivileged `terraria` user and can only write to their own data. - **Helper for Windows.** A PowerShell script exports your enabled tModLoader mods for the server. @@ -48,6 +49,7 @@ See [docs/guide.md](docs/guide.md). It covers installation, creating instances, ``` bin/terractl The command-line tool systemd/ terraria@.service and terraria@.socket +completions/bash/ Bash completion for terractl config/ /etc/default settings, sysusers.d and tmpfiles.d entries packaging/deb/ Debian package metadata and install/remove scripts docs/guide.md Full documentation diff --git a/VERSION b/VERSION index 227cea2..7ec1d6d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.0.0 +2.1.0 diff --git a/completions/bash/terractl b/completions/bash/terractl new file mode 100644 index 0000000..c017fdb --- /dev/null +++ b/completions/bash/terractl @@ -0,0 +1,119 @@ +# shellcheck shell=bash +# Bash completion for terractl (terraria-server-manager) +# +# Installed to /share/bash-completion/completions/terractl, where the +# bash-completion package loads it automatically. Also works with 'sudo terractl'. + +# Instances directory: default, or TERRARIA_BASE from /etc/default/terractl. +# The file is parsed rather than sourced, so nothing runs in your shell. +_terractl_base() { + local base=/var/lib/terraria/instances value + if [[ -r /etc/default/terractl ]]; then + value=$(sed -n 's/^[[:space:]]*TERRARIA_BASE=//p' /etc/default/terractl | tail -n 1) + value=${value%\"}; value=${value#\"}; value=${value%\'}; value=${value#\'} + if [[ -n $value ]]; then base=$value; fi + fi + printf '%s\n' "$base" +} + +# Instance names. Reading the directory needs root or the 'terraria' group; if +# it isn't readable, fall back to the terraria@ services systemd knows about. +_terractl_instances() { + local base conf + base=$(_terractl_base) + if [[ -r $base && -x $base ]]; then + while IFS= read -r conf; do + conf=${conf%/instance.conf} + printf '%s\n' "${conf##*/}" + done < <(compgen -G "$base/*/instance.conf") + elif command -v systemctl >/dev/null 2>&1; then + systemctl list-units --all --plain --no-legend 'terraria@*.service' 2>/dev/null | + sed -n 's/^terraria@\([^ ]*\)\.service.*/\1/p' + fi +} + +# Mods installed in an instance (*.tmod files), or enabled ones (enabled.json) +_terractl_mods() { + local mdir f + mdir="$(_terractl_base)/$1/data/Mods" + if [[ $2 == enabled ]]; then + [[ -r $mdir/enabled.json ]] && grep -o '"[^"]*"' "$mdir/enabled.json" | tr -d '"' + else + while IFS= read -r f; do + f=${f##*/} + printf '%s\n' "${f%.tmod}" + done < <(compgen -G "$mdir/*.tmod") + fi +} + +# Set COMPREPLY to the words matching what's typed so far +_terractl_reply() { + mapfile -t COMPREPLY < <(compgen -W "$1" -- "$cur") +} + +_terractl() { + local cur words cword + if declare -F _init_completion >/dev/null 2>&1; then + # shellcheck disable=SC2034 # prev is set by _init_completion but not needed here + local prev + _init_completion || return + else + cur=${COMP_WORDS[COMP_CWORD]} + words=("${COMP_WORDS[@]}") + cword=$COMP_CWORD + fi + + local commands="new list attach cmd mods reinstall delete prepare run --help --version" + local console="save say playing kick ban motd password time dawn noon dusk midnight settle seed version maxplayers port clear help" + + if (( cword == 1 )); then + _terractl_reply "$commands" + return + fi + + local command=${words[1]} + case $command in + list|help|-h|--help|version|-V|--version) + return ;; + new) + # new [vanilla|tmodloader] [port]: the name is new, so nothing to offer + if (( cword == 3 )); then _terractl_reply "vanilla tmodloader"; fi + return ;; + esac + + if (( cword == 2 )); then + _terractl_reply "$(_terractl_instances)" + return + fi + + local instance=${words[2]} + case $command in + cmd) + if (( cword == 3 )); then _terractl_reply "$console"; fi ;; + reinstall) + if (( cword == 3 )); then _terractl_reply "--clean"; fi ;; + delete) + _terractl_reply "-y --yes --no-backup" ;; + mods) + if (( cword == 3 )); then + _terractl_reply "list add enable disable remove" + return + fi + case ${words[3]} in + add) + # .tmod files and folders (e.g. the Windows export folder) + if declare -F _filedir >/dev/null 2>&1; then + _filedir tmod + else + compopt -o filenames 2>/dev/null + mapfile -t COMPREPLY < <(compgen -d -- "$cur"; compgen -f -X '!*.tmod' -- "$cur") + fi ;; + enable|remove) + _terractl_reply "$(_terractl_mods "$instance" installed)" ;; + disable) + _terractl_reply "$(_terractl_mods "$instance" enabled)" ;; + esac ;; + esac +} + +complete -F _terractl terractl diff --git a/docs/guide.md b/docs/guide.md index b5cf466..79f08e3 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -21,7 +21,8 @@ Run any number of vanilla Terraria and tModLoader dedicated servers on one Linux 15. [Permissions](#permissions) 16. [Running by hand for debugging](#running-by-hand-for-debugging) 17. [Troubleshooting](#troubleshooting) -18. [Command reference](#command-reference) +18. [Tab completion](#tab-completion) +19. [Command reference](#command-reference) --- @@ -669,6 +670,23 @@ It was generated before the mods were added. Delete the world files in `data/Wor --- +## Tab completion + +`terractl` comes with bash completion. Press Tab to complete: + +- command names; +- instance names; +- `vanilla`/`tmodloader` for `new`; +- common server console commands for `cmd` (`save`, `say`, `playing`, `kick`...); +- the options of `reinstall` and `delete`; +- `mods` actions. For `mods add` it completes `.tmod` files and folders; for `mods enable` and `mods remove`, the installed mods; for `mods disable`, the enabled ones. + +It works with `sudo terractl` too. It needs the `bash-completion` package, which most Debian and Ubuntu systems already have (`sudo apt install bash-completion`). It takes effect in new terminal sessions. + +Completion runs as *you*, even when you type `sudo`. Instance and mod names therefore only complete if you can read `/var/lib/terraria`, which means being root or a member of the `terraria` group (see [Permissions](#permissions)). Otherwise, it falls back to the names of servers systemd currently knows about, and mod names don't complete. + +--- + ## Command reference ``` diff --git a/packaging/deb/control b/packaging/deb/control index fd60ec6..03a4f3d 100644 --- a/packaging/deb/control +++ b/packaging/deb/control @@ -9,6 +9,7 @@ Depends: unzip, systemd Recommends: curl, libicu76 | libicu74 | libicu72 | libicu70 | libicu-dev Conflicts: terraria-instance Replaces: terraria-instance +Suggests: bash-completion Description: run multiple Terraria and tModLoader dedicated servers with systemd Provides the terraria@.service template and the terractl command for running any number of vanilla Terraria and tModLoader dedicated servers, each