#!/usr/bin/env bash ARGS=("$@") CYAN="\033[1;96m" RED="\033[0;91m" GREEN="\033[0;92m" RESET='\033[0m' HOLESAIL_DOCKER_IMAGE='anaxios/holesail:2.2.0' readonly base_url="https://api.my-mc.link/" readonly headers=( -H "Accept: application/json" -H "Content-Type: application/json" -H "x-my-mc-auth: ${MY_MC_API_KEY}" ) # command format: ' [options]' declare -rA commands=( [hello]='hello GET {} Say hello to the server.' [time]='time GET {} Get server time.' [stats]='stats GET {} Get stats of server container.' [log]='log GET {} Get link for server logs.' [start]='start GET {} Start the server.' [stop]='stop GET {} Stop the server.' [restart]='restart GET {} Restart the server.' [my-link]='my-link GET {} Get connection info for server.' [my-geyser-link]='my-geyser-link GET {} Get info for Bedrock connection.' [my-sftp]='my-sftp GET {} Get connection info for sftp connection.' [my-hash]='my-hash GET {} Get Holsail connection info.' [my-geyser-hash]='my-geyser-hash GET {} Get Holesail connection information for bedrock connection.' [my-hash-sftp]='my-hash-sftp GET {} Get the Holesail connection information.' [list-players]='list-players GET {} List players on the server.' [website]='website GET {} Get link to Website.' [map]='map GET {} Get link to Bluemap.' [status-minecraft]='status/Minecraft GET {} Get status of the my-link connection to the server.' [status-bedrock]='status/Bedrock GET {} Get status of the bedrock connection to the server.' [status-sftp]='status/SFTP GET {} Get status and info of sftp connection to the server.' [ban]='ban POST {"username":"%s"} Ban a player.' [unban]='unban POST {"username":"%s"} Unban a player.' [say]='say POST {"message":"%s"} Broadcast a message on the server.' [tell]='tell POST {"username":"%s","message":"%s"} Send a message to a player.' [console]='console POST {"command":"%s"} Run a console command.' [give]='give POST {"username":"%s","item":"%s","amount":"%s"} Give player item.' [install]='install POST {"mod":"%s"} install a mod using id.' [uninstall]='uninstall POST {"mod":"%s"} Uninstall a mod using id.' [search]='search POST {"mod":"%s"} Search for mods by name or id.' [mod-list]='mod-list GET {} Get list of mods installed on ther server.' [backup]='backup FUNC {} EXPERIMENTAL! Make a local copy of server files over sftp.' [connect]='connect FUNC {port} Open a connection to server using docker. Leave arg blank for default 25565.' [check-update]='check_update FUNC {game_version} Check installed mods have available version.' [version]='version FUNC {} Check the version of Minecraft the server is running.' [kick]='kick FUNC {player} Kick a player.' [smite]='smite FUNC {player} Smite a player with lightening.' [op]='op FUNC {player} Make player an operator.' [deop]='deop FUNC {player} Deop player.' ) function op() { local player="$1" call_api console "op $player" } function deop() { local player="$1" call_api console "deop $player" } function smite() { local player="$1" call_api console "execute at $player run summon minecraft:lightning_bolt" } function kick() { local player="$1" call_api console "kick $player" } function version() { local message="$(call_api console version | jq -r '.message')" if [[ $message =~ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then local version="${BASH_REMATCH[0]}" fi echo "$version" } function check_update() { local version="${1:-$(version)}" local mods="$(call_api mod-list | jq -c '.mods')" local -a result=("MOD\tID\tAVAILABLE\n") local item id name latest available has_version available_versions while read -r item; do name="$(jq -r '.name' <<<"$item")" id="$(jq -r '.id' <<<"$item")" available_versions="$(curl -sS -X GET "https://api.modrinth.com/v2/project/$id" | jq -r '.game_versions')" has_version="$(jq "contains([\"$version\"])" <<<"$available_versions")" if [[ "$has_version" == "true" ]]; then available="${GREEN}true${RESET}"; else available="${RED}false${RESET}"; fi result+=("$name\t$id\t$available\n") done < <(jq -c '.[]' <<<"$mods") echo -e "${result[*]}" | column -t -s $'\t' } function connect() { local port="${1:-25565}" if [[ ! $(which docker) ]]; then echo "docker is required"; exit 1; fi docker run --rm \ -e MODE=my-mc \ -e PORT=25565 \ -e HOST=0.0.0.0 \ -e PUBLIC=false \ -e MY_MC_API_KEY=$MY_MC_API_KEY \ -p $port:25565 "$HOLESAIL_DOCKER_IMAGE" echo "" } function backup() { if [[ ! $(which lftp) ]]; then echo "lftp is required"; exit 1; fi local sftp_credentials="$(call_api my-sftp)" if [[ "$(jq -r '.success' <<<"${sftp_credentials}" )" != "true" ]]; then echo "ERROR: failed to get sftp login info"; exit 1; fi local host="$(jq -r '.hostname' <<<"${sftp_credentials}")" local username="$(jq -r '.user' <<<"${sftp_credentials}")" local port="$(jq -r '.port' <<<"${sftp_credentials}")" local password="$(jq -r '.password' <<<"${sftp_credentials}")" local localdir="${MY_MC_BACKUP_DIR:-${HOME}/my-mc_backup/}" if [[ ! -d ${localdir} ]]; then mkdir -p ${localdir}; fi pushd "${localdir}" lftp -u "${username}","${password}" "${host}":"${port}" \ -e "set sftp:connect-program 'ssh -o StrictHostKeyChecking=no'; mirror -c --use-pget minecraft/ minecraft/; quit" popd } function usage() { printf "API key for my-mc must be exported using 'export MY_MC_API_KEY='\n\n" printf "Backup directory can be changed from default ~/mc-mc_backup/ by using 'export MY_MC_BACKUP_DIR='\n\n" printf "Positional arguments will fill the JSON objects with values from left to right.\n\n" local -a message=("COMMAND\tARGUMENTS\tDESCRIPTION\n") local command for command in "${!commands[@]}"; do local endpoint method body description read -r endpoint method body description <<<"${commands[${command}]}" message+=("$(echo -e "${command}\t${body}" | sed 's/[":\{\}]//g' |sed 's/%s//g'| sed 's/[,]/ /g')\t$description\n") done echo -e "${message[*]}" | column -t -s $'\t' } function call_api() { local command="$1" local args=("${@}") # remove command from arg list while preserving quoted strings. local args=("${args[@]:1}") # Check if key does NOT exists in command array if [[ ! -v commands[$command] ]]; then printf "${0}: invalid option -- $1\n\n" usage exit 1 fi local endpoint method body description read -r endpoint method body description <<<"${commands[$command]}" case "${method}" in "POST") curl -sS "${headers[@]}" -X "${method}" "${base_url}${endpoint}" \ -d "$(printf "${body}\n" "${args[@]}")" echo "" ;; "GET") curl -sS "${headers[@]}" -X "${method}" "${base_url}${endpoint}" echo "" ;; "FUNC") eval "${endpoint}" "${args[@]}" ;; *) echo "ERROR: Invalid state. Check for typo in command array." ;; esac } function main() { if [[ ! $(which curl) ]]; then echo "curl is required"; exit 1; fi if [[ ! $(which jq) ]]; then echo "jq is required"; exit 1; fi call_api "${ARGS[@]}" exit 0 } if [[ "$(basename "$0")" == "mc" ]]; then main fi