#!/bin/bash # /usr/lib/check_mk_agent/local/300/check_crafty set -u CONFIG="/etc/check_mk/crafty.conf" OK=0 WARN=1 CRIT=2 UNKNOWN=3 # # --------------------------------------------------------------------------- # Configuration # --------------------------------------------------------------------------- # if [[ ! -r "$CONFIG" ]]; then printf '%s\n' "${UNKNOWN} \"Crafty\" - Configuration file ${CONFIG} missing or unreadable" exit 0 fi # shellcheck disable=SC1090 source "$CONFIG" CRAFTY_URL="${CRAFTY_URL:-https://127.0.0.1:8443}" CRAFTY_USER="${CRAFTY_USER:-}" CRAFTY_PASSWORD="${CRAFTY_PASSWORD:-}" CRAFTY_INSECURE="${CRAFTY_INSECURE:-yes}" IGNORE_HIDDEN_SERVERS="${IGNORE_HIDDEN_SERVERS:-yes}" CHECK_UPDATES="${CHECK_UPDATES:-yes}" CRAFTY_RELEASE_API="${CRAFTY_RELEASE_API:-https://gitlab.com/api/v4/projects/34675721/releases/permalink/latest}" # # --------------------------------------------------------------------------- # Requirements # --------------------------------------------------------------------------- # if ! command -v curl >/dev/null 2>&1; then printf '%s\n' "${UNKNOWN} \"Crafty\" - curl is not installed" exit 0 fi if ! command -v jq >/dev/null 2>&1; then printf '%s\n' "${UNKNOWN} \"Crafty\" - jq is not installed" exit 0 fi if [[ -z "$CRAFTY_USER" || -z "$CRAFTY_PASSWORD" ]]; then printf '%s\n' "${UNKNOWN} \"Crafty\" - CRAFTY_USER or CRAFTY_PASSWORD is not configured" exit 0 fi # # --------------------------------------------------------------------------- # curl configuration # --------------------------------------------------------------------------- # CURL_OPTS=( --silent --show-error --connect-timeout 3 --max-time 10 ) if [[ "$CRAFTY_INSECURE" == "yes" ]]; then CURL_OPTS+=(--insecure) fi # # --------------------------------------------------------------------------- # Login # --------------------------------------------------------------------------- # LOGIN_PAYLOAD="$( jq -n \ --arg username "$CRAFTY_USER" \ --arg password "$CRAFTY_PASSWORD" \ '{ username: $username, password: $password }' )" LOGIN_RESPONSE="$( curl \ "${CURL_OPTS[@]}" \ -X POST \ -H "Content-Type: application/json" \ -d "$LOGIN_PAYLOAD" \ "${CRAFTY_URL%/}/api/v2/auth/login" \ 2>/dev/null )" CURL_RC=$? if [[ $CURL_RC -ne 0 || -z "$LOGIN_RESPONSE" ]]; then printf '%s\n' \ "${CRIT} \"Crafty\" - Unable to reach Crafty login API at ${CRAFTY_URL}" exit 0 fi if ! printf '%s' "$LOGIN_RESPONSE" | jq -e . >/dev/null 2>&1; then printf '%s\n' \ "${UNKNOWN} \"Crafty\" - Login API returned invalid JSON" exit 0 fi LOGIN_STATUS="$( printf '%s' "$LOGIN_RESPONSE" | jq -r '.status // empty' )" TOKEN="$( printf '%s' "$LOGIN_RESPONSE" | jq -r '.data.token // empty' )" if [[ "$LOGIN_STATUS" != "ok" || -z "$TOKEN" ]]; then LOGIN_ERROR="$( printf '%s' "$LOGIN_RESPONSE" | jq -r '.error_data // .error // "authentication failed"' )" printf '%s\n' \ "${CRIT} \"Crafty\" - Authentication failed: ${LOGIN_ERROR}" exit 0 fi # # --------------------------------------------------------------------------- # Server inventory # --------------------------------------------------------------------------- # SERVERS_RESPONSE="$( curl \ "${CURL_OPTS[@]}" \ -H "Authorization: Bearer ${TOKEN}" \ "${CRAFTY_URL%/}/api/v2/servers" \ 2>/dev/null )" CURL_RC=$? if [[ $CURL_RC -ne 0 ]]; then printf '%s\n' \ "${CRIT} \"Crafty\" - Server inventory request failed, curl exit code ${CURL_RC}" exit 0 fi if [[ -z "$SERVERS_RESPONSE" ]]; then printf '%s\n' \ "${CRIT} \"Crafty\" - Server inventory API returned an empty response" exit 0 fi if ! printf '%s' "$SERVERS_RESPONSE" | jq -e . >/dev/null 2>&1; then printf '%s\n' \ "${UNKNOWN} \"Crafty\" - Server inventory returned invalid JSON" exit 0 fi SERVERS_STATUS="$( printf '%s' "$SERVERS_RESPONSE" | jq -r '.status // empty' )" if [[ "$SERVERS_STATUS" != "ok" ]]; then SERVER_ERROR="$( printf '%s' "$SERVERS_RESPONSE" | jq -r '.error_data // .error // "unknown API error"' )" printf '%s\n' \ "${CRIT} \"Crafty\" - Server inventory API error: ${SERVER_ERROR}" exit 0 fi DATA_TYPE="$( printf '%s' "$SERVERS_RESPONSE" | jq -r '.data | type' )" if [[ "$DATA_TYPE" != "array" ]]; then printf '%s\n' \ "${UNKNOWN} \"Crafty\" - Server inventory returned unexpected data type: ${DATA_TYPE}" exit 0 fi # # --------------------------------------------------------------------------- # Server counts # --------------------------------------------------------------------------- # TOTAL_CONFIGURED="$( printf '%s' "$SERVERS_RESPONSE" | jq '.data | length' )" if [[ "$IGNORE_HIDDEN_SERVERS" == "yes" ]]; then SERVER_COUNT="$( printf '%s' "$SERVERS_RESPONSE" | jq '[.data[] | select(.show_status != false)] | length' )" else SERVER_COUNT="$TOTAL_CONFIGURED" fi # # --------------------------------------------------------------------------- # Crafty installed version via /metrics # --------------------------------------------------------------------------- # METRICS_RESPONSE="$( curl \ "${CURL_OPTS[@]}" \ -H "Authorization: Bearer ${TOKEN}" \ "${CRAFTY_URL%/}/metrics" \ 2>/dev/null )" METRICS_RC=$? INSTALLED_VERSION="" if [[ $METRICS_RC -eq 0 && -n "$METRICS_RESPONSE" ]]; then INSTALLED_VERSION="$( printf '%s\n' "$METRICS_RESPONSE" | grep -m1 '^Crafty_Controller_info{' | sed -n 's/.*version="\([^"]*\)".*/\1/p' | tr -d '[:space:]' )" fi # # --------------------------------------------------------------------------- # Overall Crafty service # --------------------------------------------------------------------------- # if [[ -n "$INSTALLED_VERSION" ]]; then printf '%s\n' \ "${OK} \"Crafty\" servers=${SERVER_COUNT};;;0 API reachable and authenticated - version ${INSTALLED_VERSION}, ${SERVER_COUNT}/${TOTAL_CONFIGURED} servers monitored" else printf '%s\n' \ "${OK} \"Crafty\" servers=${SERVER_COUNT};;;0 API reachable and authenticated - ${SERVER_COUNT}/${TOTAL_CONFIGURED} servers monitored" fi # # --------------------------------------------------------------------------- # Crafty update check # --------------------------------------------------------------------------- # if [[ "$CHECK_UPDATES" == "yes" ]]; then if [[ -z "$INSTALLED_VERSION" ]]; then printf '%s\n' \ "${UNKNOWN} \"Crafty Update\" - Unable to determine installed Crafty version from /metrics" else RELEASE_RESPONSE="$( curl \ --silent \ --show-error \ --location \ --fail \ --connect-timeout 3 \ --max-time 10 \ -H "User-Agent: checkmk-crafty-local-check" \ "$CRAFTY_RELEASE_API" \ 2>/dev/null )" RELEASE_RC=$? if [[ $RELEASE_RC -ne 0 || -z "$RELEASE_RESPONSE" ]]; then printf '%s\n' \ "${UNKNOWN} \"Crafty Update\" - Installed ${INSTALLED_VERSION}, unable to query GitLab release API" elif ! printf '%s' "$RELEASE_RESPONSE" | jq -e . >/dev/null 2>&1; then printf '%s\n' \ "${UNKNOWN} \"Crafty Update\" - GitLab release API returned invalid JSON" else LATEST_VERSION="$( printf '%s' "$RELEASE_RESPONSE" | jq -r '.tag_name // empty' )" LATEST_VERSION="${LATEST_VERSION#v}" if [[ -z "$LATEST_VERSION" ]]; then printf '%s\n' \ "${UNKNOWN} \"Crafty Update\" - Unable to determine latest Crafty release" elif [[ ! "$INSTALLED_VERSION" =~ ^[0-9]+([.][0-9]+)*$ ]]; then printf '%s\n' \ "${UNKNOWN} \"Crafty Update\" - Unable to parse installed version ${INSTALLED_VERSION}" elif [[ ! "$LATEST_VERSION" =~ ^[0-9]+([.][0-9]+)*$ ]]; then printf '%s\n' \ "${UNKNOWN} \"Crafty Update\" - Unable to parse latest version ${LATEST_VERSION}" else NEWEST="$( printf '%s\n%s\n' \ "$INSTALLED_VERSION" \ "$LATEST_VERSION" | sort -V | tail -n 1 )" if [[ "$INSTALLED_VERSION" == "$LATEST_VERSION" ]]; then printf '%s\n' \ "${OK} \"Crafty Update\" - Current: installed ${INSTALLED_VERSION}, latest ${LATEST_VERSION}" elif [[ "$NEWEST" == "$LATEST_VERSION" ]]; then printf '%s\n' \ "${WARN} \"Crafty Update\" - Update available: installed ${INSTALLED_VERSION}, latest ${LATEST_VERSION}" else printf '%s\n' \ "${OK} \"Crafty Update\" - Installed ${INSTALLED_VERSION} is newer than latest stable ${LATEST_VERSION}" fi fi fi fi fi # # --------------------------------------------------------------------------- # Individual Minecraft servers # --------------------------------------------------------------------------- # while IFS= read -r SERVER; do SERVER_ID="$( printf '%s' "$SERVER" | jq -r '.server_id // empty' )" SERVER_NAME="$( printf '%s' "$SERVER" | jq -r '.server_name // "Unknown"' )" SERVER_PORT="$( printf '%s' "$SERVER" | jq -r '.server_port // 0' )" AUTO_START="$( printf '%s' "$SERVER" | jq -r '.auto_start // false' )" CRASH_DETECTION="$( printf '%s' "$SERVER" | jq -r '.crash_detection // false' )" COUNT_PLAYERS="$( printf '%s' "$SERVER" | jq -r '.count_players // false' )" SHOW_STATUS="$( printf '%s' "$SERVER" | jq -r '.show_status // true' )" if [[ "$IGNORE_HIDDEN_SERVERS" == "yes" && "$SHOW_STATUS" == "false" ]]; then continue fi SERVER_NAME="${SERVER_NAME//\"/\'}" if [[ -z "$SERVER_ID" ]]; then printf '%s\n' \ "${UNKNOWN} \"Minecraft ${SERVER_NAME}\" - Server entry does not contain a server ID" continue fi # # ----------------------------------------------------------------------- # Runtime statistics # ----------------------------------------------------------------------- # STATS_RESPONSE="$( curl \ "${CURL_OPTS[@]}" \ -H "Authorization: Bearer ${TOKEN}" \ "${CRAFTY_URL%/}/api/v2/servers/${SERVER_ID}/stats" \ 2>/dev/null )" STATS_RC=$? if [[ $STATS_RC -ne 0 || -z "$STATS_RESPONSE" ]]; then printf '%s\n' \ "${UNKNOWN} \"Minecraft ${SERVER_NAME}\" - Unable to retrieve Crafty server statistics" continue fi if ! printf '%s' "$STATS_RESPONSE" | jq -e . >/dev/null 2>&1; then printf '%s\n' \ "${UNKNOWN} \"Minecraft ${SERVER_NAME}\" - Statistics API returned invalid JSON" continue fi STATS_STATUS="$( printf '%s' "$STATS_RESPONSE" | jq -r '.status // empty' )" if [[ "$STATS_STATUS" != "ok" ]]; then STATS_ERROR="$( printf '%s' "$STATS_RESPONSE" | jq -r '.error_data // .error // "unknown API error"' )" printf '%s\n' \ "${UNKNOWN} \"Minecraft ${SERVER_NAME}\" - Crafty statistics API error: ${STATS_ERROR}" continue fi # # ----------------------------------------------------------------------- # Runtime values # ----------------------------------------------------------------------- # RUNNING="$( printf '%s' "$STATS_RESPONSE" | jq -r '.data.running // false' )" CRASHED="$( printf '%s' "$STATS_RESPONSE" | jq -r '.data.crashed // false' )" UPDATING="$( printf '%s' "$STATS_RESPONSE" | jq -r '.data.updating // false' )" WAITING_START="$( printf '%s' "$STATS_RESPONSE" | jq -r '.data.waiting_start // false' )" IMPORTING="$( printf '%s' "$STATS_RESPONSE" | jq -r '.data.importing // false' )" FIRST_RUN="$( printf '%s' "$STATS_RESPONSE" | jq -r '.data.first_run // false' )" CPU="$( printf '%s' "$STATS_RESPONSE" | jq -r '.data.cpu // 0' )" MEMORY="$( printf '%s' "$STATS_RESPONSE" | jq -r '.data.mem // 0' )" MEMORY_PERCENT="$( printf '%s' "$STATS_RESPONSE" | jq -r '.data.mem_percent // 0' )" PLAYERS_ONLINE="$( printf '%s' "$STATS_RESPONSE" | jq -r ' if (.data.online | type) == "number" then .data.online else 0 end ' )" PLAYERS_MAX="$( printf '%s' "$STATS_RESPONSE" | jq -r ' if (.data.max | type) == "number" then .data.max else 0 end ' )" WORLD_NAME="$( printf '%s' "$STATS_RESPONSE" | jq -r ' if (.data.world_name // "") == "" then "unknown" else .data.world_name end ' )" WORLD_SIZE="$( printf '%s' "$STATS_RESPONSE" | jq -r ' if (.data.world_size // "") == "" then "unknown" else .data.world_size end ' )" MC_VERSION="$( printf '%s' "$STATS_RESPONSE" | jq -r ' if ( .data.version == false or .data.version == null or .data.version == "" ) then "" else (.data.version | tostring) end ' )" # # ----------------------------------------------------------------------- # Sanitize performance values # ----------------------------------------------------------------------- # if ! [[ "$CPU" =~ ^[0-9]+([.][0-9]+)?$ ]]; then CPU=0 fi if ! [[ "$MEMORY" =~ ^[0-9]+([.][0-9]+)?$ ]]; then MEMORY=0 fi if ! [[ "$MEMORY_PERCENT" =~ ^[0-9]+([.][0-9]+)?$ ]]; then MEMORY_PERCENT=0 fi if ! [[ "$PLAYERS_ONLINE" =~ ^[0-9]+$ ]]; then PLAYERS_ONLINE=0 fi if ! [[ "$PLAYERS_MAX" =~ ^[0-9]+$ ]]; then PLAYERS_MAX=0 fi # # ----------------------------------------------------------------------- # Determine CheckMK state # ----------------------------------------------------------------------- # STATE=$OK STATUS_TEXT="Stopped" if [[ "$CRASHED" == "true" ]]; then STATE=$CRIT STATUS_TEXT="Crashed" elif [[ "$UPDATING" == "true" ]]; then STATUS_TEXT="Updating" elif [[ "$IMPORTING" == "true" ]]; then STATUS_TEXT="Importing" elif [[ "$WAITING_START" == "true" ]]; then STATUS_TEXT="Waiting to start" elif [[ "$RUNNING" == "true" ]]; then STATUS_TEXT="Running" fi # # ----------------------------------------------------------------------- # Performance data # ----------------------------------------------------------------------- # PERFDATA="cpu=${CPU}%;;;0;100|memory=${MEMORY};;;0|memory_percent=${MEMORY_PERCENT}%;;;0;100" if [[ "$COUNT_PLAYERS" == "true" ]]; then if [[ "$PLAYERS_MAX" -gt 0 ]]; then PERFDATA="${PERFDATA}|players=${PLAYERS_ONLINE};;;0;${PLAYERS_MAX}" else PERFDATA="${PERFDATA}|players=${PLAYERS_ONLINE};;;0" fi fi # # ----------------------------------------------------------------------- # Human-readable output # ----------------------------------------------------------------------- # DETAILS="$STATUS_TEXT" if [[ "$RUNNING" == "true" ]]; then if [[ "$COUNT_PLAYERS" == "true" ]]; then if [[ "$PLAYERS_MAX" -gt 0 ]]; then DETAILS="${DETAILS} - players ${PLAYERS_ONLINE}/${PLAYERS_MAX}" else DETAILS="${DETAILS} - players ${PLAYERS_ONLINE}" fi fi if [[ -n "$MC_VERSION" ]]; then DETAILS="${DETAILS}, version ${MC_VERSION}" fi DETAILS="${DETAILS}, CPU ${CPU}%, memory ${MEMORY_PERCENT}%" fi if [[ "$WORLD_NAME" != "unknown" ]]; then DETAILS="${DETAILS}, world ${WORLD_NAME}" fi if [[ "$WORLD_SIZE" != "unknown" ]]; then DETAILS="${DETAILS} (${WORLD_SIZE})" fi DETAILS="${DETAILS}, port ${SERVER_PORT}" if [[ "$AUTO_START" == "true" ]]; then DETAILS="${DETAILS}, auto-start enabled" fi if [[ "$CRASH_DETECTION" == "true" ]]; then DETAILS="${DETAILS}, crash detection enabled" fi if [[ "$FIRST_RUN" == "true" ]]; then DETAILS="${DETAILS}, first-run flag set" fi printf '%s\n' \ "${STATE} \"Minecraft ${SERVER_NAME}\" ${PERFDATA} ${DETAILS}" done < <( printf '%s' "$SERVERS_RESPONSE" | jq -c '.data[]' ) exit 0