From c4ca438600418cad3e5e62dacffa97599f68a912 Mon Sep 17 00:00:00 2001 From: amy <144570677+amyavi@users.noreply.github.com> Date: Sat, 21 Dec 2024 00:24:52 -0300 Subject: [PATCH] Rewrite server update script (#148) * refactor!: rewrite update script * refactor: split common URL logic into download_with_args * feat: add skip_404 arg to url/zip download type --- .github/workflows/main.yml | 19 +++----- scripts/_common.sh | 81 +++++++++++++++++++++++++++++++ scripts/_parser.jq | 52 ++++++++++++++++++++ scripts/_sources/_index.sh | 67 +++++++++++++++++++++++++ scripts/_sources/_url.sh | 12 +++++ scripts/_sources/_zip.sh | 26 ++++++++++ scripts/downloads.json | 73 ++++++++++++++++++++++++++++ scripts/fetch_external_plugins.sh | 29 ----------- scripts/fetch_internal_plugins.sh | 16 ------ scripts/fetch_server.sh | 11 ----- scripts/update.sh | 56 +++++++++++++++++++++ 11 files changed, 373 insertions(+), 69 deletions(-) create mode 100644 scripts/_common.sh create mode 100644 scripts/_parser.jq create mode 100644 scripts/_sources/_index.sh create mode 100644 scripts/_sources/_url.sh create mode 100644 scripts/_sources/_zip.sh create mode 100644 scripts/downloads.json delete mode 100755 scripts/fetch_external_plugins.sh delete mode 100755 scripts/fetch_internal_plugins.sh delete mode 100755 scripts/fetch_server.sh create mode 100755 scripts/update.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 195dc97..29ed79f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,27 +9,20 @@ permissions: jobs: update: - if: github.repository == 'kaboomserver/server' + if: github.repository == 'kaboomserver/server' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Fetch server jar - run: scripts/fetch_server.sh - - - name: Fetch internal plugins - run: scripts/fetch_internal_plugins.sh - - - name: Fetch external plugins - run: scripts/fetch_external_plugins.sh - - - name: Update server and plugins + - name: Update server jar and plugins + id: update run: | - cp fetched_server/server.jar . - cp fetched_plugins/*.jar plugins/ + scripts/update.sh + git diff --quiet . || echo "changed=true" >> "$GITHUB_OUTPUT" - name: Push changes + if: steps.update.outputs.changed == 'true' run: | git config --global user.name 'kaboombot' git config --global user.email '58372747+kaboombot@users.noreply.github.com' diff --git a/scripts/_common.sh b/scripts/_common.sh new file mode 100644 index 0000000..022683f --- /dev/null +++ b/scripts/_common.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +_EXEC_PATH="$(realpath .)" +_HAS_TTY=0 +if (exec < /dev/tty) 2>/dev/null; then + _HAS_TTY=1 +fi + +if [ "$DEBUG" = 1 ]; then + debug() { + printf '[DEBUG] ' + + # If shell supports FUNCNAME, print it + # The -20 is used to pad the function name with up to 20 spaces on the right. + if [ -n "${FUNCNAME+x}" ]; then + # shellcheck disable=SC3054 # FUNCNAME support requires array support + printf '%-20s' "${FUNCNAME[1]}" + fi + + echo "$@" + } +else debug() { true; } +fi + +contains() { + NEEDLE="$1" + shift + + for piece in "$@"; do + if [ "$piece" = "$NEEDLE" ]; then + return 0 + fi + done + + return 1 +} + +check_path() { + rpath="$(realpath "$1")" + + case "$1" in + "/"*) echo "Attempted path traversal: $1 is absolute" + return 1;; + *);; # Safe + esac + + case "$rpath" in + "$_EXEC_PATH/"*);; # Safe + *) echo "Attempted path traversal: $1 is outside current directory" + return 1;; + esac + + return 0 +} + +download() { + debug "downloading $1 to $2" + exitcode=0 + statuscode=0 + + curl_params="-fL $1 -o $2 --write-out %{http_code}" + + # shellcheck disable=SC2086 # Intentional + if [ $_HAS_TTY = 1 ]; then + # TTY present: Enable curl's progress bar, clear it if operation successful + tput sc 2>/dev/null || true # Save cursor pos + + statuscode=$(curl -# $curl_params &1) || exitcode=$? + if [ $exitcode = 0 ]; then + (tput rc; tput ed) 2>/dev/null || true # Reset cursor pos; Clear to end + fi + else + statuscode=$(curl $curl_params) || exitcode=$? + fi + + if [ "$statuscode" = "404" ]; then + return 100 + fi + + return $exitcode +} diff --git a/scripts/_parser.jq b/scripts/_parser.jq new file mode 100644 index 0000000..8dc4cf6 --- /dev/null +++ b/scripts/_parser.jq @@ -0,0 +1,52 @@ +# Apply $filter to input +# <- downloads.json | evaluate_filter("plugins") +# -> [["internal", "plugins/Extras.jar", "type"], "zip"] +# -> [["internal", "plugins/Extras.jar", "url"], "..."] +# -> [["internal", "plugins/Extras.jar", "url"]] +# -> [["internal", "plugins/Extras.jar"]] +# -> [["internal"]] +def evaluate_filter($filter): + $filter | indices("/") | length + | truncate_stream( + inputs + | select( + .[0] as $key + | $key | join("/") + | startswith($filter))); + +# Flatten stream structure, stripping everything but the download +# path and it's properties +# <- [["internal", "plugins/Extras.jar", "type"], "zip"] +# <- [["internal", "plugins/Extras.jar", "url"], "..."] +# <- [["internal", "plugins/Extras.jar"]] +# <- [["internal"]] +# -> [["plugins/Extras.jar", "type"], "zip"] +# -> [["plugins/Extras.jar", "url"], "..."] +def get_downloads_obj: + select(length == 2) + | del(.[0][:-2]); + +# Reduce flattened stream to an object +# <- [["plugins/Extras.jar", "type"], "zip"] +# <- [["plugins/Extras.jar", "url"], "..."] +# -> { "plugins/Extras.jar": {"type": "zip", "url": "..."} } +def reduce_to_object(stream): + reduce stream as $in ({}; + setpath($in[0]; $in[1])); + +# Turn object into a bash-readable string +# <- { "plugins/Extras.jar": {"type": "zip"} } +# -> plugins/Extras.jar +# zip +# { "url": ... } +def print_bash: + to_entries[] + | (.value | del(.type)) as $args + | "\(.key)\n\(.value.type)\n\($args)"; + +reduce_to_object( + if $arg1 == "" + then inputs + else evaluate_filter($arg1) end + | get_downloads_obj) +| print_bash diff --git a/scripts/_sources/_index.sh b/scripts/_sources/_index.sh new file mode 100644 index 0000000..7ed61f2 --- /dev/null +++ b/scripts/_sources/_index.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# shellcheck disable=SC1091 + +. "$_SCRIPT_PATH"/_sources/_url.sh +. "$_SCRIPT_PATH"/_sources/_zip.sh + +_parse_args() { + # <- { "a": "b", "c": "d" } + # -> a + # -> b + # -> c + # -> d + jq --raw-output --exit-status \ + 'to_entries[] | "\(.key)\n\(.value)"' +} + +read_args() { + while read -r key; read -r value; do + debug "read: $key=$value" + + if contains "$key" "$@"; then + debug "set: arg_$key" + + # The eval here might look scary, but we know that $key + # is safe and we escape $value. + eval "arg_$key=\$value" + fi + done </dev/null + + if [ $exitcode = 100 ] && [ "${arg_skip_404:-false}" = "true" ]; then + return 0 + else + return $exitcode + fi + fi + + debug "extracting ${arg_extract:?} to $1" + unzip -p "$zip_path" \ + "${arg_extract:?}" > "$1" || exitcode=$? + rm -f "$zip_path" 2>/dev/null + + return $exitcode +} diff --git a/scripts/downloads.json b/scripts/downloads.json new file mode 100644 index 0000000..5da5132 --- /dev/null +++ b/scripts/downloads.json @@ -0,0 +1,73 @@ +{ + "server.jar": { + "type": "zip", + "url": "https://ci.plex.us.org/job/Scissors/job/1.20.4/lastSuccessfulBuild/artifact/*zip*/archive.zip", + "extract": "archive/build/libs/scissors-*.jar" + }, + "plugins": { + "external": { + "plugins/Essentials.jar": { + "type": "zip", + "url": "https://ci.ender.zone/job/EssentialsX/lastSuccessfulBuild/artifact/*zip*/archive.zip", + "extract": "archive/jars/EssentialsX-*.jar" + }, + "plugins/FastAsyncWorldEdit.jar": { + "type": "zip", + "url": "https://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/artifact/*zip*/archive.zip", + "extract": "archive/artifacts/FastAsyncWorldEdit-Bukkit-*.jar" + }, + "plugins/Geyser.jar": { + "type": "zip", + "url": "https://nightly.link/GeyserMC/Geyser/workflows/build/master/Geyser-Spigot.zip", + "extract": "Geyser-Spigot.jar" + }, + "plugins/ViaVersion.jar": { + "type": "zip", + "url": "https://ci.viaversion.com/job/ViaVersion/lastSuccessfulBuild/artifact/*zip*/archive.zip", + "extract": "archive/build/libs/ViaVersion-*.jar" + }, + "plugins/ViaBackwards.jar": { + "type": "zip", + "url": "https://ci.viaversion.com/job/ViaBackwards/lastSuccessfulBuild/artifact/*zip*/archive.zip", + "extract": "archive/build/libs/ViaBackwards-*.jar" + }, + "plugins/ViaRewind.jar": { + "type": "zip", + "url": "https://ci.viaversion.com/job/ViaRewind/lastSuccessfulBuild/artifact/*zip*/archive.zip", + "extract": "archive/build/libs/ViaRewind-*.jar" + } + }, + "internal": { + "plugins/CommandSpy.jar": { + "type": "zip", + "skip_404": true, + "url": "https://nightly.link/kaboomserver/commandspy/workflows/main/master/CommandSpy.zip", + "extract": "CommandSpy.jar" + }, + "plugins/Extras.jar": { + "type": "zip", + "skip_404": true, + "url": "https://nightly.link/kaboomserver/extras/workflows/main/master/Extras.zip", + "extract": "Extras.jar" + }, + "plugins/iControlU.jar": { + "type": "zip", + "skip_404": true, + "url": "https://nightly.link/kaboomserver/icontrolu/workflows/main/master/iControlU.zip", + "extract": "iControlU.jar" + }, + "plugins/ParticleTrails.jar": { + "type": "zip", + "skip_404": true, + "url": "https://nightly.link/kaboomserver/particletrails/workflows/main/master/ParticleTrails.zip", + "extract": "ParticleTrails.jar" + }, + "plugins/Weapons.jar": { + "type": "zip", + "skip_404": true, + "url": "https://nightly.link/kaboomserver/weapons/workflows/main/master/Weapons.zip", + "extract": "Weapons.jar" + } + } + } +} \ No newline at end of file diff --git a/scripts/fetch_external_plugins.sh b/scripts/fetch_external_plugins.sh deleted file mode 100755 index 45d266a..0000000 --- a/scripts/fetch_external_plugins.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/sh -# Script used to fetch latest versions of external plugins -# Plugins: EssentialsX, FastAsyncWorldEdit, GeyserMC, ViaVersion, ViaBackwards, ViaRewind - -mkdir -p fetched_plugins - -# Fetch plugins -for download_url in https://ci.ender.zone/job/EssentialsX/lastSuccessfulBuild/artifact/*zip*/archive.zip \ - https://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/artifact/*zip*/archive.zip \ - https://nightly.link/GeyserMC/Geyser/workflows/build/master/Geyser%20Spigot.zip \ - https://ci.viaversion.com/job/ViaVersion/lastSuccessfulBuild/artifact/*zip*/archive.zip \ - https://ci.viaversion.com/job/ViaBackwards/lastSuccessfulBuild/artifact/*zip*/archive.zip \ - https://ci.viaversion.com/job/ViaRewind/lastSuccessfulBuild/artifact/*zip*/archive.zip -do - curl -L $download_url > archive.zip - unzip -o archive.zip - rm archive.zip -done - -# Move plugins -mv archive/jars/EssentialsX-*.jar fetched_plugins/Essentials.jar -mv archive/artifacts/FastAsyncWorldEdit-Bukkit-*.jar fetched_plugins/FastAsyncWorldEdit.jar -mv Geyser-Spigot.jar fetched_plugins/Geyser.jar -mv archive/build/libs/ViaVersion-*.jar fetched_plugins/ViaVersion.jar -mv archive/build/libs/ViaBackwards-*.jar fetched_plugins/ViaBackwards.jar -mv archive/universal/build/libs/ViaRewind-*.jar fetched_plugins/ViaRewind.jar - -# Clean up -rm -rf archive/ diff --git a/scripts/fetch_internal_plugins.sh b/scripts/fetch_internal_plugins.sh deleted file mode 100755 index e217260..0000000 --- a/scripts/fetch_internal_plugins.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh -# Script used to fetch latest versions of internal plugins -# Plugins: CommandSpy, Extras, iControlU, ParticleTrails, Weapons - -mkdir -p fetched_plugins - -for download_url in https://nightly.link/kaboomserver/commandspy/workflows/main/master/CommandSpy.zip \ - https://nightly.link/kaboomserver/extras/workflows/main/master/Extras.zip \ - https://nightly.link/kaboomserver/icontrolu/workflows/main/master/iControlU.zip \ - https://nightly.link/kaboomserver/particletrails/workflows/main/master/ParticleTrails.zip \ - https://nightly.link/kaboomserver/weapons/workflows/main/master/Weapons.zip -do - curl -L $download_url > archive.zip - unzip -o archive.zip -d fetched_plugins - rm archive.zip -done diff --git a/scripts/fetch_server.sh b/scripts/fetch_server.sh deleted file mode 100755 index 85895e6..0000000 --- a/scripts/fetch_server.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# Script used to fetch the latest version of the server jar - -mkdir -p fetched_server - -curl -L https://ci.plex.us.org/job/Scissors/job/1.20.4/lastSuccessfulBuild/artifact/*zip*/archive.zip > archive.zip -unzip -o archive.zip -mv archive/build/libs/scissors-*.jar fetched_server/server.jar - -rm -rf archive/ -rm archive.zip diff --git a/scripts/update.sh b/scripts/update.sh new file mode 100755 index 0000000..f4ae9c6 --- /dev/null +++ b/scripts/update.sh @@ -0,0 +1,56 @@ +#!/bin/sh +# shellcheck disable=SC1091 # Included files should be manually checked +set -e + +# Pipefail is part of POSIX.1-2024, however some shells haven't +# implemented it yet. Turn it on only if it's available. +# shellcheck disable=SC3040 +if (set -o pipefail 2>/dev/null); then + set -o pipefail +fi + +_SCRIPT_PATH="$(dirname "$(readlink -f -- "$0")")" +. "$_SCRIPT_PATH"/_common.sh +. "$_SCRIPT_PATH"/_sources/_index.sh + +_FILTER="$1" +if [ "$_FILTER" = "help" ]; then + cat <&2 + return $exitcode + fi + + return $exitcode +} + +echo "Downloading with filter ${_FILTER:-""}..." +_parse_downloads | while read -r path; read -r type; read -r args; do + echo "> $path" + if ! check_path "$path"; then + echo "Bailing!" + exit 1 + fi + + debug "download_type: type=$type; args=$args" + echo "$args" | download_type "$type" "$path" +done