Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
shutdown-server/shutdown-server.conf
80 changes: 32 additions & 48 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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
Expand Down
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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
69 changes: 57 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand All @@ -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.
27 changes: 27 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions shutdown-client/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
all: build

build:
go build -o shutdown-client main.go
79 changes: 79 additions & 0 deletions shutdown-client/main.go
Original file line number Diff line number Diff line change
@@ -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")
}
}
Binary file added shutdown-client/shutdown-client
Binary file not shown.
7 changes: 7 additions & 0 deletions shutdown-server/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
all: build

build:
go build -o shutdown-server main.go
deploy:
./deploy $(filter-out $@,$(MAKECMDGOALS))
.PHONY: deploy
30 changes: 30 additions & 0 deletions shutdown-server/README.md
Original file line number Diff line number Diff line change
@@ -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.
12 changes: 12 additions & 0 deletions shutdown-server/deploy
Original file line number Diff line number Diff line change
@@ -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"
15 changes: 15 additions & 0 deletions shutdown-server/go.mod
Original file line number Diff line number Diff line change
@@ -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
)
Loading