first commit

This commit is contained in:
Kyle Hagerty 2021-09-14 22:15:36 -05:00
commit ef1eb43202
3 changed files with 538 additions and 0 deletions

325
NET-SNMP-EXTEND-MIB.txt Normal file
View File

@ -0,0 +1,325 @@
NET-SNMP-EXTEND-MIB DEFINITIONS ::= BEGIN
--
-- Defines a framework for scripted extensions
--
IMPORTS
nsExtensions FROM NET-SNMP-AGENT-MIB
OBJECT-TYPE, NOTIFICATION-TYPE, MODULE-IDENTITY, Integer32
FROM SNMPv2-SMI
OBJECT-GROUP, NOTIFICATION-GROUP
FROM SNMPv2-CONF
DisplayString, RowStatus, StorageType FROM SNMPv2-TC;
netSnmpExtendMIB MODULE-IDENTITY
LAST-UPDATED "201003170000Z"
ORGANIZATION "www.net-snmp.org"
CONTACT-INFO
"postal: Wes Hardaker
P.O. Box 382
Davis CA 95617
email: net-snmp-coders@lists.sourceforge.net"
DESCRIPTION
"Defines a framework for scripted extensions for the Net-SNMP agent."
REVISION "201003170000Z"
DESCRIPTION
"Fixed inconsistencies in the definition of nsExtendConfigTable."
REVISION "200405080000Z"
DESCRIPTION
"First revision."
::= { nsExtensions 1 }
nsExtendObjects OBJECT IDENTIFIER ::= { nsExtensions 2}
nsExtendGroups OBJECT IDENTIFIER ::= { nsExtensions 3}
nsExtendNumEntries OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"The number of rows in the nsExtendConfigTable"
::= { nsExtendObjects 1 }
nsExtendConfigTable OBJECT-TYPE
SYNTAX SEQUENCE OF NsExtendConfigEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"A table of scripted extensions - configuration and (basic) output."
::= { nsExtendObjects 2 }
nsExtendConfigEntry OBJECT-TYPE
SYNTAX NsExtendConfigEntry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"A conceptual row within the extension table."
INDEX { nsExtendToken }
::= { nsExtendConfigTable 1 }
NsExtendConfigEntry ::= SEQUENCE {
nsExtendToken DisplayString,
nsExtendCommand DisplayString,
nsExtendArgs DisplayString,
nsExtendInput DisplayString,
nsExtendCacheTime INTEGER,
nsExtendExecType INTEGER,
nsExtendRunType INTEGER,
nsExtendStorage StorageType,
nsExtendStatus RowStatus
}
--
-- The configuration of an extension command
--
nsExtendToken OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"An arbitrary token to identify this extension entry"
::= { nsExtendConfigEntry 1 }
nsExtendCommand OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-create
STATUS current
DESCRIPTION
"The full path of the command binary (or script) to run"
::= { nsExtendConfigEntry 2 }
nsExtendArgs OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-create
STATUS current
DESCRIPTION
"Any command-line arguments for the command"
DEFVAL { ''H } -- the empty string
::= { nsExtendConfigEntry 3 }
nsExtendInput OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-create
STATUS current
DESCRIPTION
"The standard input for the command"
DEFVAL { ''H } -- the empty string
::= { nsExtendConfigEntry 4 }
nsExtendCacheTime OBJECT-TYPE
SYNTAX INTEGER
MAX-ACCESS read-create
STATUS current
DESCRIPTION
"The length of time for which the output of
this command will be cached. During this time,
retrieving the output-related values will not
reinvoke the command.
A value of -1 indicates that the output results
should not be cached at all, and retrieving each
individual output-related value will invoke the
command afresh."
DEFVAL { 5 }
::= { nsExtendConfigEntry 5 }
nsExtendExecType OBJECT-TYPE
SYNTAX INTEGER
{ exec (1), -- 'fork-and-exec'
shell (2) -- run via a sub-shell
}
MAX-ACCESS read-create
STATUS current
DESCRIPTION
"The mechanism used to invoke the command."
DEFVAL { exec }
::= { nsExtendConfigEntry 6 }
nsExtendRunType OBJECT-TYPE
SYNTAX INTEGER
{ run-on-read (1),
run-on-set (2),
run-command (3)
}
MAX-ACCESS read-create
STATUS current
DESCRIPTION
"Used to implement 'push-button' command invocation.
The command for a 'run-on-read' entry will be invoked
whenever one of the corresponding output-related
instances is requested (and assuming the cached value
is not still current).
The command for a 'run-on-set' entry will only be invoked
on receipt of a SET assignment for this object with the
value 'run-command'.
Reading an instance of this object will always return either
'run-on-read' or 'run-on-set'.
"
DEFVAL { run-on-read }
::= { nsExtendConfigEntry 7 }
--
-- Standard table-manipulation objects
--
nsExtendStorage OBJECT-TYPE
SYNTAX StorageType
MAX-ACCESS read-create
STATUS current
DESCRIPTION
"The storage type for this conceptual row."
DEFVAL { volatile }
::= { nsExtendConfigEntry 20 }
nsExtendStatus OBJECT-TYPE
SYNTAX RowStatus
MAX-ACCESS read-create
STATUS current
DESCRIPTION
"Used to create new rows in the table, in the standard manner.
Note that is valid for an instance to be left with the value
notInService(2) indefinitely - i.e. the meaning of 'abnormally
long' (see RFC 2579, RowStatus) for this table is infinite."
::= { nsExtendConfigEntry 21 }
--
-- The results of running the extension command
--
nsExtendOutput1Table OBJECT-TYPE
SYNTAX SEQUENCE OF NsExtendOutput1Entry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"A table of scripted extensions - configuration and (basic) output."
::= { nsExtendObjects 3 }
nsExtendOutput1Entry OBJECT-TYPE
SYNTAX NsExtendOutput1Entry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"A conceptual row within the extension table."
AUGMENTS { nsExtendConfigEntry }
::= { nsExtendOutput1Table 1 }
NsExtendOutput1Entry ::= SEQUENCE {
nsExtendOutput1Line DisplayString,
nsExtendOutputFull DisplayString,
nsExtendOutNumLines Integer32,
nsExtendResult Integer32
}
nsExtendOutput1Line OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"The first line of output from the command"
::= { nsExtendOutput1Entry 1 }
nsExtendOutputFull OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"The full output from the command, as a single string"
::= { nsExtendOutput1Entry 2 }
nsExtendOutNumLines OBJECT-TYPE
SYNTAX Integer32
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"The number of lines of output (and hence
the number of rows in nsExtendOutputTable
relating to this particular entry)."
::= { nsExtendOutput1Entry 3 }
nsExtendResult OBJECT-TYPE
SYNTAX Integer32
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"The return value of the command."
::= { nsExtendOutput1Entry 4 }
--
-- The line-based output table
--
nsExtendOutput2Table OBJECT-TYPE
SYNTAX SEQUENCE OF NsExtendOutput2Entry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"A table of (line-based) output from scripted extensions."
::= { nsExtendObjects 4 }
nsExtendOutput2Entry OBJECT-TYPE
SYNTAX NsExtendOutput2Entry
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"A conceptual row within the line-based output table."
INDEX { nsExtendToken, nsExtendLineIndex }
::= { nsExtendOutput2Table 1 }
NsExtendOutput2Entry ::= SEQUENCE {
nsExtendLineIndex INTEGER,
nsExtendOutLine DisplayString
}
nsExtendLineIndex OBJECT-TYPE
SYNTAX INTEGER(1..1024)
MAX-ACCESS not-accessible
STATUS current
DESCRIPTION
"The index of this line of output.
For a given nsExtendToken, this will run from
1 to the corresponding value of nsExtendNumLines."
::= { nsExtendOutput2Entry 1 }
nsExtendOutLine OBJECT-TYPE
SYNTAX DisplayString
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"A single line of output from the extension command."
::= { nsExtendOutput2Entry 2 }
--
-- Conformance-related definitions
--
nsExtendConfigGroup OBJECT-GROUP
OBJECTS {
nsExtendCommand, nsExtendArgs, nsExtendInput,
nsExtendCacheTime, nsExtendExecType, nsExtendRunType,
nsExtendStorage, nsExtendStatus, nsExtendNumEntries
}
STATUS current
DESCRIPTION
"Objects relating to the configuration of extension commands."
::= { nsExtendGroups 1 }
nsExtendOutputGroup OBJECT-GROUP
OBJECTS {
nsExtendOutNumLines, nsExtendResult,
nsExtendOutLine, nsExtendOutput1Line, nsExtendOutputFull
}
STATUS current
DESCRIPTION
"Objects relating to the output of extension commands."
::= { nsExtendGroups 2 }
END

