diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..18730e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +shutdown-server/shutdown-server.conf diff --git a/Dockerfile b/Dockerfile index d3dd796..1f229d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,65 +1,49 @@ -FROM alpine:3.12 +FROM ubuntu:20.04 -LABEL maintainer="docker@upshift.fr" -ENV NUT_VERSION 2.7.4 +LABEL maintainer="e1z0" +ENV NUT_VERSION 2.7.4 ENV UPS_NAME="ups" ENV UPS_DESC="UPS" ENV UPS_DRIVER="usbhid-ups" ENV UPS_PORT="auto" - ENV API_PASSWORD="" ENV ADMIN_PASSWORD="" + + ENV SHUTDOWN_CMD="echo 'System shutdown not configured!'" -RUN set -ex; \ - # run dependencies - apk add --no-cache \ - openssh-client \ - libusb-compat \ - ; \ - # build dependencies - apk add --no-cache --virtual .build-deps \ - libusb-compat-dev \ - build-base \ - ; \ - # download and extract - cd /tmp; \ - wget http://www.networkupstools.org/source/2.7/nut-$NUT_VERSION.tar.gz; \ - tar xfz nut-$NUT_VERSION.tar.gz; \ - cd nut-$NUT_VERSION \ - ; \ - # build - ./configure \ - --prefix=/usr \ - --sysconfdir=/etc/nut \ - --disable-dependency-tracking \ - --enable-strip \ - --disable-static \ - --with-all=no \ - --with-usb=yes \ - --datadir=/usr/share/nut \ - --with-drvpath=/usr/share/nut \ - --with-statepath=/var/run/nut \ - --with-user=nut \ - --with-group=nut \ - ; \ - # install - make install \ - ; \ - # create nut user - adduser -D -h /var/run/nut nut; \ - chgrp -R nut /etc/nut; \ - chmod -R o-rwx /etc/nut; \ - install -d -m 750 -o nut -g nut /var/run/nut \ - ; \ - # cleanup - rm -rf /tmp/nut-$NUT_VERSION.tar.gz /tmp/nut-$NUT_VERSION; \ - apk del .build-deps +RUN apt-get update && apt-get install -y build-essential wget libusb-dev curl && \ + # download and extract + cd /tmp && \ + wget http://www.networkupstools.org/source/2.7/nut-$NUT_VERSION.tar.gz && \ + tar xfz nut-$NUT_VERSION.tar.gz && \ + cd nut-$NUT_VERSION && \ + ./configure \ + --prefix=/usr \ + --sysconfdir=/etc/nut \ + --disable-dependency-tracking \ + --enable-strip \ + --disable-static \ + --with-all=no \ + --with-usb=yes \ + --datadir=/usr/share/nut \ + --with-drvpath=/usr/share/nut \ + --with-statepath=/var/run/nut \ + --with-user=root \ + --with-group=root \ + && \ + make install && \ + # create nut user + install -d -m 750 -o root -g root /var/run/nut && \ + rm -rf /tmp/nut-$NUT_VERSION.tar.gz /tmp/nut-$NUT_VERSION COPY src/docker-entrypoint /usr/local/bin/ +COPY src/flux_mon /usr/local/bin/ +COPY shutdown-client/shutdown-client /usr/local/bin/ + ENTRYPOINT ["docker-entrypoint"] WORKDIR /var/run/nut diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e48c344 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +all: build + +build: + docker build -t nut-upsd:aarch64 -f Dockerfile --no-cache=true . +upload: + docker tag nut-upsd:aarch64 nulldevil/nut-upsd:aarch64 + docker push nulldevil/nut-upsd:aarch64 +up: + docker compose up diff --git a/README.md b/README.md index 824cec9..1e34508 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Network UPS Tools server -Docker image for Network UPS Tools server. +Docker image for Network UPS Tools server. Docker image have been remade using ubuntu 20.04 as the base, it links systemd inside container and enables shutdown functionality. Also there are some reporting to InfluxDB added. See docker compose for configuration. + +**NOTE! New shutdown function have been introduced, look at [shutdown-server](../master/shutdown-server/README.md)** ## Usage @@ -9,13 +11,7 @@ This image provides a complete UPS monitoring service (USB driver only). Start the container: ```console -# docker run \ - --name nut-upsd \ - --detach \ - --publish 3493:3493 \ - --device /dev/bus/usb/xxx/yyy \ - --env SHUTDOWN_CMD="my-shutdown-command-from-container" \ - upshift/nut-upsd +docker-compose up ``` ## Auto configuration via environment variables @@ -42,25 +38,74 @@ This specifies which program will be monitoring this UPS. ### UPS_PORT -*Default vaue*: `auto` +*Default value*: `auto` This is the serial port where the UPS is connected. ### API_USER -*Default vaue*: `upsmon` +*Default value*: `upsmon` This is the username used for communication between upsmon and upsd processes. ### API_PASSWORD -*Default vaue*: `secret` +*Default value*: `secret` This is the password for the upsmon user. ### SHUTDOWN_CMD -*Default vaue*: `echo 'System shutdown not configured!'` +*Default value*: `echo 'System shutdown not configured!'` + +You should use: `/usr/local/bin/shutdown-client -action shutdown -api_key abcd -hosts node1,node2,node3` This is the command upsmon will run when the system needs to be brought down. The command will be run from inside the container. +### ENABLE_INFLUX + +*Default value*: `false` + +This will enable reporting to InfluxDB. Only v1 of http api is supported at the time of writing. + +### INFLUX_DBUSER + +*Default value*: `none` + +InfluxDB Database user + +### INFLUX_DBPASS + +*Default value*: `none` + +InfluxDB Database password + +### INFLUX_HOST + +*Default value*: `none` + +InfluxDB Host + +### INFLUX_PORT + +*Default value*: `none` + +InfluxDB Port + +### INFLUX_DBNAME + +*Default value*: `none` + +InfluxDB Database name + +### INFLUX_DISPLAY_HOST + +*Default value*: `none` + +There you can specifiy your server hostname or any other keyword, it defines the tag for InfluxDB measurement table. + +### INFLUX_UPDATE_INTERVAL + +*Default value*: `none` + +How frequently write statistics to the InfluxDB, I'm advice you to use 10 (seconds) or more here. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f38f755 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,27 @@ +version: '2.4' +services: + nut-upsd: + container_name: nut-upsd + hostname: nut-upsd + restart: unless-stopped + image: nulldevil/nut-upsd:latest + environment: + API_PASSWORD: "password" + SHUTDOWN_CMD: "/usr/local/bin/shutdown-client -action shutdown -api_key abc -hosts node1,node2,node3" + ENABLE_INFLUX: "true" + INFLUX_DBUSER: "user" + INFLUX_DBPASS: "influx_password" + INFLUX_HOST: "influx_host" + INFLUX_PORT: "8086" + INFLUX_DBNAME: "database" + INFLUX_DISPLAY_HOST: "your_hostname" + INFLUX_UPDATE_INTERVAL: "10" + devices: + - /dev/bus/usb/001/004:/dev/bus/usb/001/001 + ports: + - "3493:3493" + privileged: true + security_opt: + - seccomp:unconfined + cap_add: + - SYS_ADMIN diff --git a/shutdown-client/Makefile b/shutdown-client/Makefile new file mode 100644 index 0000000..1e0a749 --- /dev/null +++ b/shutdown-client/Makefile @@ -0,0 +1,4 @@ +all: build + +build: + go build -o shutdown-client main.go diff --git a/shutdown-client/main.go b/shutdown-client/main.go new file mode 100644 index 0000000..42407ea --- /dev/null +++ b/shutdown-client/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "flag" + "fmt" + "strings" + "net/http" + "time" +) + +var ( + DEFAULT_PORT = 4551 + GLOBAL_TIMEOUT = 10 + api_key string + action string +) + +type HostList []string + +func (hl *HostList) String() string { + return fmt.Sprintln(*hl) +} + +func (ml *HostList) Set(s string) error { + *ml = strings.Split(s, ",") + return nil +} + +func PostAction(host string) { + fmt.Printf("Shutting down host: %s\n",host) + url := fmt.Sprintf("http://%s:%d/%s",host,DEFAULT_PORT,action) + ApiClient := http.Client{ + Timeout: time.Second * time.Duration(GLOBAL_TIMEOUT), + } + req, err := http.NewRequest(http.MethodPost, url, nil) + if err != nil { + fmt.Printf("Unable to make a request to shutdown-server api %s\n",err) + return + } + req.Header.Set("Authorization", api_key) + res, getErr := ApiClient.Do(req) + if getErr != nil { + fmt.Printf("some other error then posting to shutdown-server api %s\n",getErr) + return + } + if res.StatusCode == 200 { + fmt.Printf("Shutdown confirmed for host: %s\n",host) + return + } else if res.StatusCode == 403 { + fmt.Printf("Unauthorized, bad api key\n") + return + } else if res.StatusCode == 401 { + fmt.Printf("Shutdown command failed on remote host!\n") + } else { + fmt.Printf("Unknown state of error, status code: %s\n",res.StatusCode) + return + } +} + +func ShutdownHosts(hl HostList) { + for _, host := range hl { + PostAction(host) + } +} + +func main() { + var hlist HostList + flag.StringVar(&action,"action","","What action to take shutdown or cancel ?") + flag.StringVar(&api_key, "api_key", "", "Shutdown api key") + flag.Var(&hlist,"hosts","List of hosts to shutdown") + flag.Parse() + if action == "shutdown" { + ShutdownHosts(hlist) + } else if action == "cancel" { + ShutdownHosts(hlist) + } else { + fmt.Printf("Unknown action provided, please use shutdown or cancel\n") + } +} diff --git a/shutdown-client/shutdown-client b/shutdown-client/shutdown-client new file mode 100755 index 0000000..c36c20b Binary files /dev/null and b/shutdown-client/shutdown-client differ diff --git a/shutdown-server/Makefile b/shutdown-server/Makefile new file mode 100644 index 0000000..e512374 --- /dev/null +++ b/shutdown-server/Makefile @@ -0,0 +1,7 @@ +all: build + +build: + go build -o shutdown-server main.go +deploy: + ./deploy $(filter-out $@,$(MAKECMDGOALS)) +.PHONY: deploy diff --git a/shutdown-server/README.md b/shutdown-server/README.md new file mode 100644 index 0000000..54021ad --- /dev/null +++ b/shutdown-server/README.md @@ -0,0 +1,30 @@ +# Shutdown server, what is it ? + +What if you have multiple hosts connected to the same UPS and want them to safely shutdown, when electricity goes down? + +The shutdown server runs on host (or many hosts) and waits for api call either it's shutdown or shutdown cancel command, when the electricity fails, nut-upsd daemon with call to these shutdown servers. + +**Remember to deploy shutdown-server to all hosts you are running on ups** + +# How to deploy ? + + +``` +make deploy user@target +``` + +If you would like to use api key for security reasons, then copy settings file and re-deploy +``` +cp shutdown-server.conf-example shutdown-server.conf +``` + +It uses sudo, so make sure it's setup correctly + +Use this shutdown-server with project https://github.com/e1z0/nut-upsd, that docker container already has support for shutdown-client and shutdown-server, just use docker environment variable **SHUTDOWN_CMD** + +Example.: +``` +SHUTDOWN_CMD: "/usr/local/bin/shutdown-client -action shutdown -api_key abc -hosts node0,node3,node1,node2" +``` + +There is [docker-compose](../docker-compose.yml) file that shows this stuff in action. diff --git a/shutdown-server/deploy b/shutdown-server/deploy new file mode 100755 index 0000000..b6d0f0a --- /dev/null +++ b/shutdown-server/deploy @@ -0,0 +1,12 @@ +#!/bin/sh + +TARGET="$1" + +ssh ${TARGET} "sudo systemctl stop shutdown-server" +scp shutdown-server ${TARGET}:/tmp/ +ssh ${TARGET} "sudo mv /tmp/shutdown-server /usr/local/bin/shutdown-server" +scp shutdown-server.service ${TARGET}:/tmp/ +ssh ${TARGET} "sudo mv /tmp/shutdown-server.service /etc/systemd/system/shutdown-server.service" +scp shutdown-server.conf ${TARGET}:/tmp/ +ssh ${TARGET} "sudo mv /tmp/shutdown-server.conf /etc/" +ssh ${TARGET} "sudo systemctl enable shutdown-server;sudo systemctl restart shutdown-server" diff --git a/shutdown-server/go.mod b/shutdown-server/go.mod new file mode 100644 index 0000000..9b26f79 --- /dev/null +++ b/shutdown-server/go.mod @@ -0,0 +1,15 @@ +module example.com/m + +go 1.19 + +require ( + github.com/fasthttp/router v1.4.13 + github.com/valyala/fasthttp v1.41.0 +) + +require ( + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/klauspost/compress v1.15.9 // indirect + github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect +) diff --git a/shutdown-server/go.sum b/shutdown-server/go.sum new file mode 100644 index 0000000..1f4778d --- /dev/null +++ b/shutdown-server/go.sum @@ -0,0 +1,26 @@ +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/fasthttp/router v1.4.13 h1:42M7+7tNO6clb5seb4HhXlBIX1lnNv8DLhiT6jUv75A= +github.com/fasthttp/router v1.4.13/go.mod h1:mVhHMaSQA2Hi1HeuL/ZMuZpsZWk5bya75EpaDr3fO7E= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d h1:Q+gqLBOPkFGHyCJxXMRqtUgUbTjI8/Ze8vu8GGyNFwo= +github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY= +github.com/valyala/fasthttp v1.41.0/go.mod h1:f6VbjjoI3z1NDOZOv17o6RvtRSWxC77seBFc2uWtgiY= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/shutdown-server/main.go b/shutdown-server/main.go new file mode 100644 index 0000000..88fbff7 --- /dev/null +++ b/shutdown-server/main.go @@ -0,0 +1,153 @@ +package main + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "strings" + router2 "github.com/fasthttp/router" + "github.com/valyala/fasthttp" +) + +var ( + HTTP_PORT = 4551 + DEBUG = 1 + CONF_FILE = "/etc/shutdown-server.conf" + LOG_FILE = "/var/log/shutdown-server.log" + API_KEY = "" + logger *log.Logger +) + + +func Index(ctx *fasthttp.RequestCtx) { + ctx.SetStatusCode(fasthttp.StatusOK) +} + + +func CaptureCmd(cmd []string) (string, bool) { + out, err := exec.Command(cmd[0], cmd[1:]...).Output() + if err != nil { + fmt.Printf("Exec failed: %s\n", err) + return string(out), false + } + return string(out), true +} + +func ShutdownAction() bool { + logger.Printf("Shutdown action reached\n") + params := []string{"shutdown", "-h"} + out, ok := CaptureCmd(params) + if !ok { + fmt.Printf("Shutdown command have failed: %s\n",out) + return false + } + return ok +} + +func ShutdownCancelAction() bool { + logger.Printf("Shutdown cancel action reached\n") + params := []string{"shutdown", "-c"} + out, ok := CaptureCmd(params) + if !ok { + fmt.Printf("Shutdown cancel command have failed: %s\n",out) + return false + } + return ok +} + +func ShutdownRequest(ctx *fasthttp.RequestCtx) { + method := ctx.Method() + if string(method) == "POST" { + auth := ctx.Request.Header.Peek("Authorization") + if CheckAuth(string(auth)) { + logger.Printf("Authorization ok, requesting shutdown device...\n") + stat := ShutdownAction() + if stat { + ctx.SetStatusCode(fasthttp.StatusOK) + } else { + ctx.SetStatusCode(401) + } + } else { + logger.Printf("Bad authorization\n") + ctx.SetStatusCode(403) + } + } +} + + +func ShutdownCancel(ctx *fasthttp.RequestCtx) { + method := ctx.Method() + if string(method) == "POST" { + auth := ctx.Request.Header.Peek("Authorization") + if CheckAuth(string(auth)) { + logger.Printf("Authorization ok, requesting to cancel shutdown device...\n") + stat := ShutdownCancelAction() + if stat { + ctx.SetStatusCode(fasthttp.StatusOK) + } else { + ctx.SetStatusCode(401) + } + } else { + logger.Printf("Bad authorization\n") + ctx.SetStatusCode(403) + } + } +} + +func CheckAuth(key string) bool { + if API_KEY == "" { + return true + } + if API_KEY == key { + return true + } + return false +} + +func ReadConf() { + if _, err := os.Stat(CONF_FILE); err == nil { + tmp_conf, err := ioutil.ReadFile(CONF_FILE) + if err != nil { + return + } + API_KEY = TruncateMaterial(string(tmp_conf)) + } +} + +func TruncateMaterial(str string) string { + str = strings.Replace(str, "\t", "", -1) + str = strings.Replace(str, "\n", "", -1) + return str +} + +func HttpPool() { + router := router2.New() + router.GET("/", Index) + router.POST("/shutdown",ShutdownRequest) + router.POST("/cancel",ShutdownCancel) + logger.Printf("Binding web service to 0.0.0.0:%d...\n", HTTP_PORT) + s := &fasthttp.Server{ + Handler: router.Handler, + Name: "Damn it's just http server", + MaxRequestBodySize: 32 << 20, + } + s.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", HTTP_PORT)) +} + + +func Logging() { + f, err := os.OpenFile(LOG_FILE, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + fmt.Println("debug log file not created", err.Error()) + } + logger = log.New(f, "[LOG]", log.Ldate|log.Ltime|log.Lmicroseconds) + logger.Println("log started") +} + +func main() { + Logging() + ReadConf() + HttpPool() +} diff --git a/shutdown-server/shutdown-server b/shutdown-server/shutdown-server new file mode 100755 index 0000000..b035af3 Binary files /dev/null and b/shutdown-server/shutdown-server differ diff --git a/shutdown-server/shutdown-server.conf-example b/shutdown-server/shutdown-server.conf-example new file mode 100644 index 0000000..acbe86c --- /dev/null +++ b/shutdown-server/shutdown-server.conf-example @@ -0,0 +1 @@ +abcd diff --git a/shutdown-server/shutdown-server.service b/shutdown-server/shutdown-server.service new file mode 100644 index 0000000..a319e9a --- /dev/null +++ b/shutdown-server/shutdown-server.service @@ -0,0 +1,16 @@ +[Unit] +Description=Shutdown Server daemon +After=network.target + +[Service] +User=root +Group=root +Type=simple +WorkingDirectory=/usr/local/bin +ExecStart=/usr/local/bin/shutdown-server +KillMode=process +RestartSec=5s +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/src/docker-entrypoint b/src/docker-entrypoint index 2365511..a2baa64 100755 --- a/src/docker-entrypoint +++ b/src/docker-entrypoint @@ -1,5 +1,14 @@ #!/bin/sh -ex + +influx_loop() { + while true + do + /usr/local/bin/flux_mon + sleep 10 + done +} + if [ -z "$API_PASSWORD" ] then API_PASSWORD=$(dd if=/dev/urandom bs=18 count=1 2>/dev/null | base64) @@ -38,9 +47,15 @@ MONITOR $UPS_NAME@localhost 1 monitor $API_PASSWORD master SHUTDOWNCMD "$SHUTDOWN_CMD" EOF -chgrp -R nut /etc/nut /dev/bus/usb -chmod -R o-rwx /etc/nut +#chgrp -R nut /etc/nut /dev/bus/usb +#chmod -R o-rwx /etc/nut /usr/sbin/upsdrvctl start /usr/sbin/upsd + +if [ "$ENABLE_INFLUX" = "true" ] +then + influx_loop& +fi + exec /usr/sbin/upsmon -D diff --git a/src/flux_mon b/src/flux_mon new file mode 100755 index 0000000..326eda3 --- /dev/null +++ b/src/flux_mon @@ -0,0 +1,32 @@ +#!/bin/bash +# (c) 2022 e1z0 +# bash shell ups script to gather statistics from nut-upsd running in docker container +# see: https://github.com/e1z0/nut-upsd + +while true; do +# Collect the stats +STATS=$(upsc ups) + +#Gather Values for statistics +LOADPERCENT=$(echo -e "$STATS" |grep ups.load | cat | awk '{print $2}') +RUNTIME=$(echo -e "$STATS" |grep runtime: | cat | awk '{print $2}') +UTILVOLT=$(echo -e "$STATS" |grep input.voltage: | cat | awk '{print $2}') +OUTVOLT=$(echo -e "$STATS" |grep output.voltage: | cat | awk '{print $2}') +BATTCAP=$(echo -e "$STATS" |grep battery.charge: | cat | awk '{print $2}') +STATE=$(echo -e "$STATS" |grep ups.status: | cat | awk '{print $2}') +RATING=$(echo -e "$STATS" |grep ups.realpower.nominal: | cat | awk '{print $2}') +#LOADPERCENT=$(echo -e "$STATS" |grep ups.load | cat | awk '{print $2}') +LOAD=$((${RATING}*${LOADPERCENT}/100)) + +#Check State, 1 = Normal, 0 = Other +if [ $STATE == "OL" ]; then + STATE="1" +else + STATE="0" +fi + +#Write out to InfluxDB +/usr/bin/curl -s -i -XPOST -u ${INFLUX_DBUSER}:${INFLUX_DBPASS} "http://${INFLUX_HOST}:${INFLUX_PORT}/write?db=${INFLUX_DBNAME}" --data-binary "power_status,host=${INFLUX_DISPLAY_HOST} success=0,load=$LOAD,runtime=$RUNTIME,utility_voltage=$UTILVOLT,output_voltage=$OUTVOLT,battery_capacity=$BATTCAP,power_state=$STATE,ups_rating=$RATING,load_percentage=$LOADPERCENT" > /dev/null +#Wait X seconds +sleep ${INFLUX_UPDATE_INTERVAL} +done