Skip to content

MarekWo/UPS_Server_Docker

Repository files navigation

UPS Power Management Server (Docker Edition)

License: MIT

A complete, containerized solution for managing graceful shutdowns for UPS-protected servers that lack a direct data monitoring port. It combines a virtual NUT (Network UPS Tools) server, a power monitoring script, a central API hub for client configuration, and a modern web interface for easy management.

About This Project

▢️ Play Podcast about UPS Server

Many powerful, cost-effective UPS systems (like sine wave inverters with large external batteries) can power a server rack for hours but offer no way to signal a power failure to the connected devices. This project solves that problem by using a "canary in a coal mine" approach: it monitors several always-on devices (sentinel hosts) that are connected to standard, non-UPS grid power. If all of them go offline simultaneously, the application declares a power failure.

This container acts as the "brain" for a virtual NUT server, allowing standard NUT clients to perform a graceful shutdown. It also provides a central API to manage shutdown configurations for all clients and a modern web interface for easy administration.

This project is the perfect companion to the UPS_monitor client script, which is a lightweight client designed to react to the status changes generated by this Power Manager.


How It Works

The logic is simple but effective: Dashboard

  1. Monitor: A cron job inside the container runs a script (power_manager.py) which pings a list of user-defined sentinel hosts every 15 seconds (4 checks per minute) for rapid power failure detection.
  2. Decide:
    • If at least one sentinel host is online, the script assumes grid power is OK and sets the virtual NUT server's status to OL (Online).
    • If all sentinel hosts are offline, the script assumes a power failure and sets the status to OB LB (On Battery, Low Battery).
  3. Act & Report:
    • NUT clients (UPS_monitor scripts) periodically check the server's status. When they detect OB LB, they initiate a graceful shutdown countdown.
    • Clients are responsible for actively reporting their status (e.g., online, shutdown_pending with remaining time) back to the server's API on every check.
    • The Web GUI displays a live feed of these reported statuses. It can now show detailed states like Online, Shutting down..., WoL sent, or Status Stale if a client stops reporting.
    • When power is restored, the power_manager.py script waits a configurable delay and sends Wake-on-LAN (WoL) packets to offline clients. It also sets their status to WoL sent in the dashboard, providing clear feedback on the recovery process.

Features

  • Hardware-Independent: Works with any UPS because it doesn't require a direct data connection.
  • Easy Deployment: Fully containerized and managed with a single docker-compose.yml file.
  • Modern Web GUI: Intuitive web interface for configuration and monitoring, optimized for both desktop and mobile devices.
  • Centralized Management API: A lightweight REST API serves client configurations (/config), live UPS status (/upsc), and receives client status updates (/status), centralizing all interactions.
  • Live Client Shutdown Monitoring: The dashboard displays a real-time countdown for each client that is preparing for shutdown, providing a clear overview of the system's state during a power outage.
  • Power Outage Simulation: Manually trigger a simulated power failure from the web interface to test client shutdown procedures without physically disconnecting power.
  • Scheduled Power Outage Simulation: Configure automatic start/stop times for the simulation mode for regular, hands-free testing.
  • Standard-Compliant: Controls a standard NUT server, making it compatible with any NUT client (Linux, Windows, Synology DSM, etc.).
  • Automated Recovery: Includes a delayed Wake-on-LAN function to automatically restart servers after stable power has returned.
  • Unified Configuration: Single configuration file (power_manager.conf) manages all aspects of the system.
  • Robust Logging: Includes built-in log rotation and optional, configurable forwarding to a central syslog server like Graylog.
  • Email Notifications: Receive real-time alerts for critical events like power outages, power restoration, client status changes, and application errors.

Project Structure


