542 lines
15 KiB
Bash
542 lines
15 KiB
Bash
#!/bin/bash
|
|
# /usr/lib/check_mk_agent/local/300/check_pm2
|
|
|
|
set -u
|
|
|
|
CONFIG="/etc/check_mk/pm2.conf"
|
|
|
|
OK=0
|
|
WARN=1
|
|
CRIT=2
|
|
UNKNOWN=3
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Configuration
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
if [[ ! -r "$CONFIG" ]]; then
|
|
printf '%s\n' "${UNKNOWN} \"PM2\" - Configuration file ${CONFIG} missing or unreadable"
|
|
exit 0
|
|
fi
|
|
|
|
# shellcheck disable=SC1090
|
|
source "$CONFIG"
|
|
|
|
PM2_USER="${PM2_USER:-root}"
|
|
PM2_HOME="${PM2_HOME:-}"
|
|
|
|
CHECK_PM2_UPDATES="${CHECK_PM2_UPDATES:-yes}"
|
|
CHECK_NODE_UPDATES="${CHECK_NODE_UPDATES:-yes}"
|
|
|
|
PM2_PACKAGE_URL="${PM2_PACKAGE_URL:-https://registry.npmjs.org/pm2/latest}"
|
|
NODE_RELEASES_URL="${NODE_RELEASES_URL:-https://nodejs.org/dist/index.json}"
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Requirements
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
printf '%s\n' "${UNKNOWN} \"PM2\" - jq is not installed"
|
|
exit 0
|
|
fi
|
|
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
printf '%s\n' "${UNKNOWN} \"PM2\" - curl is not installed"
|
|
exit 0
|
|
fi
|
|
|
|
if ! id "$PM2_USER" >/dev/null 2>&1; then
|
|
printf '%s\n' "${UNKNOWN} \"PM2\" - Configured PM2 user ${PM2_USER} does not exist"
|
|
exit 0
|
|
fi
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# User / PM2 environment
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
USER_HOME="$(
|
|
getent passwd "$PM2_USER" |
|
|
cut -d: -f6
|
|
)"
|
|
|
|
if [[ -z "$USER_HOME" ]]; then
|
|
printf '%s\n' "${UNKNOWN} \"PM2\" - Unable to determine home directory for ${PM2_USER}"
|
|
exit 0
|
|
fi
|
|
|
|
if [[ -z "$PM2_HOME" ]]; then
|
|
PM2_HOME="${USER_HOME}/.pm2"
|
|
fi
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Locate PM2
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
PM2_BIN=""
|
|
|
|
for candidate in \
|
|
/usr/bin/pm2 \
|
|
/usr/local/bin/pm2 \
|
|
"${USER_HOME}/.local/bin/pm2"
|
|
do
|
|
if [[ -x "$candidate" ]]; then
|
|
PM2_BIN="$candidate"
|
|
break
|
|
fi
|
|
done
|
|
|
|
if [[ -z "$PM2_BIN" ]]; then
|
|
PM2_BIN="$(command -v pm2 2>/dev/null || true)"
|
|
fi
|
|
|
|
#
|
|
# NVM fallback
|
|
#
|
|
|
|
if [[ -z "$PM2_BIN" && -d "${USER_HOME}/.nvm/versions/node" ]]; then
|
|
PM2_BIN="$(
|
|
find "${USER_HOME}/.nvm/versions/node" \
|
|
-mindepth 3 \
|
|
-maxdepth 3 \
|
|
-type f \
|
|
-path '*/bin/pm2' \
|
|
-perm -u+x \
|
|
2>/dev/null |
|
|
sort -V |
|
|
tail -n 1
|
|
)"
|
|
fi
|
|
|
|
if [[ -z "$PM2_BIN" || ! -x "$PM2_BIN" ]]; then
|
|
printf '%s\n' "${UNKNOWN} \"PM2\" - Unable to locate PM2 executable"
|
|
exit 0
|
|
fi
|
|
|
|
PM2_BIN_DIR="$(dirname "$PM2_BIN")"
|
|
|
|
#
|
|
# Node used by this PM2 installation.
|
|
#
|
|
|
|
NODE_BIN="${PM2_BIN_DIR}/node"
|
|
|
|
if [[ ! -x "$NODE_BIN" ]]; then
|
|
NODE_BIN="$(command -v node 2>/dev/null || true)"
|
|
fi
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Retrieve PM2 process list
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
if [[ "$PM2_USER" == "root" ]]; then
|
|
|
|
PM2_JSON="$(
|
|
PM2_HOME="$PM2_HOME" \
|
|
PATH="${PM2_BIN_DIR}:${PATH}" \
|
|
"$PM2_BIN" jlist 2>/dev/null
|
|
)"
|
|
|
|
PM2_RC=$?
|
|
|
|
else
|
|
|
|
PM2_JSON="$(
|
|
runuser -u "$PM2_USER" -- \
|
|
env \
|
|
PM2_HOME="$PM2_HOME" \
|
|
PATH="${PM2_BIN_DIR}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \
|
|
"$PM2_BIN" jlist 2>/dev/null
|
|
)"
|
|
|
|
PM2_RC=$?
|
|
|
|
fi
|
|
|
|
if [[ $PM2_RC -ne 0 || -z "$PM2_JSON" ]]; then
|
|
printf '%s\n' "${CRIT} \"PM2\" - Unable to retrieve PM2 process list for user ${PM2_USER}"
|
|
exit 0
|
|
fi
|
|
|
|
if ! printf '%s' "$PM2_JSON" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
printf '%s\n' "${UNKNOWN} \"PM2\" - PM2 returned invalid JSON"
|
|
exit 0
|
|
fi
|
|
|
|
PROCESS_COUNT="$(
|
|
printf '%s' "$PM2_JSON" |
|
|
jq 'length'
|
|
)"
|
|
|
|
if [[ "$PROCESS_COUNT" -eq 0 ]]; then
|
|
printf '%s\n' "${WARN} \"PM2\" processes=0 No processes managed by PM2 for user ${PM2_USER}"
|
|
exit 0
|
|
fi
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Overall PM2 state
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
ONLINE_COUNT="$(
|
|
printf '%s' "$PM2_JSON" |
|
|
jq '[.[] | select(.pm2_env.status == "online")] | length'
|
|
)"
|
|
|
|
OFFLINE_COUNT=$((PROCESS_COUNT - ONLINE_COUNT))
|
|
|
|
if [[ "$OFFLINE_COUNT" -eq 0 ]]; then
|
|
printf '%s\n' \
|
|
"${OK} \"PM2\" processes=${PROCESS_COUNT};;;0|online=${ONLINE_COUNT};;;0|offline=0;;;0 ${PROCESS_COUNT}/${PROCESS_COUNT} processes online"
|
|
else
|
|
printf '%s\n' \
|
|
"${CRIT} \"PM2\" processes=${PROCESS_COUNT};;;0|online=${ONLINE_COUNT};;;0|offline=${OFFLINE_COUNT};;;0 ${ONLINE_COUNT}/${PROCESS_COUNT} processes online, ${OFFLINE_COUNT} not online"
|
|
fi
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# PM2 version / update
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
PM2_VERSION="$(
|
|
PATH="${PM2_BIN_DIR}:${PATH}" \
|
|
"$PM2_BIN" --version 2>/dev/null |
|
|
tail -n 1 |
|
|
tr -d '[:space:]'
|
|
)"
|
|
|
|
if [[ "$CHECK_PM2_UPDATES" == "yes" ]]; then
|
|
|
|
if [[ -z "$PM2_VERSION" ]]; then
|
|
printf '%s\n' "${UNKNOWN} \"PM2 Update\" - Unable to determine installed PM2 version"
|
|
|
|
else
|
|
|
|
PM2_RELEASE_RESPONSE="$(
|
|
curl \
|
|
--silent \
|
|
--show-error \
|
|
--fail \
|
|
--connect-timeout 3 \
|
|
--max-time 8 \
|
|
"$PM2_PACKAGE_URL" \
|
|
2>/dev/null
|
|
)"
|
|
|
|
CURL_RC=$?
|
|
|
|
if [[ $CURL_RC -ne 0 || -z "$PM2_RELEASE_RESPONSE" ]]; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"PM2 Update\" - Installed ${PM2_VERSION}, unable to query npm registry"
|
|
|
|
elif ! printf '%s' "$PM2_RELEASE_RESPONSE" | jq -e . >/dev/null 2>&1; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"PM2 Update\" - npm registry returned invalid JSON"
|
|
|
|
else
|
|
|
|
PM2_LATEST="$(
|
|
printf '%s' "$PM2_RELEASE_RESPONSE" |
|
|
jq -r '.version // empty'
|
|
)"
|
|
|
|
if [[ -z "$PM2_LATEST" ]]; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"PM2 Update\" - Unable to determine latest PM2 version"
|
|
|
|
else
|
|
|
|
NEWEST="$(
|
|
printf '%s\n%s\n' \
|
|
"$PM2_VERSION" \
|
|
"$PM2_LATEST" |
|
|
sort -V |
|
|
tail -n 1
|
|
)"
|
|
|
|
if [[ "$PM2_VERSION" == "$PM2_LATEST" ]]; then
|
|
printf '%s\n' \
|
|
"${OK} \"PM2 Update\" - Current: installed ${PM2_VERSION}, latest ${PM2_LATEST}"
|
|
|
|
elif [[ "$NEWEST" == "$PM2_LATEST" ]]; then
|
|
printf '%s\n' \
|
|
"${WARN} \"PM2 Update\" - Update available: installed ${PM2_VERSION}, latest ${PM2_LATEST}"
|
|
|
|
else
|
|
printf '%s\n' \
|
|
"${OK} \"PM2 Update\" - Installed ${PM2_VERSION} is newer than latest ${PM2_LATEST}"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Node.js / NVM version
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
if [[ "$CHECK_NODE_UPDATES" == "yes" ]]; then
|
|
|
|
if [[ -z "$NODE_BIN" || ! -x "$NODE_BIN" ]]; then
|
|
printf '%s\n' "${UNKNOWN} \"Node.js Update\" - Unable to locate Node.js executable"
|
|
|
|
else
|
|
|
|
NODE_VERSION="$(
|
|
"$NODE_BIN" --version 2>/dev/null |
|
|
sed 's/^v//'
|
|
)"
|
|
|
|
if [[ -z "$NODE_VERSION" ]]; then
|
|
printf '%s\n' "${UNKNOWN} \"Node.js Update\" - Unable to determine installed Node.js version"
|
|
|
|
else
|
|
|
|
NODE_RELEASES="$(
|
|
curl \
|
|
--silent \
|
|
--show-error \
|
|
--fail \
|
|
--connect-timeout 3 \
|
|
--max-time 8 \
|
|
"$NODE_RELEASES_URL" \
|
|
2>/dev/null
|
|
)"
|
|
|
|
CURL_RC=$?
|
|
|
|
if [[ $CURL_RC -ne 0 || -z "$NODE_RELEASES" ]]; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Node.js Update\" - Installed ${NODE_VERSION}, unable to query Node.js releases"
|
|
|
|
elif ! printf '%s' "$NODE_RELEASES" | jq -e 'type == "array"' >/dev/null 2>&1; then
|
|
printf '%s\n' \
|
|
"${UNKNOWN} \"Node.js Update\" - Node.js release API returned invalid data"
|
|
|
|
else
|
|
|
|
INSTALLED_MAJOR="${NODE_VERSION%%.*}"
|
|
|
|
LATEST_CURRENT="$(
|
|
printf '%s' "$NODE_RELEASES" |
|
|
jq -r '
|
|
[
|
|
.[]
|
|
| select(.lts == false)
|
|
| .version
|
|
][0] // empty
|
|
' |
|
|
sed 's/^v//'
|
|
)"
|
|
|
|
LATEST_LTS="$(
|
|
printf '%s' "$NODE_RELEASES" |
|
|
jq -r '
|
|
[
|
|
.[]
|
|
| select(.lts != false)
|
|
| .version
|
|
][0] // empty
|
|
' |
|
|
sed 's/^v//'
|
|
)"
|
|
|
|
LATEST_SAME_MAJOR="$(
|
|
printf '%s' "$NODE_RELEASES" |
|
|
jq -r \
|
|
--arg prefix "v${INSTALLED_MAJOR}." '
|
|
[
|
|
.[]
|
|
| select(.version | startswith($prefix))
|
|
| .version
|
|
][0] // empty
|
|
' |
|
|
sed 's/^v//'
|
|
)"
|
|
|
|
SAME_MAJOR_LTS="$(
|
|
printf '%s' "$NODE_RELEASES" |
|
|
jq -r \
|
|
--arg prefix "v${INSTALLED_MAJOR}." '
|
|
[
|
|
.[]
|
|
| select(.version | startswith($prefix))
|
|
| .lts
|
|
][0] // false
|
|
'
|
|
)"
|
|
|
|
#
|
|
# If no releases for the installed major exist in the current
|
|
# Node.js release index, consider it unsupported/EOL.
|
|
#
|
|
|
|
if [[ -z "$LATEST_SAME_MAJOR" ]]; then
|
|
|
|
printf '%s\n' \
|
|
"${WARN} \"Node.js Update\" - Installed ${NODE_VERSION} appears unsupported/EOL; latest LTS ${LATEST_LTS}, current ${LATEST_CURRENT}"
|
|
|
|
elif [[ "$SAME_MAJOR_LTS" == "false" && "$INSTALLED_MAJOR" -lt "${LATEST_CURRENT%%.*}" ]]; then
|
|
|
|
printf '%s\n' \
|
|
"${WARN} \"Node.js Update\" - Installed ${NODE_VERSION} is on a non-LTS release line; latest LTS ${LATEST_LTS}, current ${LATEST_CURRENT}"
|
|
|
|
elif [[ "$NODE_VERSION" != "$LATEST_SAME_MAJOR" ]]; then
|
|
|
|
NEWEST="$(
|
|
printf '%s\n%s\n' \
|
|
"$NODE_VERSION" \
|
|
"$LATEST_SAME_MAJOR" |
|
|
sort -V |
|
|
tail -n 1
|
|
)"
|
|
|
|
if [[ "$NEWEST" == "$LATEST_SAME_MAJOR" ]]; then
|
|
printf '%s\n' \
|
|
"${WARN} \"Node.js Update\" - Update available in Node ${INSTALLED_MAJOR}.x: installed ${NODE_VERSION}, latest ${LATEST_SAME_MAJOR}; latest LTS ${LATEST_LTS}"
|
|
else
|
|
printf '%s\n' \
|
|
"${OK} \"Node.js Update\" - Installed ${NODE_VERSION}; latest LTS ${LATEST_LTS}, current ${LATEST_CURRENT}"
|
|
fi
|
|
|
|
else
|
|
|
|
printf '%s\n' \
|
|
"${OK} \"Node.js Update\" - Current on Node ${INSTALLED_MAJOR}.x: ${NODE_VERSION}; latest LTS ${LATEST_LTS}, current ${LATEST_CURRENT}"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
#
|
|
# ---------------------------------------------------------------------------
|
|
# Individual PM2 processes
|
|
# ---------------------------------------------------------------------------
|
|
#
|
|
|
|
while IFS= read -r PROCESS; do
|
|
|
|
NAME="$(
|
|
printf '%s' "$PROCESS" |
|
|
jq -r '.name // "unknown"'
|
|
)"
|
|
|
|
STATUS="$(
|
|
printf '%s' "$PROCESS" |
|
|
jq -r '.pm2_env.status // "unknown"'
|
|
)"
|
|
|
|
PID="$(
|
|
printf '%s' "$PROCESS" |
|
|
jq -r '.pid // 0'
|
|
)"
|
|
|
|
RESTARTS="$(
|
|
printf '%s' "$PROCESS" |
|
|
jq -r '.pm2_env.restart_time // 0'
|
|
)"
|
|
|
|
CRON_RESTART="$(
|
|
printf '%s' "$PROCESS" |
|
|
jq -r '.pm2_env.cron_restart // empty'
|
|
)"
|
|
|
|
AUTORESTART="$(
|
|
printf '%s' "$PROCESS" |
|
|
jq -r '.pm2_env.autorestart // true'
|
|
)"
|
|
|
|
CPU="$(
|
|
printf '%s' "$PROCESS" |
|
|
jq -r '.monit.cpu // 0'
|
|
)"
|
|
|
|
MEMORY_BYTES="$(
|
|
printf '%s' "$PROCESS" |
|
|
jq -r '.monit.memory // 0'
|
|
)"
|
|
|
|
START_TIME="$(
|
|
printf '%s' "$PROCESS" |
|
|
jq -r '.pm2_env.pm_uptime // 0'
|
|
)"
|
|
|
|
NAME="${NAME//\"/\'}"
|
|
|
|
MEMORY_MB="$(
|
|
awk -v bytes="$MEMORY_BYTES" \
|
|
'BEGIN { printf "%.1f", bytes / 1024 / 1024 }'
|
|
)"
|
|
|
|
NOW_MS=$(( $(date +%s) * 1000 ))
|
|
|
|
if [[ "$START_TIME" =~ ^[0-9]+$ && "$START_TIME" -gt 0 ]]; then
|
|
|
|
UPTIME_SECONDS="$(
|
|
awk -v now="$NOW_MS" -v start="$START_TIME" \
|
|
'BEGIN {
|
|
diff = (now - start) / 1000
|
|
if (diff < 0) diff = 0
|
|
printf "%.0f", diff
|
|
}'
|
|
)"
|
|
|
|
else
|
|
UPTIME_SECONDS=0
|
|
fi
|
|
|
|
DAYS=$((UPTIME_SECONDS / 86400))
|
|
HOURS=$(((UPTIME_SECONDS % 86400) / 3600))
|
|
MINUTES=$(((UPTIME_SECONDS % 3600) / 60))
|
|
|
|
if [[ "$DAYS" -gt 0 ]]; then
|
|
UPTIME_TEXT="${DAYS}d ${HOURS}h ${MINUTES}m"
|
|
elif [[ "$HOURS" -gt 0 ]]; then
|
|
UPTIME_TEXT="${HOURS}h ${MINUTES}m"
|
|
else
|
|
UPTIME_TEXT="${MINUTES}m"
|
|
fi
|
|
|
|
STATE=$OK
|
|
STATE_TEXT="Online"
|
|
|
|
if [[ "$STATUS" != "online" ]]; then
|
|
STATE=$CRIT
|
|
STATE_TEXT="Status ${STATUS}"
|
|
fi
|
|
|
|
if [[ -n "$CRON_RESTART" ]]; then
|
|
RESTART_INFO="restarts ${RESTARTS}, scheduled restart ${CRON_RESTART}"
|
|
else
|
|
RESTART_INFO="restarts ${RESTARTS}"
|
|
fi
|
|
|
|
if [[ "$AUTORESTART" == "false" ]]; then
|
|
RESTART_INFO="${RESTART_INFO}, autorestart disabled"
|
|
fi
|
|
|
|
PERFDATA="cpu=${CPU}%;;;0;100|memory=${MEMORY_BYTES}B;;;0|restarts=${RESTARTS};;;0|uptime=${UPTIME_SECONDS}s;;;0"
|
|
|
|
printf '%s\n' \
|
|
"${STATE} \"PM2 ${NAME}\" ${PERFDATA} ${STATE_TEXT} - PID ${PID}, uptime ${UPTIME_TEXT}, CPU ${CPU}%, memory ${MEMORY_MB} MiB, ${RESTART_INFO}"
|
|
|
|
done < <(
|
|
printf '%s' "$PM2_JSON" |
|
|
jq -c '.[]'
|
|
)
|
|
|
|
exit 0 |