Files
terraria-server-manager/completions/bash/terractl
T
adamandClaude Opus 5.5 fc2c08d47b
release / deb (push) Successful in 17s
add doctor, scheduled backups and fetch; release 0.0.12
- doctor [<name>] [--fix]: checks the system (user, programs, folders,
  zips, units and leftover copies in /etc, sandbox access to moved
  folders, journal, polkit, daemon, orphaned boot links and schedules)
  and each instance (ownership, server zip, config paths, mods, ports,
  failed starts/backups), with a fix for each. --fix applies the safe
  ones and re-checks them.
- backup enable|disable|now|list: a systemd timer per instance
  (terraria-backup@.timer, Persistent=), saving the world first, into
  /var/lib/terraria/backups/<name>/ (group-readable), pruning only
  scheduled backups. rename carries the schedule, delete removes it.
  upgrade's backups move there too.
- fetch vanilla|tmodloader [<version>] [--list] [--upgrade <name>]:
  downloads from terraria.org / tModLoader's GitHub releases, checks
  GitHub's sha256 and the zip, names it with its version.
- terractld may write /etc/systemd/system, for backup enable and
  doctor --fix over --host.
- Units, tmpfiles, packaging, completion, docs and changelog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 22:26:56 +00:00

264 lines
10 KiB
Plaintext

# shellcheck shell=bash
# Bash completion for terractl (terraria-server-manager)
#
# Installed to <prefix>/share/bash-completion/completions/terractl, where the
# bash-completion package loads it automatically. Also works with 'sudo terractl'.
# A setting from /etc/default/terractl, or its default: _terractl_setting <name> <default>.
# The file is parsed rather than sourced, so nothing runs in your shell.
_terractl_setting() {
local result=$2 value
if [[ -r /etc/default/terractl ]]; then
value=$(sed -n "s/^[[:space:]]*$1=//p" /etc/default/terractl | tail -n 1)
value=${value%\"}; value=${value#\"}; value=${value%\'}; value=${value#\'}
if [[ -n $value ]]; then result=$value; fi
fi
printf '%s\n' "$result"
}
# Instances directory
_terractl_base() { _terractl_setting TERRARIA_BASE /var/lib/terraria/instances; }
# Server zips an instance can use, by its TYPE in instance.conf (all zips if
# that can't be read). On a remote, all of its zips, from its daemon.
_terractl_zips() {
if [[ -n $2 ]]; then
timeout 5 terractl --host "$2" info 2>/dev/null </dev/null | sed -n 's/^dist_zip=//p'
return
fi
local dist type pattern='*.zip' f
dist=$(_terractl_setting TERRARIA_DIST /var/lib/terraria/dist)
type=$(sed -n 's/^TYPE=\([a-z]*\).*/\1/p' "$(_terractl_base)/$1/instance.conf" 2>/dev/null | tail -n 1)
case $type in
vanilla) pattern='terraria-server-*.zip' ;;
tmodloader) pattern='tModLoader*.zip' ;;
esac
while IFS= read -r f; do printf '%s\n' "${f##*/}"; done < <(compgen -G "$dist/$pattern")
}
# 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
}
# Instance names here, or on remote <host> if one is given
_terractl_any_instances() {
if [[ -n $1 ]]; then _terractl_remote_instances "$1"; else _terractl_instances; 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)
_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
}
# File names, optionally only those ending in .<extension> (folders always offered)
_terractl_files() {
if declare -F _filedir >/dev/null 2>&1; then
_filedir "$@"
else
compopt -o filenames 2>/dev/null
if [[ -n ${1:-} ]]; then
mapfile -t COMPREPLY < <(compgen -d -- "$cur"; compgen -f -X "!*.$1" -- "$cur")
else
mapfile -t COMPREPLY < <(compgen -f -- "$cur")
fi
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
# -n : keeps "<remote>:<name>" (copy, move) in one word
_init_completion -n : || return
else
cur=${COMP_WORDS[COMP_CWORD]}
words=("${COMP_WORDS[@]}")
cword=$COMP_CWORD
fi
local commands="new list info start stop restart enable disable attach watch cmd mods upgrade reinstall fix-perms fetch backup doctor 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"
# 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 journal-access --host --help --version"
fi
if (( cword == 1 )); then
_terractl_reply "$commands"
return
fi
local command=${words[1]} prev=${words[cword-1]}
case $command in
list|journal-access|help|-h|--help|version|-V|--version)
return ;;
new)
# new <new name> [vanilla|tmodloader] [port]: the name is new, so nothing to offer
if (( cword == 3 )); then _terractl_reply "vanilla tmodloader"; fi
return ;;
import)
# import <new name> <file.tar.gz>: the name is new, so nothing to offer
if (( cword == 3 )); then _terractl_files tar.gz; fi
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 token revoke"
fi
return ;;
doctor)
# doctor [<name>] [--fix]
if [[ $cur == -* ]]; then _terractl_reply "--fix"; else _terractl_reply "$(_terractl_any_instances "$host")"; fi
return ;;
fetch)
# fetch vanilla|tmodloader [<version>|latest] [--list] [--upgrade <name>]
if (( cword == 2 )); then
_terractl_reply "vanilla tmodloader"
elif [[ $prev == --upgrade ]]; then
_terractl_reply "$(_terractl_any_instances "$host")"
elif [[ $cur == -* ]]; then
_terractl_reply "--list --upgrade"
else
_terractl_reply "latest"
fi
return ;;
backup)
# backup enable|disable|now|list <name> [--every <when>] [--keep <number>]
if (( cword == 2 )); then
_terractl_reply "enable disable now list"
elif (( cword == 3 )); then
_terractl_reply "$(_terractl_any_instances "$host")"
elif [[ ${words[2]} == enable ]]; then
case $prev in
--every) _terractl_reply "daily weekly hourly monthly" ;;
--keep) ;;
*) _terractl_reply "--every --keep" ;;
esac
fi
return ;;
esac
if (( cword == 2 )); then
if [[ -n $host ]]; then
_terractl_reply "$(_terractl_remote_instances "$host")"
else
_terractl_reply "$(_terractl_instances)"
fi
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 ;;
upgrade)
if [[ $cur == -* ]]; then
_terractl_reply "--downgrade --no-backup"
else
_terractl_reply "latest $(_terractl_zips "$instance" "$host")"
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)
_terractl_reply "-y --yes --no-backup" ;;
export)
if [[ $cur == -* ]]; then _terractl_reply "--with-server"; else _terractl_files tar.gz; fi ;;
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)
_terractl_files tmod ;;
enable|remove)
if [[ -z $host ]]; then _terractl_reply "$(_terractl_mods "$instance" installed)"; fi ;;
disable)
if [[ -z $host ]]; then _terractl_reply "$(_terractl_mods "$instance" enabled)"; fi ;;
esac ;;
esac
}
complete -F _terractl terractl