197
README.md Normal file
View File

@ -0,0 +1,197 @@
# Install + Configure NUT & NET-SNMP
## Install and Configure NUT
If you're not using the same UPS, check the NUT hardware compatibility list to see if your equipment is supported by the drivers included with NUT. First, run the command below to install NUT
```
sudo apt install nut
```
Next, let's configure NUT:
```
$ sudo vi /etc/ups/ups.conf
```
Append these lines:
```
[name_of_your_ups]
driver = usbhid-ups
port = auto
desc = "Description of your UPS"
```
Change [name_of_your_ups] to any name you'd like. This value becomes the query name for polling the device.
Let's restart the UPS driver:
```
sudo systemctl reload nut-driver.service
```
If you receive any error ensure you have sudo privileges—and try restarting the machine if the problem persists. We will also set NUT's mode to standalone.
sudo vi /etc/ups/nut.conf
```
MODE=standalone
```
From their documentation:
- standalone: This mode address a local only configuration, with 1 UPS protecting the local system. This implies to start the 3 NUT layer (driver, upsd and upsmon) and the matching configuration files. This mode can also address UPS redundancy.
## Configure NUT server and monitor
Once the driver is up and running, we need to set up the user for accessing the NUT daemon. Append the following under upsd.users.
sudo vi /etc/ups/upsd.users
```
[upsmon]
password = <secretpass>
upsmon master
```
Change <secretpass> to a secure password of your choosing.
Lastly, we'll edit the NUT monitor configuration with the name of our device, and the access information for the newly created user. Append the following under upsmon.conf
sudo vi /etc/ups/upsmon.conf
```
MONITOR cyberpower@localhost 1 local_mon <secretpass> master
```
Of course, change cyberpower to the name you gave your device, and <secretpass> to the password you specified in /etc/ups/upsd.users
Enable services
To ensure monitoring ability after planned and unplanned reboots, we need to enable and start the NUT system services.
```
sudo systemctl enable nut-monitor.service
sudo systemctl enable nut-server.service
sudo systemctl start nut-server.service
sudo systemctl start nut-monitor.service
```
If you encounter any errors starting the services, restarting may resolve these. Often, running upsdrvctl start as we did earlier hijacks USB communication with the UPS and causes an error when trying to start NUT services.
Test UPS querying!
Run (replacing cyberpower with your device name):
```
upsc name_of_your_ups
```
And, if all configurations are happy you will see some data returned:
```
battery.charge: 100
battery.charge.low: 10
battery.charge.warning: 20
battery.mfr.date: CPS
battery.runtime: 2790
battery.runtime.low: 300
battery.type: PbAcid
battery.voltage: 16.0
battery.voltage.nominal: 24
device.mfr: CPS
device.model: CP850PFCLCD
device.serial: 000000000000
device.type: ups
driver.name: usbhid-ups
driver.parameter.pollfreq: 30
driver.parameter.pollinterval: 2
driver.parameter.port: auto
driver.version: 2.7.2
driver.version.data: CyberPower HID 0.3
driver.version.internal: 0.38
input.transfer.high: 139
input.transfer.low: 88
...
```
## Install Net-SNMP
```
sudo yum install net-snmp
```
Create the script for MIB formatting of the query data: Note: this script formats MIBs specifically for LibreNMS.
```
sudo vi /etc/snmp/ups-nut.sh
```
Past the following, making sure to change UPS_NAME to your device name:
```
#!/usr/bin/env bash
UPS_NAME='cyberpower'
PATH=$PATH:/usr/bin:/bin
TMP=$(upsc $UPS_NAME 2>/dev/null)
for value in "battery\.charge: [0-9.]+" "battery\.(runtime\.)?low: [0-9]+" "battery\.runtime: [0-9]+" "battery\.voltage: [0-9.]+" "battery\.voltage\.nominal: [0-9]+" "input\.voltage\.nominal: [0-9.]+" "input\.voltage: [0-9.]+" "ups\.load: [0-9.]+"
do
OUT=$(echo $TMP | grep -Eo "$value" | awk '{print $2}' | LANG=C sort | head -n 1)
if [ -n "$OUT" ]; then
echo $OUT
else
echo "Unknown"
fi
done
```
Make the script executable:
```
sudo chmod +x /etc/snmp/ups-nut.sh
```
Now let's extend this script to the SNMP daemon:
```
sudo vi /etc/snmp/snmpd.conf
```
Append the following and change <community> to your community name:
```
rocommunity <community>
extend ups-nut /etc/snmp/ups-nut.sh
```
Let's enable and start the SNMP daemon:
```
sudo systemctl enable snmpd
sudo systemctl start snmpd
```
And finally, make a test query from a another host that can reach the server:
```
$ snmpwalk -v2c -c <community> <host> 'NET-SNMP-EXTEND-MIB::nsExtendOutLine'
You should see results similar to this:
NET-SNMP-EXTEND-MIB::nsExtendOutLine."ups-nut".1 = STRING: 100
NET-SNMP-EXTEND-MIB::nsExtendOutLine."ups-nut".2 = STRING: 300
NET-SNMP-EXTEND-MIB::nsExtendOutLine."ups-nut".3 = STRING: 2790
NET-SNMP-EXTEND-MIB::nsExtendOutLine."ups-nut".4 = STRING: 16.0
NET-SNMP-EXTEND-MIB::nsExtendOutLine."ups-nut".5 = STRING: 24
NET-SNMP-EXTEND-MIB::nsExtendOutLine."ups-nut".6 = STRING: 120
NET-SNMP-EXTEND-MIB::nsExtendOutLine."ups-nut".7 = STRING: 125.0
NET-SNMP-EXTEND-MIB::nsExtendOutLine."ups-nut".8 = STRING: 14
```
Your host can now receive an SNMP query, poll your device with NUT, and respond with MIBs formatted for display/graphing on LibreNMS, Observium, or other SNMP-based monitoring platforms.
You could also skip SNMP entirely and use a bash or python script to automate the retrieval of the UPS values via the upsc command.
I hope this helps anyone looking to quantify and monitor the health of their UPS from afar.

16
ups-nut.sh Normal file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
UPS_NAME='tripplite'
PATH=$PATH:/usr/bin:/bin
TMP=$(upsc $UPS_NAME 2>/dev/null)
for value in "battery\.charge: [0-9.]+" "battery\.(runtime\.)?low: [0-9]+" "battery\.runtime: [0-9]+" "battery\.voltage: [0-9.]+" "battery\.voltage\.nominal: [0-9]+" "input\.voltage\.nominal: [0-9.]+" "input\.voltage: [0-9.]+" "ups\.load: [0-9.]+"
do
OUT=$(echo $TMP | grep -Eo "$value" | awk '{print $2}' | LANG=C sort | head -n 1)
if [ -n "$OUT" ]; then
echo $OUT
else
echo "Unknown"
fi
done