.
β”œβ”€β”€ app/                    # Main application scripts
β”‚   β”œβ”€β”€ power_manager.py    # Core monitoring script
β”‚   β”œβ”€β”€ api.py              # REST API server
β”‚   β”œβ”€β”€ web_gui.py          # Web interface application
β”‚   β”œβ”€β”€ mail_send.py        # mail sending script
β”‚   └── templates/          # HTML templates for web GUI
β”œβ”€β”€ config/                 # All user-editable configuration files
β”‚   └── power_manager.conf  # Main configuration file
β”œβ”€β”€ cron/                   # Cron job definition
β”œβ”€β”€ logrotate/              # Log rotation configuration
β”œβ”€β”€ rsyslog/                # (Optional) Syslog forwarding configuration
β”œβ”€β”€ .gitignore              # Prevents local configs from being committed
β”œβ”€β”€ Dockerfile              # The blueprint for the container image
β”œβ”€β”€ docker-compose.yml      # The easy-run file for Docker Compose
└── entrypoint.sh           # The container's startup script


Prerequisites

  • A host machine that is connected to the UPS to run the container.
  • Docker and Docker Compose installed on the host machine. You may find my Wiki instruction helpful: How to Install Docker Engine on Debian.
  • A few reliable, always-on devices with static IPs on your network that are NOT connected to the UPS to act as sentinels.
  • curl, git packages installed on the host.

Important Note on Docker DNS Resolution (Workaround for recent Docker bug)

A recent Docker bug can cause DNS resolution to fail inside containers, potentially affecting features like email notifications. To mitigate this, a dns section has been added to docker-compose.yml.example with fallback public DNS servers.

If you wish to use custom DNS servers, you can configure them in your .env file:

DNS1=192.168.131.152
DNS2=192.168.131.153

Without this configuration, features relying on external network resolution (e.g., sending emails) might cease to function.


