Initial load
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "sunwait"]
|
||||||
|
path = sunwait
|
||||||
|
url = http://github.com/risacher/sunwait
|
||||||
Executable
+209
@@ -0,0 +1,209 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
myCommand=""
|
||||||
|
debug=""
|
||||||
|
PODip="192.168.0.151"
|
||||||
|
PODport="32000"
|
||||||
|
PPBAkey=""
|
||||||
|
PPBAname=""
|
||||||
|
MQTT_HOST="192.168.0.6"
|
||||||
|
MQTT_USER="" # optional
|
||||||
|
MQTT_PASS="" # optional
|
||||||
|
myDevice="all"
|
||||||
|
|
||||||
|
do_help() {
|
||||||
|
cat << HELP_EOF
|
||||||
|
Usage: $0 <command> [OPTIONS]
|
||||||
|
command is one of:
|
||||||
|
--ip) IP of the PPBA API server
|
||||||
|
--port) Port of the PPBA API server
|
||||||
|
--init) Starts the Unity driver (must do this first)
|
||||||
|
--start) Starts the PPBA driver (do this after --init)
|
||||||
|
--mqtt) Send HA MQTT discovery information to broker
|
||||||
|
--stats) Send MQTT stats to broker
|
||||||
|
--power [on|off] <DEV>) Powers on a device (see DEV, below)
|
||||||
|
-d|--debug) Print debug information
|
||||||
|
-h|--help) print this help information
|
||||||
|
|
||||||
|
You can put multiple commands on the command line; they will be executed sequentially.
|
||||||
|
They are scanned for in the order listed above.
|
||||||
|
Thus, you can do "$0 --init --start --mqtt" for a one-liner startup script
|
||||||
|
DEV is a device name:
|
||||||
|
all | quad | var | dew1 | dew2 | autodew
|
||||||
|
HELP_EOF
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
do_debug() {
|
||||||
|
[ -n "$debug" ] && echo "DEBUG: $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
do_api() {
|
||||||
|
ep="${1#/}"
|
||||||
|
method="${2:-GET}"
|
||||||
|
notes="${3}"
|
||||||
|
url="http://${PODip}:${PODport}/${ep}"
|
||||||
|
do_debug "curl -s -X ${method} \"$url\""
|
||||||
|
[ -n "$notes" ] && echo "$notes" >&2
|
||||||
|
curl -s -X ${method} "$url"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_getKey() {
|
||||||
|
# Get POD PPBA unique key and name
|
||||||
|
PPBAkey=$(do_api "/Server/DeviceManager/Connected" | jq -r '.data[] | select (.name =="PPBAdvance") | .uniqueKey')
|
||||||
|
PPBAname=$(do_api "/Server/DeviceManager/Device/${PPBAkey}/ProfileName" | jq -r '.data')
|
||||||
|
do_debug "PPBAkey=$PPBAkey, PPBAname=$PPBAname"
|
||||||
|
if [ -z "$PPBAkey" -o -z "$PPBAname" ]; then
|
||||||
|
# The PPBA is not powered on or cannot be found
|
||||||
|
do_debug "The PPBA is not powered on or cannot be found"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
do_init() {
|
||||||
|
do_getKey
|
||||||
|
do_api "/Server/Start" PUT "Starting server..." | jq '.status'
|
||||||
|
sleep 2
|
||||||
|
}
|
||||||
|
|
||||||
|
do_start() {
|
||||||
|
do_getKey
|
||||||
|
do_api "/Driver/PPBAdvance/Start?DriverUniqueKey=${PPBAkey}" OPTIONS "Starting PPBAdvance driver..." | jq '.status'
|
||||||
|
# do_api "/Driver/PPBAdvance/Power/Hub/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning off Quad Power..." | jq '.status'
|
||||||
|
# do_api "/Driver/PPBAdvance/Power/Variable/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning off Variable Power..." | jq '.status'
|
||||||
|
# do_api "/Driver/PPBAdvance/Dew/1/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning off Dew Heater 1..." | jq '.status'
|
||||||
|
# do_api "/Driver/PPBAdvance/Dew/2/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning off Dew Heater 2..." | jq '.status'
|
||||||
|
# do_api "/Driver/PPBAdvance/Dew/Auto/Off?DriverUniqueKey=${PPBAkey}" POST "Turning off Auto Dew..." | jq '.status'
|
||||||
|
}
|
||||||
|
|
||||||
|
do_poweroff() {
|
||||||
|
do_getKey
|
||||||
|
case "$myDevice" in
|
||||||
|
Quad|quad*|All|all) do_api "/Driver/PPBAdvance/Power/Hub/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning Off Quad Power..." | jq '.status';;&
|
||||||
|
Var|var*|All|all) do_api "/Driver/PPBAdvance/Power/Variable/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning Off Variable Power..." | jq '.status';;&
|
||||||
|
Dew1|dew1|All|all) do_api "/Driver/PPBAdvance/Dew/1/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning Off Dew Heater 1..." | jq '.status';;&
|
||||||
|
Dew2|dew2|All|all) do_api "/Driver/PPBAdvance/Dew/2/Off?DriverUniqueKey=${PPBAkey}" PUT "Turning Off Dew Heater 2..." | jq '.status';;&
|
||||||
|
Auto*|auto*|All|all) do_api "/Driver/PPBAdvance/Dew/Auto/Off?DriverUniqueKey=${PPBAkey}" POST "Turning Off Auto Dew..." | jq '.status';;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
do_poweron() {
|
||||||
|
do_getKey
|
||||||
|
case "$myDevice" in
|
||||||
|
Quad|quad*|All|all) do_api "/Driver/PPBAdvance/Power/Hub/On?DriverUniqueKey=${PPBAkey}" PUT "Turning on Quad Power..." | jq '.status';;&
|
||||||
|
Var*|var*|All|all) do_api "/Driver/PPBAdvance/Power/Variable/On?DriverUniqueKey=${PPBAkey}" PUT "Turning on Variable Power..." | jq '.status';;&
|
||||||
|
Dew1|dew1|All|all) do_api "/Driver/PPBAdvance/Dew/1/On?DriverUniqueKey=${PPBAkey}" PUT "Turning on Dew Heater 1..." | jq '.status';;&
|
||||||
|
Dew2|dew2|All|all) do_api "/Driver/PPBAdvance/Dew/2/On?DriverUniqueKey=${PPBAkey}" PUT "Turning on Dew Heater 2..." | jq '.status';;&
|
||||||
|
Auto*|auto|All|all) do_api "/Driver/PPBAdvance/Dew/Auto/On?DriverUniqueKey=${PPBAkey}" POST "Turning on Auto Dew..." | jq '.status';;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mqtt_pub() {
|
||||||
|
topic="$1"
|
||||||
|
message="$2"
|
||||||
|
do_debug "mosquitto_pub -h $MQTT_HOST -t \"PPBA/status/$topic\" -m \"$message\""
|
||||||
|
mosquitto_pub -h $MQTT_HOST -t "PPBA/status/$topic" -m "$message" -r
|
||||||
|
}
|
||||||
|
|
||||||
|
do_stats() {
|
||||||
|
do_getKey
|
||||||
|
stats=$(do_api "/Driver/PPBAdvance/Report?DriverUniqueKey=${PPBAkey}")
|
||||||
|
for stat in "voltage" "current" "quadCurrent" "power" "temperature" "humidity" "dewPoint" "isOverCurrent" "averageAmps" "ampsPerHour" "wattPerHour" "upTime"; do
|
||||||
|
val=$(echo "$stats" | jq -r ".data.message.$stat")
|
||||||
|
mqtt_pub "$stat" "$val"
|
||||||
|
done
|
||||||
|
for stat in "powerHubStatus" "powerVariablePortStatus" "ppbA_DualUSB2Status"; do
|
||||||
|
val=$(echo "$stats" | jq -r ".data.message.$stat.state")
|
||||||
|
mqtt_pub "$stat" "$val"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
###
|
||||||
|
###
|
||||||
|
|
||||||
|
do_mqtt() {
|
||||||
|
do_getKey
|
||||||
|
JSON_DATA=$(do_api "/Driver/PPBAdvance/Report?DriverUniqueKey=${PPBAkey}")
|
||||||
|
DEVICE_ID="ppbadv_gen2"
|
||||||
|
DISCOVERY_PREFIX="homeassistant"
|
||||||
|
STATE_TOPIC="$DEVICE_ID/state"
|
||||||
|
STATE_TOPIC="PPBA/status"
|
||||||
|
POLL_INTERVAL=30 # seconds
|
||||||
|
|
||||||
|
publish_discovery() {
|
||||||
|
echo "Publishing Home Assistant MQTT discovery configs..."
|
||||||
|
# Helper to publish a single discovery config message
|
||||||
|
publish_sensor() {
|
||||||
|
local ENTITY="$1"
|
||||||
|
local NAME="$2"
|
||||||
|
local UNIT="$3"
|
||||||
|
local DEVICE_CLASS="$4"
|
||||||
|
local VALUE_TEMPLATE="{{ value_json.$ENTITY }}"
|
||||||
|
local TOPIC="$DISCOVERY_PREFIX/sensor/$DEVICE_ID/${ENTITY}/config"
|
||||||
|
local PAYLOAD="{ \
|
||||||
|
\"name\": \"$NAME\", \
|
||||||
|
\"state_topic\": \"$STATE_TOPIC/${ENTITY}\", \
|
||||||
|
\"unique_id\": \"${DEVICE_ID}_${ENTITY}\", \
|
||||||
|
\"device\": { \
|
||||||
|
\"identifiers\": [\"$DEVICE_ID\"], \
|
||||||
|
\"manufacturer\": \"Pegasus Astro\", \
|
||||||
|
\"model\": \"Pocket Powerbox Advance Gen2\", \
|
||||||
|
\"name\": \"PPB Advance Gen2\" \
|
||||||
|
}"
|
||||||
|
|
||||||
|
# Optional fields
|
||||||
|
if [ -n "$UNIT" ]; then
|
||||||
|
PAYLOAD="$PAYLOAD, \"unit_of_measurement\": \"$UNIT\""
|
||||||
|
fi
|
||||||
|
if [ -n "$DEVICE_CLASS" ]; then
|
||||||
|
PAYLOAD="$PAYLOAD, \"device_class\": \"$DEVICE_CLASS\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
PAYLOAD="$PAYLOAD }"
|
||||||
|
mosquitto_pub -h "$MQTT_HOST" -u "$MQTT_USER" -P "$MQTT_PASS" \
|
||||||
|
-t "$TOPIC" -m "$PAYLOAD" -r
|
||||||
|
}
|
||||||
|
# Sensors
|
||||||
|
publish_sensor "voltage" "PPB Voltage" "V" "voltage"
|
||||||
|
publish_sensor "current" "PPB Current" "A" "current"
|
||||||
|
publish_sensor "temperature" "PPB Temperature" "°C" "temperature"
|
||||||
|
publish_sensor "humidity" "PPB Humidity" "%" "humidity"
|
||||||
|
publish_sensor "dewPoint" "PPB Dew Point" "°C" ""
|
||||||
|
publish_sensor "averageAmps" "PPB Average Amps" "A" ""
|
||||||
|
publish_sensor "ampsPerHour" "PPB Amps Per Hour" "Ah" ""
|
||||||
|
publish_sensor "wattPerHour" "PPB Watt Per Hour" "Wh" ""
|
||||||
|
publish_sensor "upTime" "PPB Uptime" "" ""
|
||||||
|
publish_sensor "powerHubStatus" "PPB Power Hub Status" "" ""
|
||||||
|
publish_sensor "powerVariablePortStatus" "PPB Variable Port" "" ""
|
||||||
|
publish_sensor "ppbA_DualUSB2Status" "PPB Dual USB2 Status" "" ""
|
||||||
|
}
|
||||||
|
publish_discovery
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
while [ -n "$1" ]; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help) do_help; shift 1;;
|
||||||
|
--ip) PODip="$2"; shift 2;;
|
||||||
|
--port) PODport="$2"; shift 2;;
|
||||||
|
--init*) do_init; shift 1;;
|
||||||
|
--start) do_start; shift 1;;
|
||||||
|
--mqtt*) do_mqtt; shift 1;;
|
||||||
|
--stat*) do_stats; shift 1;;
|
||||||
|
--power) myCommand="power${2:-on}"; myDevice="$3"; shift 3;;
|
||||||
|
--poweron) myCommand="poweron"; myDevice="$2"; shift 2;;
|
||||||
|
--poweroff) myCommand="poweroff"; myDevice="$2"; shift 2;;
|
||||||
|
-d|--debug) debug="true"; shift 1;;
|
||||||
|
*) shift 1;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
case "$myCommand" in
|
||||||
|
poweroff) do_poweroff;;
|
||||||
|
poweron) do_poweron;;
|
||||||
|
# *) do_help;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Updates our crontab file so that it has all the stuff we need
|
||||||
|
|
||||||
|
cat << EOF | crontab
|
||||||
|
# Updates Nagios day/night time includes for current seasonal sunrise/sunset
|
||||||
|
01 00 * * * \${HOME}/NWC2026-AstropotaPOD/daynight.sh
|
||||||
|
|
||||||
|
# MQTT Stats from PPBA
|
||||||
|
* * * * * \${HOME}/NWC2026-AstropotaPOD/AstropotaPOD.sh --stats
|
||||||
|
EOF
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Updates our crontab file so that it has all the stuff we need
|
||||||
|
InfluxToken="InfluxDB Token Goes Here"
|
||||||
|
flairID="Flair Client ID Goes Here"
|
||||||
|
flairCS="Flair Client Secret Goes Here"
|
||||||
|
|
||||||
|
cat << EOF | crontab
|
||||||
|
@reboot ${HOME}/NWC2026-AstropotaPOD/start_gearman.sh
|
||||||
|
@reboot ${HOME}/NWC2026-AstropotaPOD/AstropotaPOD.sh --init --start --mqtt
|
||||||
|
|
||||||
|
# Get AstropotaPOD Stats and set up the MQTT HA integration information
|
||||||
|
0 * * * * ${HOME}/NWC2026-AstropotaPOD/AstropotaPOD.sh --mqtt
|
||||||
|
* * * * * ${HOME}/NWC2026-AstropotaPOD/AstropotaPOD.sh --stats
|
||||||
|
|
||||||
|
# Pull stats and throw into InfluxDB
|
||||||
|
* * * * * INFLUX_TOKEN=${InfluxToken} ${HOME}/NWC2026-AstropotaPOD/telegraf.sh --local
|
||||||
|
*/2 * * * * INFLUX_TOKEN=${InfluxToken} ${HOME}/NWC2026-AstropotaPOD/telegraf.sh --ppba
|
||||||
|
*/3 * * * * INFLUX_TOKEN=${InfluxToken} ${HOME}/NWC2026-AstropotaPOD/telegraf.sh --iss
|
||||||
|
*/15 * * * * INFLUX_TOKEN=${InfluxToken} ${HOME}/NWC2026-AstropotaPOD/telegraf.sh --weather
|
||||||
|
|
||||||
|
# Grab Puck temp stats and throw into MQTT (which will then be pulled into Nagios via a check_mqtt plugin)
|
||||||
|
*/5 * * * * FLAIR_CLIENT_ID="${flairID}" FLAIR_CLIENT_SECRET="${flairCS}" ${HOME}/NWC2026-AstropotaPOD/flair2MQTT.sh bridge
|
||||||
|
EOF
|
||||||
Executable
+188
@@ -0,0 +1,188 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
. /home/eloyd/NWC2026-AstropotaPOD/AstropotaUtils.sh
|
||||||
|
|
||||||
|
# Default states, minimums and maximums, etc
|
||||||
|
mqtt_host="192.168.0.6"
|
||||||
|
debug=""
|
||||||
|
min=""
|
||||||
|
max=""
|
||||||
|
warn=""
|
||||||
|
crit=""
|
||||||
|
exitCode="0"
|
||||||
|
statusText="OK"
|
||||||
|
|
||||||
|
# Definitions for where the AstropotaPOD is located (using Nagios Global Headquarters)
|
||||||
|
LAT="44.973N"
|
||||||
|
LON="93.155W"
|
||||||
|
TARGET_TZ="EDT"
|
||||||
|
sunwait="/home/eloyd/NWC2026-AstropotaPOD/sunwait"
|
||||||
|
|
||||||
|
do_debug() {
|
||||||
|
[ -z "$debug" ] && return
|
||||||
|
echo "DEBUG: $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
do_help() {
|
||||||
|
echo "Usage: $0 -w <warn> -c <crit> -m <min> -M <max> [-d] (kp | flux | sun | puck <field> | NINA <topic> | weather <field> | ppba <field>)"
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
while getopts "w:c:m:M:hd" opt; do
|
||||||
|
case "$opt" in
|
||||||
|
w) warn="$OPTARG";;
|
||||||
|
c) crit="$OPTARG";;
|
||||||
|
m) min="$OPTARG";;
|
||||||
|
M) max="$OPTARG";;
|
||||||
|
h) do_help;;
|
||||||
|
d) debug="true";;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
shift $((OPTIND - 1))
|
||||||
|
command="$@"
|
||||||
|
[ "$warn" = "-c" -o "$warn" = "ignored" ] && warn=""
|
||||||
|
[ "$crit" = "ignored" ] && crit=""
|
||||||
|
[ "$min" = "-M" -o "$min" = "ignored" ] && min=""
|
||||||
|
[ "$max" = "ignored" ] && max=""
|
||||||
|
do_debug "We'll be looking for: $command [-w $warn] [-c $crit] [-m $min] [-M $max]"
|
||||||
|
|
||||||
|
get_weather() {
|
||||||
|
do_debug "In get_weather"
|
||||||
|
do_debug "mosquitto_sub -W 5 -h $mqtt_host -t AstropotaPOD/weather/$1 -C 1"
|
||||||
|
mosquitto_sub -W 5 -h $mqtt_host -t AstropotaPOD/weather/$1 -C 1
|
||||||
|
}
|
||||||
|
|
||||||
|
convert_to_time_t() {
|
||||||
|
theTime="$1"
|
||||||
|
whatDay="${2:-today}"
|
||||||
|
theTZ="${3:-EDT}"
|
||||||
|
do_debug "convert_to_time_t: theTime=$theTime, whatDay=$whatDay"
|
||||||
|
NOW=$(date +%s) # Now in time_t
|
||||||
|
theDate=$(date -d "$whatDay" +"%d-%b-%Y")
|
||||||
|
do_debug "theDate=$theDate"
|
||||||
|
nextEvent=$(date -d "$theDate $theTime ${theTZ}" +%s)
|
||||||
|
do_debug "nextEvent=$nextEvent"
|
||||||
|
echo "$nextEvent"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Sunwait will get today's rise (rise) today's set (set) or tomorrow's rise (tomorrow)
|
||||||
|
get_sunrise() {
|
||||||
|
do_debug "In get_sunrise"
|
||||||
|
NOW=$(date +%s)
|
||||||
|
do_debug "NOW=$NOW"
|
||||||
|
dawnTime=$($sunwait list 1 nautical rise $LAT $LON | tail -1)
|
||||||
|
nextDawn=$(convert_to_time_t $dawnTime)
|
||||||
|
# If it's 2am, then NOW is less than DAWN, we don't need to do anything.
|
||||||
|
# If it's 2pm, then NOW is greater than DAWN, so we need to get tomorrow's dawn.
|
||||||
|
if [ "$NOW" -gt "$nextDawn" ]; then
|
||||||
|
do_debug "Need to look at tomorrow's dawn"
|
||||||
|
dawnTime=$($sunwait list 2 nautical rise $LAT $LON | tail -1)
|
||||||
|
nextDawn=$(convert_to_time_t $dawnTime tomorrow)
|
||||||
|
do_debug "Tomorrow's dawn is nextDawn=$nextDawn"
|
||||||
|
else
|
||||||
|
do_debug "Dawn is still in the future for today, so the next dawn is today's dawn: $nextDawn"
|
||||||
|
fi
|
||||||
|
do_debug "dawnTime=$dawnTime, nextDawn=$nextDawn"
|
||||||
|
duskTime=$($sunwait list nautical set $LAT $LON)
|
||||||
|
nextDusk=$(date -d "${dateToday} ${duskTime} ${TARGET_TZ}" +%s)
|
||||||
|
do_debug "duskTime=$duskTime, nextDusk=$nextDusk"
|
||||||
|
echo "nextDusk=$nextDusk, nextDawn=$nextDawn"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_mqtt() {
|
||||||
|
do_debug "In get_mqtt"
|
||||||
|
do_debug "mosquitto_sub -W 5 -h $mqtt_host -t \"$1\" -C 1"
|
||||||
|
mosquitto_sub -W 5 -h $mqtt_host -t "$1" -C 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_puck() {
|
||||||
|
do_debug "In get_puck"
|
||||||
|
do_debug "mosquitto_sub -W 5 -h $mqtt_host -t AstropotaPOD/sensor/AstropotaPOD-e9af/$1 -C 1"
|
||||||
|
mosquitto_sub -W 5 -h $mqtt_host -t AstropotaPOD/sensor/AstropotaPOD-e9af/$1 -C 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_ppba() {
|
||||||
|
do_debug "In get_ppba"
|
||||||
|
do_debug "mosquitto_sub -W 5 -h $mqtt_host -t PPBA/status/$1 -C 1"
|
||||||
|
mosquitto_sub -W 5 -h $mqtt_host -t PPBA/status/$1 -C 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_NINA() {
|
||||||
|
do_debug "In get_NINA"
|
||||||
|
do_debug "mosquitto_sub -W 5 -h $mqtt_host -t Astro/NINA/$1 -C 1"
|
||||||
|
mosquitto_sub -W 5 -h $mqtt_host -t Astro/NINA/$1 -C 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get_suntime() {
|
||||||
|
do_debug "In get_suntime"
|
||||||
|
${sunwait} poll nautical $LAT $LON >/dev/null
|
||||||
|
sunState=$?
|
||||||
|
do_debug "sunState=$sunState"
|
||||||
|
exitCode=0
|
||||||
|
statusText="OK"
|
||||||
|
case "$sunState" in
|
||||||
|
2) exitCode=1; statusText="WARNING - Sky is too bright for imaging.";;
|
||||||
|
3) exitCode=0; statusText="OK - Sky is dark. Safe to image deep-sky targets.";;
|
||||||
|
*) exitCode=3; statusText="UNKNOWN - invalid code returned from sunwait command";;
|
||||||
|
esac
|
||||||
|
sunRise=$(get_sunrise)
|
||||||
|
echo "${statusText} | IsDark=$exitCode, $sunRise;;;;"
|
||||||
|
exit $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
# main
|
||||||
|
value=""
|
||||||
|
do_debug "command is $command"
|
||||||
|
case "$command" in
|
||||||
|
f*|F*) command="10MeV-Flux"; value=$(curl -s "https://services.swpc.noaa.gov/json/goes/primary/integral-protons-7-day.json" | jq '. | map(select(.energy == ">=10 MeV")) | last | .flux');;
|
||||||
|
k*|K*) command="Kp-Index"; value=$(curl -s https://services.swpc.noaa.gov/products/noaa-planetary-k-index.json | jq '[last][].Kp');;
|
||||||
|
n*|N*) command="$2"; value=$(get_NINA $2);;
|
||||||
|
s*|S*) command="Suntime"; get_suntime;;
|
||||||
|
t*|T*) command="SunRiseSunSet"; value=$(get_sunrise);;
|
||||||
|
w*|W*) command="$2"; value=$(get_weather $2);;
|
||||||
|
mqtt*) command="$2"; value=$(get_mqtt $2);;
|
||||||
|
ppba*) command="$2"; value=$(get_ppba $2);;
|
||||||
|
puck*) command="$2"; value=$(get_puck $2);;
|
||||||
|
*) echo "unknown command was $command";;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# --- FLOATING POINT FORMATTING ---
|
||||||
|
# If value is not empty/unknown, check its formatting
|
||||||
|
if [ "$value" != "?" ]; then
|
||||||
|
# 1. First, check if it contains RA/Dec formatting symbols (: or °)
|
||||||
|
# If it does, bypass formatting completely to preserve the raw coordinate string
|
||||||
|
if [[ "$value" == *":"* ]] || [[ "$value" == *"°"* ]]; then
|
||||||
|
value="$value"
|
||||||
|
|
||||||
|
# 2. Otherwise, check if it's a strict decimal number and pad/round to 4 spaces
|
||||||
|
elif [[ "$value" =~ ^-?[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
value=$(printf "%.4f" "$value")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# CRITICAL check must always run first to ensure it takes precedence
|
||||||
|
statusText=$(check_threshold "$value" "$warn" "$crit")
|
||||||
|
exitCode=$?
|
||||||
|
echo "$command $statusText: $value|$command=$value;$warn;$crit;$min;$max"
|
||||||
|
exit $exitCode
|
||||||
|
|
||||||
|
# --- NAGIOS OUTPUT STAGE ---
|
||||||
|
# Because check_threshold already figured out the severity, we just match the exit code
|
||||||
|
case $EXIT_CODE in
|
||||||
|
0)
|
||||||
|
echo "OK - Value is $value | $command=$value;$warn;$crit;$min;$max"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
echo "WARNING - Value $value is outside of warning range ($warn) | $command=$value;$warn;$crit;$min;$max"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
echo "CRITICAL - Value $value is outside of critical range ($crit) | $command=$value;$warn;$crit;$min;$max"
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "UNKNOWN - Received unexpected response from check engine | $command=$value;$warn;$crit;$min;$max"
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
Executable
+152
@@ -0,0 +1,152 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
hostname=""
|
||||||
|
community=""
|
||||||
|
counter=""
|
||||||
|
perfName=""
|
||||||
|
iname=""
|
||||||
|
inum=""
|
||||||
|
debug=""
|
||||||
|
min=""
|
||||||
|
max=""
|
||||||
|
warn=""
|
||||||
|
crit=""
|
||||||
|
exitCode="0"
|
||||||
|
statusText="OK"
|
||||||
|
|
||||||
|
# snmpwalk -v 2c -c public 192.168.0.1 1.3.6.1.2.1.2.2.1.2 | sed -e "s/^IF-MIB::ifDescr.//" | awk '{print $NF, $1}' | while read iname inum; do echo "$iname"; for oid in 1.3.6.1.2.1.2.2.1.5 1.3.6.1.2.1.2.2.1.10 1.3.6.1.2.1.2.2.1.16 1.3.6.1.2.1.2.2.1.20; do snmpget -v 2c -c public 192.168.0.1 ${oid}.${inum}; done; done
|
||||||
|
# current bandwidth snmpwalk -v 2c -c public 192.168.1.1 1.3.6.1.2.1.2.2.1.5
|
||||||
|
# octets received snmpwalk -v 2c -c public 192.168.1.1 1.3.6.1.2.1.2.2.1.10
|
||||||
|
# octets sent snmpwalk -v 2c -c public 192.168.1.1 1.3.6.1.2.1.2.2.1.16
|
||||||
|
# error octets 1.3.6.1.2.1.2.2.1.20
|
||||||
|
|
||||||
|
do_debug() {
|
||||||
|
[ -z "$debug" ] && return
|
||||||
|
echo "DEBUG: $*" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
do_help() {
|
||||||
|
echo "Usage: $0 -H <hostname> -C <community> [-I <interface name> | -i <inum>) -t (b|r|s|e) [-w <warn>] [-c <crit>]"
|
||||||
|
# -w <warn> -c <crit> -m <min> -M <max> [-d] (kp | flux)"
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
while getopts "dhC:H:I:i:t:w:c:" opt; do
|
||||||
|
case "$opt" in
|
||||||
|
H) hostname="$OPTARG";;
|
||||||
|
I) iname="$OPTARG";;
|
||||||
|
i) inum="$OPTARG";;
|
||||||
|
t) counter="$OPTARG";;
|
||||||
|
C) community="$OPTARG";;
|
||||||
|
w) warn="$OPTARG";;
|
||||||
|
c) crit="$OPTARG";;
|
||||||
|
h) do_help;;
|
||||||
|
d) debug="true";;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
shift $((OPTIND - 1))
|
||||||
|
command="$@"
|
||||||
|
do_debug "host=$hostname iname=$iname inum=$inum counter=$counter warn=$warn crit=$crit"
|
||||||
|
[ -z "$hostname" ] && echo "No hostname specified." && exit 0
|
||||||
|
[ -z "$community" ] && echo "No community specified." && exit 0
|
||||||
|
|
||||||
|
check_threshold() {
|
||||||
|
local val=$1
|
||||||
|
local range=$2
|
||||||
|
local alert_inside=0
|
||||||
|
local start=0
|
||||||
|
local end="inf"
|
||||||
|
# Handle empty thresholds safely
|
||||||
|
if [[ -z "$range" ]]; then return 0; fi
|
||||||
|
# Check for the inside-range prefix '@'
|
||||||
|
if [[ "$range" == @* ]]; then
|
||||||
|
alert_inside=1
|
||||||
|
range="${range#@}" # Strip the '@'
|
||||||
|
fi
|
||||||
|
# Parse start and end values out of the range colon (:) syntax
|
||||||
|
if [[ "$range" == *:* ]]; then
|
||||||
|
start="${range%%:*}"
|
||||||
|
end="${range#*:}"
|
||||||
|
# Handle implicit defaults
|
||||||
|
[[ -z "$start" ]] && start=0
|
||||||
|
[[ "$start" == "~" ]] && start="-inf"
|
||||||
|
[[ -z "$end" ]] && end="inf"
|
||||||
|
else
|
||||||
|
# No colon: Shorthand for 0:X
|
||||||
|
start=0
|
||||||
|
end="$range"
|
||||||
|
fi
|
||||||
|
# Evaluate logic using bc (handles floats and floating point math)
|
||||||
|
local outside=0
|
||||||
|
# Check lower boundary; inclusive of endpoints
|
||||||
|
if [[ "$start" != "-inf" ]]; then
|
||||||
|
if (( $(echo "$val < $start" | bc -l) )); then outside=1; fi
|
||||||
|
fi
|
||||||
|
# Check upper boundary; inclusive of endpoints
|
||||||
|
if [[ "$end" != "inf" ]]; then
|
||||||
|
if (( $(echo "$val > $end" | bc -l) )); then outside=1; fi
|
||||||
|
fi
|
||||||
|
# Return true/false based on whether the @ symbol inverted the logic
|
||||||
|
if [[ $alert_inside -eq 1 ]]; then
|
||||||
|
# Alert if INSIDE the range
|
||||||
|
if [[ $outside -eq 0 ]]; then return 1; else return 0; fi
|
||||||
|
else
|
||||||
|
# Alert if OUTSIDE the range (Standard Nagios Behavior)
|
||||||
|
if [[ $outside -eq 1 ]]; then return 1; else return 0; fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
do_snmp() {
|
||||||
|
myNum="$1"
|
||||||
|
myOid="$2"
|
||||||
|
myName="$3"
|
||||||
|
do_debug "checking $hostname for $myName (inum: $myNum) on $myOid:"
|
||||||
|
output=$(snmpget -v 2c -c $community $hostname ${myOid}.${myNum} | awk '{print $NF}')
|
||||||
|
if [ -z "$output" ]; then
|
||||||
|
echo "UNKNOWN: snmpget error"
|
||||||
|
exit 3
|
||||||
|
else
|
||||||
|
echo "$output"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
do_stats() {
|
||||||
|
myNum="$1"
|
||||||
|
myName="$2"
|
||||||
|
myOid=""
|
||||||
|
do_debug "do_stats: myNum=$myNum myName=$myName myOid=$myOid"
|
||||||
|
case "$counter" in
|
||||||
|
b) counter="Bandwidth"; perfName="'bw_bytes'"; myOid="1.3.6.1.2.1.2.2.1.5";;
|
||||||
|
r) counter="Received"; perfName="'bytes_recv'"; myOid="1.3.6.1.2.1.2.2.1.10";;
|
||||||
|
s) counter="Sent"; perfName="'bytes_sent'"; myOid="1.3.6.1.2.1.2.2.1.16";;
|
||||||
|
e) counter="Errors"; perfName="'bytes_errors'"; myOid="1.3.6.1.2.1.2.2.1.20";;
|
||||||
|
*) echo "Invalid counter type specified."; exit 0;;
|
||||||
|
esac
|
||||||
|
do_debug "do_stats: do_snmp $myNum $myOid $myName"
|
||||||
|
value=$(do_snmp $myNum $myOid $myName)
|
||||||
|
# CRITICAL check must always run first to ensure it takes precedence
|
||||||
|
if ! check_threshold "$value" "$crit"; then
|
||||||
|
exitCode=2
|
||||||
|
statusText="CRITICAL"
|
||||||
|
elif ! check_threshold "$value" "$warn"; then
|
||||||
|
exitCode=1
|
||||||
|
statusText="WARNING"
|
||||||
|
fi
|
||||||
|
# [ "$value" != "?" ] && value=$(printf "%.4f" "$value")
|
||||||
|
echo "$myName $counter $statusText: $value|$perfName=${value}B;$warn;$crit;$min;$max"
|
||||||
|
exit $exitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "$inum" ]; then
|
||||||
|
do_debug "Specified inum=$inum"
|
||||||
|
do_stats $inum
|
||||||
|
else
|
||||||
|
do_debug "Did not specify an inum"
|
||||||
|
do_debug "snmpwalk -v 2c -c $community $hostname 1.3.6.1.2.1.2.2.1.2"
|
||||||
|
snmpwalk -v 2c -c $community $hostname 1.3.6.1.2.1.2.2.1.2 | sed -e "s/^IF-MIB::ifDescr.//" | awk '{print $1, $NF}' | while read myNum myName; do
|
||||||
|
[ -z "$iname" -a -z "$inum" ] && echo "iname: $myName (inum: $myNum)" && continue # Show interfaces if we didn't specify one
|
||||||
|
[ "$iname" != "$myName" ] && continue
|
||||||
|
do_debug "Found $iname (ifnum: $myNum)"
|
||||||
|
do_stats $myNum
|
||||||
|
done
|
||||||
|
fi
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# check_iss.sh
|
||||||
|
|
||||||
|
API="http://api.open-notify.org/iss-now.json"
|
||||||
|
|
||||||
|
# Query the public ISS live API
|
||||||
|
DATA=$(curl --connect-timeout 5 --max-time 15 -s "$API")
|
||||||
|
|
||||||
|
if [ "$?" -gt 0 ]; then
|
||||||
|
echo "UNKNOWN - $API returned no data or serious error: $DATA"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
if [ $(echo "$DATA" | jq -r '.message') != "success" ]; then
|
||||||
|
echo "UNKNOWN - $API timed out"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract coordinates
|
||||||
|
echo $DATA | jq -r '.iss_position | .latitude + " " + .longitude' | \
|
||||||
|
(read LAT LON; \
|
||||||
|
echo "OK - ISS Location: Lat $LAT, Lon $LON | ISS_lat=$LAT;;;; ISS_lon=$LON;;;;")
|
||||||
|
exit 0
|
||||||
Executable
+106
@@ -0,0 +1,106 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# UNIVERSAL KASA HS103 NAGIOS PLUGIN
|
||||||
|
# Usage: ./check_kasa.sh <IP_ADDRESS> <relay_state|rssi|on_time|sw_ver|mac>
|
||||||
|
# ==========================================================================
|
||||||
|
|
||||||
|
plugIP=""
|
||||||
|
plugPort="9999"
|
||||||
|
metric=""
|
||||||
|
|
||||||
|
while getopts "H:m:P:" opt; do
|
||||||
|
case "$opt" in
|
||||||
|
H) plugIP="$OPTARG";;
|
||||||
|
P) plugPort="$OPTARG";;
|
||||||
|
m) metric="$OPTARG";;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
shift $((OPTIND - 1))
|
||||||
|
restOfLine="$@"
|
||||||
|
|
||||||
|
if [ -z "$plugIP" ] || [ -z "$metric" ]; then
|
||||||
|
echo "UNKNOWN - Missing arguments. Usage: $0 <IP> <metric>"
|
||||||
|
echo " metric: relay_state rssi on_time sw_ver hw_ver mac model"
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Pre-encrypted XOR payload for get_sysinfo
|
||||||
|
# NOTE: Why this value is what it is is beyond the scope of this script
|
||||||
|
PAYLOAD="AAAAI9Dw0qHYq9+61/XPtJS20bTAn+yV5o/hh+jK8J7rh+vLtpbr"
|
||||||
|
|
||||||
|
# Fetch raw binary stream as clean decimal strings
|
||||||
|
RAW_STREAM=$(echo "$PAYLOAD" | base64 -d | nc -w 3 "$plugIP" "$plugPort" | od -An -v -t u1)
|
||||||
|
|
||||||
|
if [ -z "$RAW_STREAM" ]; then
|
||||||
|
echo "CRITICAL - No network response from HS103 at $plugIP"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# XOR Decryption Loop
|
||||||
|
DECRYPTED_JSON=""
|
||||||
|
KEY=171
|
||||||
|
|
||||||
|
for BYTE in $RAW_STREAM; do
|
||||||
|
if [ "$BYTE" -eq 0 ] && [ -z "$DECRYPTED_JSON" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
OUTPUT=$(( BYTE ^ KEY ))
|
||||||
|
KEY=$BYTE
|
||||||
|
DECRYPTED_JSON+="$(printf "\\$(printf '%03o' "$OUTPUT")")"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Clean out trailing encryption anomalies
|
||||||
|
CLEAN_JSON=$(echo "$DECRYPTED_JSON" | grep -o '{.*}' | sed 's/.$//')
|
||||||
|
|
||||||
|
# Extract the alias name universally for clean alert messaging
|
||||||
|
ALIAS=$(echo "$CLEAN_JSON" | jq -r '.get_sysinfo.alias')
|
||||||
|
|
||||||
|
# Dynamic Evaluation Engine based on your CLI parameter Choice
|
||||||
|
case "$metric" in
|
||||||
|
relay_state)
|
||||||
|
VALUE=$(echo "$CLEAN_JSON" | jq '.get_sysinfo.relay_state')
|
||||||
|
if [ "$VALUE" -eq 1 ]; then
|
||||||
|
echo "OK - ${ALIAS} Relay is ON | relay_state=1;;;;"
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "CRITICAL - ${ALIAS} Relay is OFF | relay_state=0;;;;"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
rssi)
|
||||||
|
VALUE=$(echo "$CLEAN_JSON" | jq '.get_sysinfo.rssi')
|
||||||
|
# Standard Wi-Fi signal threshold parameters
|
||||||
|
if [ "$VALUE" -ge -70 ]; then
|
||||||
|
echo "OK - ${ALIAS} Wi-Fi Signal Strength: ${VALUE} dBm | rssi=${VALUE};-75;-85;;"
|
||||||
|
exit 0
|
||||||
|
elif [ "$VALUE" -lt -70 ] && [ "$VALUE" -ge -82 ]; then
|
||||||
|
echo "WARNING - ${ALIAS} Wi-Fi Signal Weak: ${VALUE} dBm | rssi=${VALUE};-75;-85;;"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "CRITICAL - ${ALIAS} Wi-Fi Signal Dropping Out: ${VALUE} dBm | rssi=${VALUE};-75;-85;;"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
on_time)
|
||||||
|
VALUE=$(echo "$CLEAN_JSON" | jq '.get_sysinfo.on_time')
|
||||||
|
# Converts raw seconds to readable hours for clear telemetry status
|
||||||
|
HOURS=$(echo "scale=2; $VALUE / 3600" | bc)
|
||||||
|
echo "OK - ${ALIAS} Continuous Active Runtime: ${HOURS} Hours | on_time=${VALUE};;;;"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
sw_ver|hw_ver|mac|model)
|
||||||
|
# Dynamic handling for inventory/metadata parameters
|
||||||
|
VALUE=$(echo "$CLEAN_JSON" | jq -r ".get_sysinfo.${metric}")
|
||||||
|
echo "OK - ${ALIAS} ${metric}: ${VALUE}"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "UNKNOWN - Unsupported metric: ${metric}. Choose: relay_state, rssi, on_time, sw_ver, hw_ver, mac, model"
|
||||||
|
exit 3
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+48
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Grab parameters with built-in fallbacks
|
||||||
|
SEARCH_REGEX="${1:-Starlink}"
|
||||||
|
LIMIT_COUNT="${2:-1}"
|
||||||
|
|
||||||
|
# Convert search string to lowercase for the safety pool check
|
||||||
|
LOWER_SEARCH=$(echo "$SEARCH_REGEX" | tr '[:upper:]' '[:lower:]')
|
||||||
|
|
||||||
|
# Define an intelligent data pool cap based on user request
|
||||||
|
if [[ "$LOWER_SEARCH" == *"starlink"* || "$LOWER_SEARCH" == *"oneweb"* || "$LOWER_SEARCH" == *"|"* ]]; then
|
||||||
|
# For specific trackers, fetch a wider baseline pool (at least 20)
|
||||||
|
FETCH_LIMIT=$(( LIMIT_COUNT > 20 ? LIMIT_COUNT : 20 ))
|
||||||
|
else
|
||||||
|
# Otherwise, trust the user input directly
|
||||||
|
FETCH_LIMIT=$LIMIT_COUNT
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Hard-clamp the final fetch request strictly to 100 (the API's absolute max ceiling)
|
||||||
|
if [ "$FETCH_LIMIT" -gt 100 ]; then
|
||||||
|
FETCH_LIMIT=100
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Fetch the upcoming space manifest using the stable 2.3.0 production API
|
||||||
|
DATA=$(curl -s "https://ll.thespacedevs.com/2.3.0/launches/upcoming/?limit=${FETCH_LIMIT}")
|
||||||
|
|
||||||
|
# Defensive Check: Ensure the API returned a valid JSON payload containing "results"
|
||||||
|
if ! echo "$DATA" | jq -e '.results' >/dev/null 2>&1; then
|
||||||
|
echo "CRITICAL - API Error: Received invalid response or rate limit from server | launch_alert=0"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Filter the data pool locally using your regex, then slice cleanly to your display limit
|
||||||
|
LAUNCH_INFO=$(echo "$DATA" | jq -r --arg regex "$SEARCH_REGEX" --argjson limit "$LIMIT_COUNT" '
|
||||||
|
.results |
|
||||||
|
map(select(.name | test($regex; "i"))) |
|
||||||
|
.[0:$limit] |
|
||||||
|
.[] |
|
||||||
|
"\(.name) @ \(.net)"')
|
||||||
|
|
||||||
|
if [ ! -z "$LAUNCH_INFO" ]; then
|
||||||
|
# Output matches cleanly formatted for both CLI tracking and Nagios perf-piping
|
||||||
|
echo "OK - Target Approaching: $LAUNCH_INFO" | tr -d "|" | sed '$s/$/ | launch_alert=1/'
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "OK - No flights matching pattern [$SEARCH_REGEX] found in the upcoming queue | launch_alert=0"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
+357
@@ -0,0 +1,357 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""
|
||||||
|
SYNOPSIS
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import optparse
|
||||||
|
import traceback
|
||||||
|
import ssl
|
||||||
|
|
||||||
|
# Python 2/3 Compatibility imports
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
try:
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.error
|
||||||
|
except ImportError:
|
||||||
|
import urllib2
|
||||||
|
import urllib
|
||||||
|
|
||||||
|
try:
|
||||||
|
urlencode = urllib.parse.urlencode
|
||||||
|
except AttributeError:
|
||||||
|
urlencode = urllib.urlencode
|
||||||
|
|
||||||
|
try:
|
||||||
|
urlopen = urllib.request.urlopen
|
||||||
|
except AttributeError:
|
||||||
|
urlopen = urllib2.urlopen
|
||||||
|
|
||||||
|
try:
|
||||||
|
urlquote = urllib.parse.quote
|
||||||
|
except AttributeError:
|
||||||
|
urlquote = urllib.quote
|
||||||
|
|
||||||
|
try:
|
||||||
|
urlerror = urllib.error.URLError
|
||||||
|
except AttributeError:
|
||||||
|
urlerror = urllib2.URLError
|
||||||
|
|
||||||
|
try:
|
||||||
|
httperror = urllib.error.HTTPError
|
||||||
|
except AttributeError:
|
||||||
|
httperror = urllib2.HTTPError
|
||||||
|
|
||||||
|
import shlex
|
||||||
|
import re
|
||||||
|
import signal
|
||||||
|
|
||||||
|
|
||||||
|
__VERSION__ = '1.2.4'
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionError(Exception):
|
||||||
|
error_output_prefix = "UNKNOWN: An error occurred connecting to API. "
|
||||||
|
pass
|
||||||
|
|
||||||
|
class URLError(ConnectionError):
|
||||||
|
def __init__(self, error_message):
|
||||||
|
self.error_message = ConnectionError.error_output_prefix + "(Connection error: '" + error_message + "')"
|
||||||
|
|
||||||
|
class HTTPError(ConnectionError):
|
||||||
|
def __init__(self, error_message):
|
||||||
|
self.error_message = ConnectionError.error_output_prefix + "(HTTP error: '" + error_message + "')"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
version = 'check_ncpa.py, Version %s' % __VERSION__
|
||||||
|
|
||||||
|
parser = optparse.OptionParser()
|
||||||
|
parser.add_option("-H", "--hostname", help="The hostname to be connected to.")
|
||||||
|
parser.add_option("-M", "--metric", default='',
|
||||||
|
help="The metric to check, this is defined on client "
|
||||||
|
"system. This would also be the plugin name in the "
|
||||||
|
"plugins directory. Do not attach arguments to it, "
|
||||||
|
"use the -a directive for that. DO NOT INCLUDE the api/ "
|
||||||
|
"instruction.")
|
||||||
|
parser.add_option("-P", "--port", default=5693, type="int",
|
||||||
|
help="Port to use to connect to the client.")
|
||||||
|
parser.add_option("-w", "--warning", default=None, type="str",
|
||||||
|
help="Warning value to be passed for the check.")
|
||||||
|
parser.add_option("-c", "--critical", default=None, type="str",
|
||||||
|
help="Critical value to be passed for the check.")
|
||||||
|
parser.add_option("-u", "--units", default=None,
|
||||||
|
help="The unit prefix (k, Ki, M, Mi, G, Gi, T, Ti) for b and B unit "
|
||||||
|
"types which calculates the value returned.")
|
||||||
|
parser.add_option("-n", "--unit", default=None,
|
||||||
|
help="Overrides the unit with whatever unit you define. "
|
||||||
|
"Does not perform calculations. This changes the unit of measurement only.")
|
||||||
|
parser.add_option("-a", "--arguments", default=None,
|
||||||
|
help="Arguments for the plugin to be run. Not necessary "
|
||||||
|
"unless you're running a custom plugin. Given in the same "
|
||||||
|
"as you would call from the command line. Example: -a '-w 10 -c 20 -f /usr/local'")
|
||||||
|
parser.add_option("-t", "--token", default='',
|
||||||
|
help="The token for connecting.")
|
||||||
|
parser.add_option("-T", "--timeout", default=55, type="int",
|
||||||
|
help="Enforced timeout, will terminate plugins after "
|
||||||
|
"this amount of seconds. [%default]")
|
||||||
|
parser.add_option("-d", "--delta", action='store_true',
|
||||||
|
help="Signals that this check is a delta check and a "
|
||||||
|
"local state will kept.")
|
||||||
|
parser.add_option("-l", "--list", action='store_true',
|
||||||
|
help="List all values under a given node. Do not perform "
|
||||||
|
"a check.")
|
||||||
|
parser.add_option("-v", "--verbose", action='store_true',
|
||||||
|
help='Print more verbose error messages.')
|
||||||
|
parser.add_option("-D", "--debug", action='store_true',
|
||||||
|
help='Print LOTS of error messages. Used mostly for debugging.')
|
||||||
|
parser.add_option("-V", "--version", action='store_true',
|
||||||
|
help='Print version number of plugin.')
|
||||||
|
parser.add_option("-q", "--queryargs", default=None,
|
||||||
|
help='Extra query arguments to pass in the NCPA URL.')
|
||||||
|
parser.add_option("-s", "--secure", action='store_true', default=False,
|
||||||
|
help='Require successful certificate verification. Does not work on Python < 2.7.9.')
|
||||||
|
parser.add_option("-p", "--performance", action='store_true', default=False,
|
||||||
|
help='Print performance data even when there is none. '
|
||||||
|
'Will print data matching the return code of this script')
|
||||||
|
options, _ = parser.parse_args()
|
||||||
|
|
||||||
|
if options.version:
|
||||||
|
print(version)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
if options.arguments and options.metric and not 'plugin' in options.metric:
|
||||||
|
parser.print_help()
|
||||||
|
parser.error('You cannot specify arguments without running a custom plugin.')
|
||||||
|
|
||||||
|
if not options.hostname:
|
||||||
|
parser.print_help()
|
||||||
|
parser.error("Hostname is required for use.")
|
||||||
|
|
||||||
|
elif not options.metric and not options.list:
|
||||||
|
parser.print_help()
|
||||||
|
parser.error('No metric given, if you want to list all possible items '
|
||||||
|
'use --list.')
|
||||||
|
|
||||||
|
options.metric = re.sub(r'^/?(api/)?', '', options.metric)
|
||||||
|
|
||||||
|
return options
|
||||||
|
|
||||||
|
|
||||||
|
# ~ The following are all helper functions. I would normally split these out into
|
||||||
|
# ~ a new module but this needs to be portable.
|
||||||
|
|
||||||
|
|
||||||
|
def get_url_from_options(options):
|
||||||
|
host_part = get_host_part_from_options(options)
|
||||||
|
arguments = get_arguments_from_options(options)
|
||||||
|
return '%s?%s' % (host_part, arguments)
|
||||||
|
|
||||||
|
|
||||||
|
def get_host_part_from_options(options):
|
||||||
|
"""Gets the address that will be queries for the JSON.
|
||||||
|
|
||||||
|
"""
|
||||||
|
hostname = options.hostname
|
||||||
|
port = options.port
|
||||||
|
|
||||||
|
if not options.metric is None:
|
||||||
|
metric = urlquote(options.metric)
|
||||||
|
else:
|
||||||
|
metric = ''
|
||||||
|
|
||||||
|
arguments = get_check_arguments_from_options(options)
|
||||||
|
if not metric and not arguments:
|
||||||
|
api_address = 'https://%s:%d/api' % (hostname, port)
|
||||||
|
else:
|
||||||
|
api_address = 'https://%s:%d/api/%s/%s' % (hostname, port, metric, arguments)
|
||||||
|
|
||||||
|
return api_address
|
||||||
|
|
||||||
|
|
||||||
|
def get_check_arguments_from_options(options):
|
||||||
|
"""Gets the escaped URL for plugin arguments to be added
|
||||||
|
to the end of the host URL. This is different from the get_arguments_from_options
|
||||||
|
in that this is meant for the syntax when the user is calling a check, whereas the below
|
||||||
|
is when GET arguments need to be added.
|
||||||
|
|
||||||
|
"""
|
||||||
|
arguments = options.arguments
|
||||||
|
if arguments is None:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
lex = shlex.shlex(arguments)
|
||||||
|
lex.whitespace_split = True
|
||||||
|
arguments = '/'.join([urlquote(x, safe='') for x in lex])
|
||||||
|
return arguments
|
||||||
|
|
||||||
|
|
||||||
|
def get_arguments_from_options(options, **kwargs):
|
||||||
|
"""Returns the http query arguments. If there is a list variable specified,
|
||||||
|
it will return the arguments necessary to query for a list.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Note: Changed back to units due to the units being what is passed via the
|
||||||
|
# API call which can confuse people if they don't match
|
||||||
|
arguments = { 'token': options.token,
|
||||||
|
'units': options.units }
|
||||||
|
|
||||||
|
if not options.list:
|
||||||
|
arguments['warning'] = options.warning
|
||||||
|
arguments['critical'] = options.critical
|
||||||
|
arguments['delta'] = options.delta
|
||||||
|
arguments['check'] = 1
|
||||||
|
arguments['unit'] = options.unit
|
||||||
|
|
||||||
|
args = list((k, v) for k, v in list(arguments.items()) if v is not None)
|
||||||
|
|
||||||
|
# Get the options (comma separated)
|
||||||
|
if options.queryargs:
|
||||||
|
# for each comma, perform lookahead, split if we aren't inside quotes.
|
||||||
|
arguments_list = re.split(''',(?=(?:[^'"]|'[^']*'|"[^"]*")*$)''', options.queryargs)
|
||||||
|
for argument in arguments_list:
|
||||||
|
key, value = argument.split('=', 1)
|
||||||
|
if value is not None:
|
||||||
|
args.append((key, value))
|
||||||
|
|
||||||
|
#~ Encode the items in the dictionary that are not None
|
||||||
|
return urlencode(args)
|
||||||
|
|
||||||
|
|
||||||
|
def get_json(options):
|
||||||
|
"""Get the page given by the options. This will call down the url and
|
||||||
|
encode its finding into a Python object (from JSON).
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = get_url_from_options(options)
|
||||||
|
|
||||||
|
if options.verbose:
|
||||||
|
print('Connecting to: ' + url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
|
||||||
|
try:
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
if not options.secure:
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
ret = urlopen(url, context=ctx)
|
||||||
|
except AttributeError:
|
||||||
|
ret = urlopen(url)
|
||||||
|
|
||||||
|
except httperror as e:
|
||||||
|
try:
|
||||||
|
raise HTTPError('{0} {1}'.format(e.code, e.reason))
|
||||||
|
except AttributeError:
|
||||||
|
raise HTTPError('{0}'.format(e.code))
|
||||||
|
except urlerror as e:
|
||||||
|
raise URLError('{0}'.format(e.reason))
|
||||||
|
|
||||||
|
ret = ret.read()
|
||||||
|
|
||||||
|
if options.verbose:
|
||||||
|
print('File returned contained:\n' + ret.decode('utf-8'))
|
||||||
|
|
||||||
|
arr = json.loads(ret)
|
||||||
|
|
||||||
|
if options.list:
|
||||||
|
return arr
|
||||||
|
|
||||||
|
# Fix for NCPA < 2
|
||||||
|
if 'value' in arr:
|
||||||
|
arr = arr['value']
|
||||||
|
|
||||||
|
# We need to flip the returncode and stdout
|
||||||
|
if isinstance(arr['stdout'], int) and not isinstance(arr['returncode'], int):
|
||||||
|
tmp = arr['returncode']
|
||||||
|
arr['returncode'] = arr['stdout']
|
||||||
|
arr['stdout'] = tmp
|
||||||
|
|
||||||
|
# If we recieve and error, return critical and give out error text
|
||||||
|
elif 'error' in arr:
|
||||||
|
arr['stdout'] = 'CRITICAL: %s' % arr['error']
|
||||||
|
arr['returncode'] = 2
|
||||||
|
|
||||||
|
return arr
|
||||||
|
|
||||||
|
|
||||||
|
def run_check(info_json):
|
||||||
|
"""Run a check against the remote host.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if 'stdout' in info_json and 'returncode' in info_json:
|
||||||
|
return info_json['stdout'], info_json['returncode']
|
||||||
|
elif 'error' in info_json:
|
||||||
|
return info_json['error'], 3
|
||||||
|
|
||||||
|
|
||||||
|
def show_list(info_json):
|
||||||
|
"""Show the list of available options.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return json.dumps(info_json, indent=4), 0
|
||||||
|
|
||||||
|
|
||||||
|
def timeout_handler(threshold):
|
||||||
|
def wrapped(signum, frames):
|
||||||
|
stdout = "UNKNOWN: Execution exceeded timeout threshold of %ds" % threshold
|
||||||
|
print(stdout)
|
||||||
|
sys.exit(3)
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
options = parse_args()
|
||||||
|
|
||||||
|
# We need to ensure that we will only execute for a certain amount of
|
||||||
|
# seconds.
|
||||||
|
signal.signal(signal.SIGALRM, timeout_handler(options.timeout))
|
||||||
|
signal.alarm(options.timeout)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if options.version:
|
||||||
|
stdout = 'The version of this plugin is %s' % __VERSION__
|
||||||
|
return stdout, 0
|
||||||
|
|
||||||
|
info_json = get_json(options)
|
||||||
|
|
||||||
|
if options.list:
|
||||||
|
return show_list(info_json)
|
||||||
|
else:
|
||||||
|
stdout, returncode = run_check(info_json)
|
||||||
|
|
||||||
|
if options.performance and stdout.find("|") == -1:
|
||||||
|
stdout = "{0} | 'status'={1};1;2;;".format(stdout, returncode)
|
||||||
|
return stdout, returncode
|
||||||
|
except (HTTPError, URLError) as e:
|
||||||
|
if options.debug:
|
||||||
|
return 'The stack trace:\n' + traceback.format_exc(), 3
|
||||||
|
elif options.verbose:
|
||||||
|
return 'An error occurred:\n' + str(e.error_message), 3
|
||||||
|
else:
|
||||||
|
return e.error_message, 3
|
||||||
|
except Exception as e:
|
||||||
|
if options.debug:
|
||||||
|
return 'The stack trace:\n' + traceback.format_exc(), 3
|
||||||
|
elif options.verbose:
|
||||||
|
return 'An error occurred:\n' + str(e), 3
|
||||||
|
else:
|
||||||
|
return 'UNKNOWN: Error occurred while running the plugin. Use the verbose flag for more details.', 3
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
stdout, returncode = main()
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
print(unicode(stdout).encode('utf-8'))
|
||||||
|
else:
|
||||||
|
print(stdout.encode().decode('utf-8'))
|
||||||
|
sys.exit(returncode)
|
||||||
Executable
BIN
Binary file not shown.
@@ -0,0 +1,38 @@
|
|||||||
|
define command {
|
||||||
|
command_name AstropotaNotify-Host
|
||||||
|
command_line /home/nwc2026/NWC2026-AstropotaPOD/notify-by-ntfy.sh -t host -ha "$HOSTALIAS$" -h "$HOSTNAME$" -hs "$HOSTSTATE$" -ld "$LONGDATETIME$" -nt "$NOTIFICATIONTYPE$" -C "$CONTACTPAGER$"
|
||||||
|
}
|
||||||
|
define command {
|
||||||
|
command_name AstropotaNotify-Service
|
||||||
|
command_line /home/nwc2026/NWC2026-AstropotaPOD/notify-by-ntfy.sh -t service -ha "$HOSTALIAS$" -h "$HOSTNAME$" -hs "$HOSTSTATE$" -ld "$LONGDATETIME$" -nt "$NOTIFICATIONTYPE$" -s "$SERVICEDESC$" -ss "$SERVICESTATE$" -C "$CONTACTPAGER$"
|
||||||
|
|
||||||
|
}
|
||||||
|
define command {
|
||||||
|
command_name check_astro
|
||||||
|
command_line /home/nwc2026/NWC2026-AstropotaPOD/check_astro -w $ARG2$ -c $ARG3$ -m $ARG4$ -M $ARG5$ $ARG1$ $ARG8$
|
||||||
|
}
|
||||||
|
define command {
|
||||||
|
command_name check_dd_wrt
|
||||||
|
command_line /home/nwc2026/NWC2026-AstropotaPOD/check_dd_wrt -H $HOSTADDRESS$ -C $ARG1$ -i $ARG2$ -t $ARG3$ $ARG4$ $ARG8$
|
||||||
|
}
|
||||||
|
define command {
|
||||||
|
command_name check_iss
|
||||||
|
command_line /home/nwc2026/NWC2026-AstropotaPOD/check_iss
|
||||||
|
}
|
||||||
|
define command {
|
||||||
|
command_name check_launch
|
||||||
|
command_line /home/nwc2026/NWC2026-AstropotaPOD/check_launch $ARG1$ $ARG2$ $ARG8$
|
||||||
|
}
|
||||||
|
define command {
|
||||||
|
command_name check_kasa
|
||||||
|
command_line /home/nwc2026/NWC2026-AstropotaPOD/check_kasa -H $HOSTADDRESS$ -m $ARG1$ $ARG8$
|
||||||
|
}
|
||||||
|
|
||||||
|
define command {
|
||||||
|
command_name MQTT-PODalert
|
||||||
|
command_line /usr/bin/mosquitto_pub -h ssdnode-1.bitnetix.com -t "nagios/alerts" -m "$ARG2$" $ARG8$
|
||||||
|
}
|
||||||
|
define command {
|
||||||
|
command_name ping-other-host
|
||||||
|
command_line $USER1$/check_icmp -H $ARG1$ -w "$ARG2$" -c "$ARG3$" -p 5 $ARG8$
|
||||||
|
}
|
||||||
Executable
+30
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Set your exact location (Nagios Global Headquarters)
|
||||||
|
LAT="44.973N"
|
||||||
|
LON="93.155W"
|
||||||
|
|
||||||
|
# sunwait outputs values in clean 24-hour HH:MM format
|
||||||
|
# We use nautical because the program actually shows the end of nautical, which is the start of astronomical
|
||||||
|
DAWN=$(${HOME}/NWC2026-AstropotaPOD/sunwait list dawn nautical $LAT $LON)
|
||||||
|
DUSK=$(${HOME}/NWC2026-AstropotaPOD/sunwait list dusk nautical $LAT $LON)
|
||||||
|
|
||||||
|
# From here, it's Nagios API time
|
||||||
|
# Your Nagios XI API Details
|
||||||
|
XI_URL="http://192.168.123.16/nagiosxi/api/v1"
|
||||||
|
API_KEY="YOUR NAGIOS XI API KEY"
|
||||||
|
|
||||||
|
# Build the new time strings
|
||||||
|
# Day string becomes: "05:42-20:14"
|
||||||
|
# Night string becomes: "00:00-05:42,20:14-24:00"
|
||||||
|
DAY_RANGE="${DAWN}-${DUSK}"
|
||||||
|
NIGHT_RANGE="00:00-${DAWN},${DUSK}-24:00"
|
||||||
|
|
||||||
|
# Ensure our Timeperiods exist the way we want
|
||||||
|
curl -XPOST "${XI_URL}/config/timeperiod/AstropotaPOD-day?apikey=${API_KEY}&pretty=1" \
|
||||||
|
-d "timeperiod_name=AstropotaPOD-day&alias=Local+Daytime+recalculated+via+cron&sunday=${DAY_RANGE}&monday=${DAY_RANGE}&tuesday=${DAY_RANGE}&wednesday=${DAY_RANGE}&thursday=${DAY_RANGE}&friday=${DAY_RANGE}&saturday=${DAY_RANGE}"
|
||||||
|
curl -XPOST "${XI_URL}/config/timeperiod/AstropotaPOD-night?apikey=${API_KEY}&pretty=1" \
|
||||||
|
-d "timeperiod_name=AstropotaPOD-night&alias=Local+Nighttime+recalculated+via+cron&sunday=${NIGHT_RANGE}&monday=${NIGHT_RANGE}&tuesday=${NIGHT_RANGE}&wednesday=${NIGHT_RANGE}&thursday=${NIGHT_RANGE}&friday=${NIGHT_RANGE}&saturday=${NIGHT_RANGE}"
|
||||||
|
|
||||||
|
# Tell Nagios XI to safely apply the changes to the engine
|
||||||
|
curl -XPOST "${XI_URL}/system/applyconfig?apikey=${API_KEY}"
|
||||||
Executable
+89
@@ -0,0 +1,89 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# --- CONFIGURATION FROM ENVIRONMENT VARIABLES ---
|
||||||
|
CLIENT_ID="${FLAIR_CLIENT_ID}"
|
||||||
|
CLIENT_SECRET="${FLAIR_CLIENT_SECRET}"
|
||||||
|
|
||||||
|
if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ]; then
|
||||||
|
echo "Error: Missing environment variables." >&2
|
||||||
|
echo "Please set FLAIR_CLIENT_ID and FLAIR_CLIENT_SECRET before running this script." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
MQTT_BROKER="192.168.0.6"
|
||||||
|
MQTT_PORT="1883"
|
||||||
|
# Matching your requested structure
|
||||||
|
MQTT_TOPIC_BASE="AstropotaPOD/sensor"
|
||||||
|
|
||||||
|
# --- HELPER: GET OAUTH2 TOKEN ---
|
||||||
|
get_flair_token() {
|
||||||
|
local token_response
|
||||||
|
token_response=$(curl -s -X POST "https://api.flair.co/oauth2/token" \
|
||||||
|
-d "grant_type=client_credentials" \
|
||||||
|
-d "client_id=${CLIENT_ID}" \
|
||||||
|
-d "client_secret=${CLIENT_SECRET}")
|
||||||
|
|
||||||
|
echo "$token_response" | jq -r '.access_token // empty'
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- FETCH REFRESHED TOKEN ---
|
||||||
|
TOKEN=$(get_flair_token)
|
||||||
|
if [ -z "$TOKEN" ] || [ "$TOKEN" == "null" ]; then
|
||||||
|
echo "Failed to authenticate with Flair." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- FETCH AND PUBLISH TO MQTT ---
|
||||||
|
PAYLOAD=$(curl -s -X GET "https://api.flair.co/api/puck2s" \
|
||||||
|
-H "Authorization: Bearer ${TOKEN}" \
|
||||||
|
-H "Accept: application/json")
|
||||||
|
|
||||||
|
do_pub() {
|
||||||
|
mosquitto_pub -h "$MQTT_BROKER" -p "$MQTT_PORT" -t "${TARGET_TOPIC}/${1}" -m "$2" -r
|
||||||
|
}
|
||||||
|
|
||||||
|
while read -r puck_data; do
|
||||||
|
if [ -z "$puck_data" ]; then continue; fi
|
||||||
|
|
||||||
|
# Grab the human-readable name from your payload (e.g., "AstropotaPOD-e9af")
|
||||||
|
puck_name=$(echo "$puck_data" | jq -r '.attributes.name // empty')
|
||||||
|
|
||||||
|
# Fallback to ID just in case a device doesn't have a custom name assigned
|
||||||
|
if [ -z "$puck_name" ] || [ "$puck_name" == "null" ]; then
|
||||||
|
puck_name=$(echo "$puck_data" | jq -r '.id')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract metrics from payload attributes
|
||||||
|
temp_c=$(echo "$puck_data" | jq -r '.attributes."current-temperature-c" // empty')
|
||||||
|
humidity=$(echo "$puck_data" | jq -r '.attributes."current-humidity" // empty')
|
||||||
|
wifi_rssi=$(echo "$puck_data" | jq -r '.attributes."current-wifi-rssi" // empty')
|
||||||
|
power_source=$(echo "$puck_data" | jq -r '.attributes."power-source" // empty')
|
||||||
|
|
||||||
|
reporting_interval=$(echo "$puck_data" | jq -r '.attributes."reporting-interval-ds" // empty')
|
||||||
|
firmware=$(echo "$puck_data" | jq -r '.attributes."firmware-version" // empty')
|
||||||
|
beacon_ms=$(echo "$puck_data" | jq -r '.attributes."beacon-interval-ms" // empty')
|
||||||
|
|
||||||
|
voltage=$(echo "$puck_data" | jq -r '.attributes.voltage')
|
||||||
|
last_voltage=$(echo "$puck_data" | jq -r '.attributes."last-reported-voltage"')
|
||||||
|
if [ "$voltage" == "0" ] || [ "$voltage" == "0.0" ]; then
|
||||||
|
battery="$last_voltage"
|
||||||
|
else
|
||||||
|
battery="$voltage"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Dynamic target base matching your requested pattern: AstropotaPOD/sensor/AstropotaPOD-e9af/
|
||||||
|
TARGET_TOPIC="${MQTT_TOPIC_BASE}/${puck_name}"
|
||||||
|
|
||||||
|
# Blast sub-topics cleanly to Mosquitto
|
||||||
|
[ -n "$temp_c" ] && do_pub "temperature" "$temp_c"
|
||||||
|
[ -n "$humidity" ] && do_pub "humidity" "$humidity"
|
||||||
|
[ -n "$wifi_rssi" ] && do_pub "wifi_rssi" "$wifi_rssi"
|
||||||
|
[ -n "$power_source" ] && do_pub "power_source" "$power_source"
|
||||||
|
[ -n "$battery" ] && do_pub "battery_voltage" "$battery"
|
||||||
|
[ -n "$reporting_interval" ] && do_pub "reporting_interval" "$reporting_interval"
|
||||||
|
[ -n "$firmware" ] && do_pub "firmware_version" "$firmware"
|
||||||
|
[ -n "$beacon_ms" ] && do_pub "beacon_interval" "$beacon_ms"
|
||||||
|
|
||||||
|
# echo "Published to MQTT under: ${TARGET_TOPIC}/"
|
||||||
|
|
||||||
|
done < <(echo "$PAYLOAD" | jq -c '.data[]')
|
||||||
Executable
+91
@@ -0,0 +1,91 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This assumes a BLANK Raspberry Pi Zero W that we can install our AstropotaPOD stuff onto
|
||||||
|
|
||||||
|
do_toilet() {
|
||||||
|
echo "$*" | toilet -f standard -F metal
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. Update
|
||||||
|
echo "Updating repositories and ensuring we have the software we need..."
|
||||||
|
sudo apt -y update && sudo apt -y dist-upgrade && sudo apt -y autoremove
|
||||||
|
|
||||||
|
# 2. Make sure we have the software we need
|
||||||
|
echo "Installing toilet...I know. Bear with me."
|
||||||
|
sudo apt -y install toilet figlet
|
||||||
|
do_toilet "Pre-reqs"
|
||||||
|
sudo apt -y install git snmp jq bc mosquitto-clients
|
||||||
|
sudo sed -i 's/^AcceptEnv LANG LC_\*/#AcceptEnv LANG LC_\*/g' /etc/ssh/sshd_config
|
||||||
|
sudo systemctl restart ssh
|
||||||
|
do_toilet "Pips (via apt)"
|
||||||
|
sudo apt -y install python3-requests python3-paho-mqtt
|
||||||
|
|
||||||
|
# 3. Go get some stuff
|
||||||
|
do_toilet "Log dir"
|
||||||
|
cd
|
||||||
|
mkdir logs
|
||||||
|
#do_toilet "Getting NWC2026-AstropotaPOD"
|
||||||
|
#[ ! -d "NWC2026-AstropotaPOD" ] && git clone https://git.everwatch.global/eloyd/NWC2026-AstropotaPOD
|
||||||
|
|
||||||
|
# 4. Install Nagios Plugins
|
||||||
|
do_toilet "Nagios Plugins"
|
||||||
|
if [ ! -f "/usr/local/nagios/libexec/check_http" ]; then
|
||||||
|
cd
|
||||||
|
tar xfz NWC2026-AstropotaPOD/nagios-plugins-2.5.tar.gz
|
||||||
|
cd nagios-plugins-2.5
|
||||||
|
./configure
|
||||||
|
make && sudo make install
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Install NCPA
|
||||||
|
do_toilet "NCPA Client"
|
||||||
|
if [ ! -f "/usr/local/ncpa/etc/ncpa.cfg" ]; then
|
||||||
|
cd; cd NWC2026-AstropotaPOD
|
||||||
|
sudo apt -y install ./ncpa_3.3.1-1_armhf.deb
|
||||||
|
sudo sed -i -e "s/^community_string.*/community_string=AstropotamusWasHere/" /usr/local/ncpa/etc/ncpa.cfg
|
||||||
|
sudo systemctl restart ncpa
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. Install Nagios Mod Gearman
|
||||||
|
do_toilet "NMG Worker"
|
||||||
|
if [ ! -f "/usr/local/bin/nagios-mod-gearman-worker" ]; then
|
||||||
|
cd
|
||||||
|
git clone https://github.com/NagiosEnterprises/nagios-mod-gearman
|
||||||
|
cd nagios-mod-gearman
|
||||||
|
sudo apt -y install automake libncurses-dev gearman libgearman-dev help2man dctrl-tools libperl-dev g++ libltdl-dev pkgconf make dpkg-dev debhelper libssl-dev
|
||||||
|
sudo ./autogen.sh
|
||||||
|
sudo ./configure
|
||||||
|
sudo make install
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 7. Install check_ncpa clients
|
||||||
|
do_toilet "NCPA Clients"
|
||||||
|
cd; cd NWC2026-AstropotaPOD
|
||||||
|
sudo cp check_ncpa.py /usr/local/nagios/libexec
|
||||||
|
arch=$(uname -m)
|
||||||
|
file="check_ncpa.${arch}"
|
||||||
|
if [ -r "$file" ]; then
|
||||||
|
echo "Will install $file, since it's here..."
|
||||||
|
echo "==> Installing ${file} to /usr/local/nagios/libexec..."
|
||||||
|
sudo install -d /usr/local/nagios/libexec
|
||||||
|
sudo install -m 0755 ${file} /usr/local/nagios/libexec/check_ncpa
|
||||||
|
echo "==> Installation complete!"
|
||||||
|
echo "Installed to: /usr/local/nagios/libexec/check_ncpa"
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "###"
|
||||||
|
echo "###"
|
||||||
|
echo "###"
|
||||||
|
echo "### IMPORTANT:"
|
||||||
|
echo "###"
|
||||||
|
echo "### You must extract, compile, and install the check_ncpa_c.2.Mar-05-26.tgz beta code to /usr/local/nagios/libexec"
|
||||||
|
echo "###"
|
||||||
|
echo "### Failure to do so will result in service check failures that may be hard to diagnose."
|
||||||
|
echo "###"
|
||||||
|
echo "###"
|
||||||
|
echo "###"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Test
|
||||||
|
#/usr/local/bin/nagios-mod-gearman-worker --debug=2 --server=192.168.123.16 --key=AstropotamusWasHere --hosts --services --hostgroup=PODhosts --servicegroup=PODservices
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/* ==========================================================================
|
||||||
|
1. SIDEBAR CANVAS: Unified Deep Space Background
|
||||||
|
========================================================================== */
|
||||||
|
/* Targets the primary sidebar structural container and its underlying list */
|
||||||
|
html body.parent div.parentpage div#leftnav,
|
||||||
|
html body.parent div.parentpage .leftnav-container,
|
||||||
|
html body.parent div.parentpage ul.navside {
|
||||||
|
background-color: #02040a !important;
|
||||||
|
/* Diagonal 45-degree gradient matches the sweep pattern of the top header */
|
||||||
|
background: linear-gradient(45deg,
|
||||||
|
#02040a 0%,
|
||||||
|
#060c1f 40%,
|
||||||
|
#0b1736 100%
|
||||||
|
) !important;
|
||||||
|
border-right: 1px solid #1e3a8a !important; /* Low-contrast sapphire right boundary */
|
||||||
|
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
2. CATEGORY HEADERS: Structural Menu Sections
|
||||||
|
========================================================================== */
|
||||||
|
/* Style rules for static section headers (e.g., "Views", "Dashboards", "System") */
|
||||||
|
html body.parent div.parentpage .menu-section-title,
|
||||||
|
html body.parent div.parentpage ul.navside li.navside-head {
|
||||||
|
color: #3b82f6 !important; /* Prominent observatory focus blue */
|
||||||
|
font-weight: 600 !important;
|
||||||
|
text-transform: uppercase !important;
|
||||||
|
letter-spacing: 0.5px !important;
|
||||||
|
border-bottom: 1px solid rgba(37, 99, 235, 0.15) !important;
|
||||||
|
padding-bottom: 4px !important;
|
||||||
|
margin-top: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
3. NAVIGATION LINKS: Base Typography & Spacing Continuity
|
||||||
|
========================================================================== */
|
||||||
|
/* Maps target rules for left-hand menu lists while locking the native font layout */
|
||||||
|
html body.parent div.parentpage ul.navside li a,
|
||||||
|
html body.parent div.parentpage .leftnav-container ul li a {
|
||||||
|
display: block !important;
|
||||||
|
color: #cbd5e1 !important; /* Clean slate-white match with top navigation */
|
||||||
|
text-decoration: none !important;
|
||||||
|
/* Custom cubic-bezier ensures uniform physics curves with top header changes */
|
||||||
|
transition: transform 0.15s cubic-bezier(0.25, 1, 0.5, 1), color 0.15s ease, background-color 0.15s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
4. NAVIGATION INTERACTION: Responsive Left-Hand Menu Pop
|
||||||
|
========================================================================== */
|
||||||
|
/* Triggers a physical pop response when hovering over left nav elements */
|
||||||
|
html body.parent div.parentpage ul.navside li a:hover,
|
||||||
|
html body.parent div.parentpage .leftnav-container ul li a:hover {
|
||||||
|
color: #ffffff !important; /* Snaps text to clean white */
|
||||||
|
background-color: rgba(255, 255, 255, 0.04) !important; /* Low-opacity selection frame */
|
||||||
|
|
||||||
|
/* Horizontal push (+4px to the right) functions better in vertical menus than a vertical lift */
|
||||||
|
transform: translateX(4px) !important;
|
||||||
|
|
||||||
|
text-shadow: 0 0 8px rgba(255, 255, 255, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
5. MENU TREE UTILITIES: Expand/Collapse Arrow Tweaks
|
||||||
|
========================================================================== */
|
||||||
|
/* Adjusts standard navigation arrow structures to use the accent blue tint */
|
||||||
|
html body.parent div.parentpage ul.navside li i.fa-chevron-right,
|
||||||
|
html body.parent div.parentpage ul.navside li i.fa-chevron-down,
|
||||||
|
html body.parent div.parentpage .leftnav-container .fa-caret-right {
|
||||||
|
color: #1d4ed8 !important; /* Integrated sapphire blue arrow indicators */
|
||||||
|
transition: color 0.15s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html body.parent div.parentpage ul.navside li a:hover i {
|
||||||
|
color: #60a5fa !important; /* Highlights arrows on active parent item hover */
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Executable
+100
@@ -0,0 +1,100 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
notify_host="https://notify.astropotamus.com"
|
||||||
|
chanDefault="POD"
|
||||||
|
alertType=""
|
||||||
|
msgTitle=""
|
||||||
|
message=""
|
||||||
|
tag="exclamation"
|
||||||
|
priority="3"
|
||||||
|
state=""
|
||||||
|
testMode=""
|
||||||
|
|
||||||
|
### Define command for service
|
||||||
|
### define command {
|
||||||
|
### command_name notify-service-by-ntfy
|
||||||
|
### command_line $USER1$/notify-by-ntfy.sh -t service -ha "$HOSTALIAS$" -h "$HOSTNAME$" -hs "$HOSTSTATE$" -ld "$LONGDATETIME$" -nt "$NOTIFICATIONTYPE$" -s "$SERVICEDESC$" -ss "$SERVICESTATE$"
|
||||||
|
### }
|
||||||
|
|
||||||
|
while [ -n "$1" ]; do
|
||||||
|
case "$1" in
|
||||||
|
--test) testMode="true"; shift 1;;
|
||||||
|
-C|--channel) channel="$2"; shift 1;;
|
||||||
|
-t|--type) alertType="$2"; shift 2;;
|
||||||
|
-ha) NAGIOS_HOSTALIAS="$2"; shift 2;;
|
||||||
|
-h) NAGIOS_HOSTNAME="$2"; shift 2;;
|
||||||
|
-hs) NAGIOS_HOSTSTATE="$2"; shift 2;;
|
||||||
|
-ld) NAGIOS_LONGDATETIME="$2"; shift 2;;
|
||||||
|
-nt) NAGIOS_NOTIFICATIONTYPE="$2"; shift 2;;
|
||||||
|
-s) NAGIOS_SERVICEDESC="$2"; shift 2;;
|
||||||
|
-ss) NAGIOS_SERVICESTATE="$2"; shift 2;;
|
||||||
|
*) shift 1;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -z "$channel" ] && channel="$chanDefault"
|
||||||
|
|
||||||
|
case "$NAGIOS_NOTIFICATIONTYPE" in
|
||||||
|
PROBLEM) tag="bangbang";;
|
||||||
|
RECOVERY) tag="ok";;
|
||||||
|
ACKNOWLEDGEMENT) tag="ok_hand";;
|
||||||
|
FLAPPINGSTART) tag="hatched_chick";;
|
||||||
|
FLAPPINGSTOP) tag="hourglass_flowing_sand";;
|
||||||
|
DOWNTIMESTART) tag="sleeping_bed";;
|
||||||
|
DOWNTIMEEND) tag="sunny";;
|
||||||
|
*) tag="exclamation";;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$NAGIOS_HOSTSTATE" in
|
||||||
|
0) tag="$tag,+1";;
|
||||||
|
1) tag="$tag,-1";;
|
||||||
|
2) tag="$tag,-1";;
|
||||||
|
3) tag="$tag,question";;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$NAGIOS_SERVICESTATE" in
|
||||||
|
0) tag="$tag,+1";;
|
||||||
|
1) tag="$tag,-1";;
|
||||||
|
2) tag="$tag,warning";;
|
||||||
|
3) tag="$tag,question";;
|
||||||
|
esac
|
||||||
|
|
||||||
|
do_notify() {
|
||||||
|
[ -n "$1" ] && channel="${channel}-${1}"
|
||||||
|
curl -s \
|
||||||
|
-H "Title: ${msgTitle}" \
|
||||||
|
-H "Tags: ${tag}" \
|
||||||
|
-H "Priority: ${priority}" \
|
||||||
|
-d "${message}" \
|
||||||
|
${notify_host}/${channel}
|
||||||
|
}
|
||||||
|
|
||||||
|
do_host() {
|
||||||
|
msgTitle="HOST ${NAGIOS_NOTIFICATIONTYPE}"
|
||||||
|
message="$NAGIOS_NOTIFICATIONTYPE $NAGIOS_HOSTNAME is $NAGIOS_HOSTSTATE $NAGIOS_LONGDATETIME"
|
||||||
|
do_notify "Hosts"
|
||||||
|
}
|
||||||
|
|
||||||
|
do_service() {
|
||||||
|
msgTitle="SERVICE ${NAGIOS_NOTIFICATIONTYPE}"
|
||||||
|
message="$NAGIOS_NOTIFICATIONTYPE $NAGIOS_SERVICEDESC @ $NAGIOS_HOSTALIAS is $NAGIOS_SERVICESTATE $NAGIOS_LONGDATETIME"
|
||||||
|
do_notify "Services"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "$testMode" ]; then
|
||||||
|
NAGIOS_NOTIFICATIONTYPE="Test"
|
||||||
|
NAGIOS_HOSTNAME="FakeHost"
|
||||||
|
NAGIOS_HOSTSTATE="OK"
|
||||||
|
NAGIOS_SERVICEDESC="FakeService"
|
||||||
|
NAGIOS_HOSTALIAS="FakeHostAlias"
|
||||||
|
NAGIOS_SERVICESTATE="OK"
|
||||||
|
NAGIOS_LONGDATETIME="Today"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$alertType" in
|
||||||
|
h|host|host) do_host;;
|
||||||
|
s|svc|service) do_service;;
|
||||||
|
*) shift 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
action="start"
|
||||||
|
xihost="192.168.123.16"
|
||||||
|
key="NRDP Key for Nagios Server"
|
||||||
|
logFile="${HOME}/logs/gearman.log"
|
||||||
|
|
||||||
|
start_options="--server=${xihost} --key=${key}"
|
||||||
|
start_options+=" --min-worker=2"
|
||||||
|
start_options+=" --max-worker=5"
|
||||||
|
start_options+=" --spawn-rate=1"
|
||||||
|
start_options+=" --load_limit1=1"
|
||||||
|
start_options+=" --load_limit5=2"
|
||||||
|
start_options+=" --load_limit15=3"
|
||||||
|
start_options+=" --idle-timeout=600"
|
||||||
|
start_options+=" --hosts --services --hostgroup=PODhosts --servicegroup=PODservices --logmode=file --logfile=${logFile} --debug=1"
|
||||||
|
|
||||||
|
while [ -n "$1" ]; do
|
||||||
|
case "$1" in
|
||||||
|
--start) action="start"; shift 1;;
|
||||||
|
--stop) action="stop"; shift 1;;
|
||||||
|
--restart) action="restart"; shift 1;;
|
||||||
|
*) shift 1;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
do_stop() {
|
||||||
|
echo "Stopping nagios-mod-gearman-worker..."
|
||||||
|
killall /usr/local/bin/nagios-mod-gearman-worker
|
||||||
|
echo "Done."
|
||||||
|
}
|
||||||
|
|
||||||
|
do_start() {
|
||||||
|
echo "Starting nagios-mod-gearman-worker..."
|
||||||
|
/usr/local/bin/nagios-mod-gearman-worker -d $start_options
|
||||||
|
echo "Done."
|
||||||
|
}
|
||||||
|
|
||||||
|
do_restart() {
|
||||||
|
do_stop
|
||||||
|
sleep 5
|
||||||
|
do_start
|
||||||
|
}
|
||||||
|
|
||||||
|
do_${action}
|
||||||
Submodule
+1
Submodule sunwait added at 151d8340a7
Executable
+104
@@ -0,0 +1,104 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [ -z "$INFLUX_TOKEN" ]; then
|
||||||
|
echo "WARNING: You need to set an INFLUX_TOKEN environment variable or else it won't actually work."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This is hardcoded here because it's easier. Find it on your own. :-)
|
||||||
|
PPBAkey="00e086b2-85b9-4562-a6bd-6d5a68ad2c26"
|
||||||
|
fluxHost="192.168.0.6"
|
||||||
|
fluxPort="8086"
|
||||||
|
ppbaHost="192.168.0.151"
|
||||||
|
ppbaPort="32000"
|
||||||
|
|
||||||
|
do_help() {
|
||||||
|
echo "Help coming soon. I promise. Do not panic!"
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
do_preface() {
|
||||||
|
cat << PREFACE_EOF
|
||||||
|
[global_tags]
|
||||||
|
|
||||||
|
[agent]
|
||||||
|
interval = "10s"
|
||||||
|
round_interval = true
|
||||||
|
metric_batch_size = 1000
|
||||||
|
metric_buffer_limit = 10000
|
||||||
|
collection_jitter = "0s"
|
||||||
|
flush_interval = "10s"
|
||||||
|
flush_jitter = "0s"
|
||||||
|
precision = "0s"
|
||||||
|
|
||||||
|
[[outputs.influxdb_v2]]
|
||||||
|
urls = ["http://${fluxHost}:${fluxPort}"]
|
||||||
|
token = "$INFLUX_TOKEN"
|
||||||
|
organization = "Astropotamus"
|
||||||
|
bucket = "AstropotaPOD"
|
||||||
|
PREFACE_EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
do_postface() {
|
||||||
|
cat << POSTFACE_EOF
|
||||||
|
POSTFACE_EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
do_ppba() {
|
||||||
|
do_preface
|
||||||
|
cat << PPBA_EOF
|
||||||
|
[[inputs.http]]
|
||||||
|
urls = [
|
||||||
|
"http://${ppbaHost}:${ppbaPort}/Driver/PPBAdvance/Report?DriverUniqueKey=${PPBAkey}"
|
||||||
|
]
|
||||||
|
name_override = "PPBA"
|
||||||
|
data_format = "json"
|
||||||
|
tagexclude = ["url", "host"]
|
||||||
|
json_query="data.message"
|
||||||
|
json_string_fields=["dewHubStatus_hub_?_current_isOverCurrent","powerHubStatus_state","powerVariablePortStatus_state"]
|
||||||
|
PPBA_EOF
|
||||||
|
do_postface
|
||||||
|
}
|
||||||
|
|
||||||
|
do_weather() {
|
||||||
|
do_preface
|
||||||
|
cat << WEATHER_EOF
|
||||||
|
[[inputs.http]]
|
||||||
|
urls=["https://api.openweathermap.org/data/2.5/weather?id=5126015&units=metric&appid=043d58f75a4ffe7cf5d414ead183cb7f"]
|
||||||
|
data_format = "json"
|
||||||
|
tag_keys = [ "coord_lon", "coord_lat", "sys_country", "id", "name" ]
|
||||||
|
json_string_fields = [ "weather_0_main", "weather_0_description" ]
|
||||||
|
json_time_key = "dt"
|
||||||
|
json_time_format = "unix"
|
||||||
|
name_override = "openweathermap"
|
||||||
|
WEATHER_EOF
|
||||||
|
do_postface
|
||||||
|
}
|
||||||
|
|
||||||
|
do_local() {
|
||||||
|
do_preface
|
||||||
|
cat << LOCAL_EOF
|
||||||
|
[[inputs.cpu]]
|
||||||
|
percpu = true
|
||||||
|
totalcpu = true
|
||||||
|
collect_cpu_time = false
|
||||||
|
report_active = false
|
||||||
|
core_tags = false
|
||||||
|
[[inputs.mem]]
|
||||||
|
[[inputs.system]]
|
||||||
|
LOCAL_EOF
|
||||||
|
do_postface
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFile=$(mktemp)
|
||||||
|
while [ -n "$1" ]; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help) do_help;;
|
||||||
|
--ppba) do_ppba > $tmpFile; shift 1;;
|
||||||
|
--weather) do_weather > $tmpFile; shift 1;;
|
||||||
|
--iss) rm $tmpFile; echo "Not yet."; exit;;
|
||||||
|
--local) do_local > $tmpFile; shift 1;;
|
||||||
|
*) shift 1;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
telegraf --config $tmpFile --once --quiet
|
||||||
|
rm $tmpFile
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/* ==========================================
|
||||||
|
1. THE CANVAS: Elongated Diagonal Sky Wash
|
||||||
|
========================================== */
|
||||||
|
html body.parent div.parentpage div#header.parenthead {
|
||||||
|
width: 100% !important;
|
||||||
|
background-color: #02040a !important;
|
||||||
|
|
||||||
|
/* 45-degree angle stretches the colors sideways across your 1706px screen */
|
||||||
|
background: linear-gradient(45deg,
|
||||||
|
#02040a 0%,
|
||||||
|
#0b1736 35%,
|
||||||
|
#1e3a8a 50%,
|
||||||
|
#0b1736 65%,
|
||||||
|
#02040a 100%
|
||||||
|
) !important;
|
||||||
|
|
||||||
|
border-bottom: 2px solid #2563eb !important; /* Sharp blue accent baseline */
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.7) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
2. TEXT INTERACTION: Increased Lift
|
||||||
|
========================================== */
|
||||||
|
html body.parent div.parentpage div#header.parenthead .mainmenu div a,
|
||||||
|
html body.parent div.parentpage div#header.parenthead #config-menulink span a {
|
||||||
|
display: inline-block !important;
|
||||||
|
/* Uses a spring-like ease out instead of a flat linear animation */
|
||||||
|
transition: transform 0.15s cubic-bezier(0.25, 1, 0.5, 1), color 0.15s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html body.parent div.parentpage div#header.parenthead .mainmenu div a:hover,
|
||||||
|
html body.parent div.parentpage div#header.parenthead #config-menulink span a:hover {
|
||||||
|
color: #ffffff !important;
|
||||||
|
transform: translateY(-3px) !important; /* Doubled the lift distance for a distinct reaction */
|
||||||
|
text-shadow: 0 0 10px rgba(255, 255, 255, 0.4); /* Subtle text clarity boost */
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
3. BUTTON INTERACTION: Solid Scale Pop
|
||||||
|
========================================== */
|
||||||
|
html body.parent div.parentpage div#header.parenthead button.btn,
|
||||||
|
html body.parent div.parentpage div#header.parenthead .btn-primary {
|
||||||
|
transition: transform 0.15s cubic-bezier(0.25, 1, 0.5, 1), box-shadow 0.15s ease, filter 0.15s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html body.parent div.parentpage div#header.parenthead button.btn:hover,
|
||||||
|
html body.parent div.parentpage div#header.parenthead .btn-primary:hover {
|
||||||
|
transform: translateY(-3px) scale(1.04) !important; /* Increased lift and expansion scale */
|
||||||
|
box-shadow: 0 0 15px rgba(37, 99, 235, 0.6) !important; /* Clear highlight glow underneath */
|
||||||
|
filter: brightness(1.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================
|
||||||
|
4. UTILITY ICONS: Quick Response
|
||||||
|
========================================== */
|
||||||
|
html body.parent div.parentpage div#header.parenthead .header-right a i,
|
||||||
|
html body.parent div.parentpage div#header.parenthead #profile-button i,
|
||||||
|
html body.parent div.parentpage div#header.parenthead #help-button i {
|
||||||
|
display: inline-block !important;
|
||||||
|
transition: transform 0.12s ease, color 0.12s ease !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
html body.parent div.parentpage div#header.parenthead .header-right a i:hover,
|
||||||
|
html body.parent div.parentpage div#header.parenthead #profile-button:hover i,
|
||||||
|
html body.parent div.parentpage div#header.parenthead #help-button:hover i {
|
||||||
|
color: #60a5fa !important;
|
||||||
|
transform: scale(1.2) translateY(-1px) !important; /* Visually pops forward and up slightly */
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user