Installation and Configuration

  1. Clone the Repository:

    git clone [https://github.com/MarekWo/UPS_Server_Docker.git](https://github.com/MarekWo/UPS_Server_Docker.git) /opt/ups-server-docker
    cd /opt/ups-server-docker
  2. Prepare Docker Compose File: Copy the example Docker Compose file. This ensures your customized file won't be overwritten by future git pull updates.

    cp docker-compose.yml.example docker-compose.yml

    You can now edit docker-compose.yml if you need to make advanced changes, but for most users, the default is fine.

  3. Configure the Server: All configuration is now managed through a single file: ./config/power_manager.conf.

    • First, copy the example configuration file:
      cp config/power_manager.conf.example config/power_manager.conf
    • Then, edit config/power_manager.conf with your specific values:
      • SENTINEL_HOSTS: A space-separated list of IPs for your sentinel devices.
      • WOL_DELAY_MINUTES: The time in minutes to wait after power is restored before sending WoL packets.
      • UPS_STATE_FILE: The path to the state file used by the dummy-ups driver. This must match the port setting in NUT configuration.
      • API_TOKEN: (Required) The secret token used to authenticate client requests. This value must match the token used by your UPS_monitor clients.
      • DEFAULT_BROADCAST_IP: The default broadcast address for Wake-on-LAN packets.
      • SMTP Settings:
        • SMTP_SERVER
        • SMTP_PORT
        • SMTP_USE_TLS
        • SMTP_USERNAME
        • SMTP_PASSWORD
        • SMTP_SENDER_NAME
        • SMTP_SENDER_EMAIL
        • SMTP_RECIPIENTS
      • Notification Settings:
        • NOTIFY_POWER_FAIL
        • NOTIFY_POWER_RESTORED
        • NOTIFY_CLIENT_SHUTDOWN
        • NOTIFY_CLIENT_STALE
        • NOTIFY_APP_ERROR
        • NOTIFY_SIMULATION_MODE
      • [WAKE_HOST_X]: Sections defining each server to wake up. Each section requires:
        • NAME: Descriptive name for the host
        • IP: IP address of the host
        • MAC: MAC address for Wake-on-LAN
        • BROADCAST_IP: (optional) Specific broadcast IP for this host
        • SHUTDOWN_DELAY_MINUTES: (optional) Makes this host a UPS client with specified shutdown delay. Use 0 for immediate shutdown
        • AUTO_WOL: (optional): When set to "false" the WoL packet will not be sent to this host automatically
  4. Environment Configuration: This file (.env) is used to pass crucial settings into the container, such as the host's IP address and your local timezone.

    • First, copy the example environment file if you haven't already:
      cp .env.example .env
    • Then, edit the .env file and set the following variables:
      • TZ: Your local timezone name (e.g., Europe/Warsaw).
      • UPS_SERVER_HOST_IP: (Required) The IP address of the Docker host machine. This is the IP that your NUT clients will use to connect to the server (e.g., 192.168.1.10).
  5. (Optional) Configure Syslog Forwarding: This allows you to send all internal logs to a central server like Graylog.

    • First, copy the example configuration file:
      cp rsyslog/custom.conf.example /etc/rsyslog.d/custom.conf
    • Then, edit rsyslog/custom.conf and replace the placeholder IP address and port with your syslog server details.

Example Configuration

Here's an example of the unified power_manager.conf file:

# === CONFIGURATION FILE FOR power_manager.py ===

# Sentinel hosts - devices on grid power (not UPS) for monitoring
SENTINEL_HOSTS="192.168.1.11 192.168.1.12 192.168.1.13 192.168.1.14"

# Time to wait after power restoration before sending WoL packets
WOL_DELAY_MINUTES=5

# Secret token for API authentication (must match client configuration)
API_TOKEN="your_super_secret_api_token"

# Enable Power Outage Simulation mode.
# When set to "true", this will force the UPS status to "OB LB" regardless
# of the sentinel hosts' status. Useful for testing shutdown procedures.
# Valid values: "true" or "false".
POWER_SIMULATION_MODE="false"

# Path to the dummy-ups driver's state file
UPS_STATE_FILE=/var/run/nut/virtual.device

# Default broadcast address for WoL packets
DEFAULT_BROADCAST_IP=192.168.1.255

# === SMTP NOTIFICATIONS ===
SMTP_SERVER="smtp.example.com"
SMTP_PORT="587"
SMTP_USE_TLS="auto"
SMTP_USER="user@example.com"
SMTP_PASSWORD="your_password"
SMTP_SENDER_NAME="UPS Server"
SMTP_SENDER_EMAIL="ups@example.com"
SMTP_RECIPIENTS="admin@example.com"

# === NOTIFICATION SETTINGS ===
# Enable or disable notifications for specific events. Valid values: "true" or "false".
NOTIFY_POWER_FAIL="true"
NOTIFY_POWER_RESTORED="true"
NOTIFY_CLIENT_SHUTDOWN="false"
NOTIFY_CLIENT_STALE="true"
NOTIFY_APP_ERROR="true"
NOTIFY_SIMULATION_MODE="true"

# === WAKE-ON-LAN HOST DEFINITIONS ===

# UPS Client with shutdown delay
[WAKE_HOST_1]
NAME=Synology NAS
IP=192.168.1.12
MAC=00:11:32:f8:af:9f
BROADCAST_IP=192.168.1.255
SHUTDOWN_DELAY_MINUTES=10

# Another UPS Client
[WAKE_HOST_2]
NAME=Proxmox Server
IP=192.168.1.13
MAC=00:11:32:2c:31:42
SHUTDOWN_DELAY_MINUTES=15

# UPS Client with immediate shutdown (delay=0)
[WAKE_HOST_3]
NAME=Low-Battery UPS Host
IP=192.168.1.14
MAC=00:11:32:bb:cc:dd
SHUTDOWN_DELAY_MINUTES=0

# WoL-only host (no UPS client functionality)
[WAKE_HOST_4]
NAME=File Server
IP=192.168.1.15
MAC=00:11:32:aa:bb:cc
AUTO_WOL="false"

# === POWER OUTAGE SIMULATION SCHEDULES ===

# [SCHEDULE_1]
# NAME="Weekly Test Shutdown"
# TYPE="recurring"
# DAY_OF_WEEK="friday"
# TIME="23:00"
# ACTION="start"
# ENABLED="true"

The SMTP_USE_TLS option provides explicit control over STARTTLS usage:

  • auto (default): Uses STARTTLS on all ports except 26 (legacy behavior)
  • true: Always attempts STARTTLS (except on port 465 which uses SSL)
  • false: Never uses STARTTLS (for servers that don't support it)

πŸ”Œ Network Configuration for Wake-on-LAN (WoL)

The Wake-on-LAN (WoL) feature requires the container to send special "magic packets" to your network's broadcast address. Docker's default, sandboxed network mode prevents containers from sending broadcast packets to the physical LAN, which means the WoL feature will not work out of the box.

To enable WoL, you must allow the container to share the host's network stack.

Option 1: Full Functionality with host Network Mode (Recommended for WoL)

The simplest way to enable WoL is to add the network_mode: host directive to your docker-compose.yml file. This gives the container direct access to the host's network interfaces.

Your docker-compose.yml should be modified to include this line:

services:
  ups-server:
    build: .
    container_name: ups-server
    restart: unless-stopped
    network_mode: host  # This line enables WoL functionality

    # NOTE: The 'ports' section is ignored in host mode and can be removed.
    # ports:
    #   - "3493:3493"
    #   - "5000:5000"
    #   - "80:80"
    
    # ... rest of your configuration

Security Note: Using network_mode: host removes network isolation between the container and the host. The container gains access to all of the host's network interfaces and can bind to any port. While this application is built to be trustworthy, you should always be aware of the security implications of this setting.

Option 2: Standard Network Isolation (WoL Disabled)

If you do not need the Wake-on-LAN feature or are not comfortable with using host network mode, simply do not add the network_mode: host line.

In this case, the NUT server and API will function correctly for monitoring and shutting down clients, but the ability to automatically wake them up will be disabled.


Usage

Once configured, starting the server is a single command from the project's root directory:

docker compose up --build -d
  • --build: Only needed the first time or after changing the Dockerfile or application scripts.
  • -d: Runs the container in the background (detached mode).

Managing the Container:

  • View Logs:
    docker compose logs -f
  • Stop the Container:
    docker compose down
  • Restart the Container:
    docker compose restart

You can also monitor the power_manager.log file in the ./logs directory, which is created automatically on your host.


Web Interface

The UPS Server includes a modern, responsive web interface for easy configuration and monitoring.

Accessing the Web GUI

After starting the container, the web interface will be available at:

http://<UPS_SERVER_IP>

For example, if your UPS server host has IP 192.168.1.10:

http://192.168.1.10

Web GUI Features

  • Dashboard: Real-time monitoring of system status, sentinel hosts, and UPS clients. Shows a live countdown for clients that are shutting down.
  • Configuration Management: Easy-to-use forms for managing all system settings.
  • Live Status Updates: Automatic refresh of host statuses every 5 seconds.
  • Mobile Optimized: Responsive design that works on all devices.
  • One-Click Wake-on-LAN: Send WoL packets directly from the interface.

For detailed Web GUI documentation, see WEB_GUI_README.md.


API Endpoints

The server provides a REST API for client configuration and status monitoring. All endpoints require an Authorization header with a bearer token. The API token is now configured in config/power_manager.conf.

Example Request:

curl -H "Authorization: Bearer <your_secret_token>" http://<server_ip>:5000/upsc

GET /config

This endpoint provides client-specific shutdown configuration. The client's IP address is used to look up its settings in the WAKE_HOST_X sections of power_manager.conf.

  • Query Parameter: ip=<client_ip> (optional, falls back to request source IP).
  • Returns: A JSON object with the client's configuration, including the dynamically generated UPS_NAME and SHUTDOWN_DELAY_MINUTES.

Example Response (/config?ip=192.168.1.12):

{
  "SHUTDOWN_DELAY_MINUTES": "10",
  "UPS_NAME": "ups@192.168.1.10"
}

POST /status

This endpoint allows UPS clients to report their current status back to the server. This is used to display the shutdown countdown in the Web GUI.

  • Body: A JSON object containing the client's status.
  • Example Payload:
    {
      "ip": "192.168.1.12",
      "status": "shutdown_pending",
      "remaining_seconds": 245,
      "shutdown_delay": 5
    }

GET /upsc

This endpoint provides live status information from the NUT server, equivalent to running the upsc command locally, but with clean, nested JSON output.

  • Returns: A nested JSON object containing all available UPS variables, plus additional simulation status information. This is ideal for monitoring dashboards or advanced client-side logic.
  • Simulation Detection: The response includes a simulation field in the ups section that indicates whether the current power outage status is real (false) or simulated (true).

Example Response:

{
  "device": {
    "mfr": "Dummy Manufacturer",
    "model": "Dummy UPS",
    "type": "ups"
  },
  "driver": {
    "name": "dummy-ups",
    "parameter": {
      "mode": "dummy",
      "pollinterval": 2,
      "port": "/var/run/nut/virtual.device"
    },
    "version": "2.8.0",
    "version.internal": 0.15
  },
  "ups": {
    "mfr": "Dummy Manufacturer",
    "model": "Dummy UPS",
    "status": "OL",
    "simulation": false
  }
}

Simulation Status Field:

The simulation field in the ups section indicates the current power outage simulation status:

  • false: Normal operation - UPS status reflects real power conditions
  • true: Simulation mode active - UPS status is artificially set for testing purposes

This field is read from the POWER_SIMULATION_MODE parameter in power_manager.conf and allows UPS clients to distinguish between real power outages and simulated ones for testing.


Services and Ports

The container provides the following services:

  • Port 80: Web GUI interface
  • Port 5000: REST API for client configuration
  • Port 3493: NUT server for UPS clients

Updating

To update the application to the latest version from GitHub, follow these steps. This method is robust and will discard any accidental local changes, ensuring a clean update.

  1. Navigate to the application directory

    cd /opt/ups-server-docker

    Note: sudo is likely required for the following commands if you cloned the repository into a system directory like /opt.

  2. Fetch the latest version from the repository This command downloads the latest updates from GitHub.

    sudo git fetch origin
  3. Reset your local files to match the latest version This command will discard any local changes (like modified permissions or accidental edits) and force your local copy to match the official version.

    sudo git reset --hard origin/main
  4. Rebuild and restart the container This applies the updates and restarts the application.

    sudo docker compose up --build -d

    Docker Compose will intelligently rebuild only what's necessary and restart the container. Your configuration files in the ./config directory will be preserved.


Migration from the Hub Edition (from before August 15th, 2025)

If you're upgrading from a version that used separate upshub.conf configuration:

  1. Backup your current configuration:

    cp config/power_manager.conf config/power_manager.conf.backup
    cp config/upshub.conf config/upshub.conf.backup
  2. Migrate UPS client settings: Add SHUTDOWN_DELAY_MINUTES parameter to appropriate [WAKE_HOST_X] sections in power_manager.conf based on your old upshub.conf settings.

  3. Remove old configuration:

    rm config/upshub.conf
  4. Update and restart:

    docker compose up --build -d

🀝 Contributing

Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this project better, please feel free to share it. The preferred way to do so is by starting a conversation in the Discussions section of the repository.

If you would like to contribute with your code, please fork the repo and create a pull request.

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

Thank you for helping make this tool better!

πŸ› Reporting Issues and Suggesting Features

Have you found a bug or have an idea for a new feature? I would love to hear from you!

To ensure that ideas are well-discussed and bugs are properly triaged, this project uses GitHub Discussions as the first step for all new reports.

How to submit an issue or idea:

  1. Go to the Discussions tab and open a new discussion in the "Ideas" category.
  2. Provide a clear title and a detailed description of the issue or your suggestion. If you're reporting a bug, please include:
    • Steps to reproduce the behavior.
    • What you expected to happen.
    • What actually happened (screenshots are welcome!).
    • Your environment details (e.g., Docker version, host OS).
  3. Engage in the discussion. I will review your post and may ask follow-up questions.
  4. From Discussion to Issue. If the report is confirmed as a bug or the feature is considered for implementation, I will create an official Issue directly from your discussion thread to track its progress.

This process helps keep the official issue tracker clean and focused on actionable items. Thank you for your understanding and cooperation!

🎬 BONUS: See the UPS Server Presentation on YouTube

See the video on YouTube


License

This project is licensed under the MIT License - see the LICENSE file for details.

About

A complete, containerized solution for managing graceful shutdowns for UPS-protected servers that lack a direct data monitoring port. It combines a virtual NUT (Network UPS Tools) server, a power monitoring script, a central API hub for client configuration, and a modern web interface for easy management.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

Packages

 
 
 

Contributors