nut

package module
v0.0.0-...-e16e18b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2025 License: MIT Imports: 13 Imported by: 0

README

GoDoc

go.nut

go.nut is a Golang library for interacting with NUT (Network UPS Tools)

This is a maintained fork with support for modern NUT servers, including TLS/SSL via STARTTLS, improved error handling, thread-safety, context support, connection pooling, and production-ready features.

Features

Core Features
  • ✅ TCP communication with NUT servers (port 3493)
  • ✅ TLS/SSL support via STARTTLS (NUT >= 2.7.0)
  • ✅ Authentication support (username/password)
  • ✅ UPS listing and management
  • ✅ Variable and command querying
  • ✅ Thread-safe operations with mutex protection
  • ✅ Proper error handling and connection lifecycle management
  • ✅ Support for UPS names with spaces/special characters
Production Features (Optional)
  • Context Support: Cancellation and timeout control for all operations
  • Connection Pool: Efficient connection reuse for high-concurrency scenarios
  • Metrics & Monitoring: Track commands, bytes, errors, and reconnects
  • Custom Timeouts: Flexible timeout configuration via options pattern
  • Debug Logging: Optional logger for troubleshooting
  • Go modules support (go 1.21+)

Getting started

import "github.com/bearx3f/go.nut"

Basic Connection

// Simple connection (backward compatible)
client, err := nut.Connect("192.168.1.100")
if err != nil {
    log.Fatal(err)
}
defer client.Disconnect()

// Get list of UPS devices
upsList, err := client.GetUPSList()
if err != nil {
    log.Fatal(err)
}

// Access UPS variables
for _, ups := range upsList {
    vars, _ := ups.GetVariables()
    fmt.Printf("UPS: %s\n", ups.Name)
    for name, value := range vars {
        fmt.Printf("  %s = %v\n", name, value)
    }
}

Using TLS/SSL (STARTTLS)

client, err := nut.Connect("192.168.1.100")
if err != nil {
    log.Fatal(err)
}

// Start TLS connection
err = client.StartTLS()
if err != nil {
    log.Fatal(err)
}

// Now authenticate and use securely
authenticated, err := client.Authenticate("username", "password")
if err != nil || !authenticated {
    log.Fatal("Authentication failed")
}

defer client.Disconnect()

Advanced Features

Context Support & Custom Options
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Connect with custom options
client, err := nut.ConnectWithOptionsAndConfig(ctx, "192.168.1.100", 
    []nut.ClientOption{
        nut.WithConnectTimeout(5 * time.Second),
        nut.WithReadTimeout(3 * time.Second),
        nut.WithLogger(log.Default()),
    }, 
    3493,
)
if err != nil {
    log.Fatal(err)
}
defer client.Disconnect()

// Send command with context
resp, err := client.SendCommandWithContext(ctx, "VER")
if err != nil {
    log.Fatal(err)
}
Connection Pool (High-Concurrency)
// Create a connection pool
pool, err := nut.NewPool(nut.PoolConfig{
    Hostname:  "192.168.1.100",
    Port:      3493,
    MaxSize:   10, // Maximum 10 concurrent connections
    ClientOptions: []nut.ClientOption{
        nut.WithConnectTimeout(5 * time.Second),
        nut.WithReadTimeout(2 * time.Second),
    },
})
if err != nil {
    log.Fatal(err)
}
defer pool.Close()

// Get a client from the pool
ctx := context.Background()
client, err := pool.Get(ctx)
if err != nil {
    log.Fatal(err)
}

// Use the client
upsList, err := client.GetUPSList()

// Always return the client to the pool
pool.Put(client)

// Check pool statistics
idle, active := pool.Stats()
fmt.Printf("Pool - Idle: %d, Active: %d\n", idle, active)
Metrics & Monitoring
// Track client operations
metrics := client.GetMetrics()
fmt.Printf("Commands sent: %d\n", metrics.CommandsSent)
fmt.Printf("Commands failed: %d\n", metrics.CommandsFailed)
fmt.Printf("Bytes sent: %d\n", metrics.BytesSent)
fmt.Printf("Bytes received: %d\n", metrics.BytesReceived)
fmt.Printf("Reconnects: %d\n", metrics.Reconnects)

Documentation

Bug Fixes & Improvements

This fork includes numerous critical bug fixes and improvements:

Critical Fixes
  • TLS Implementation: Fixed STARTTLS to properly use tls.Client() instead of tls.Server()
  • Thread Safety: Added mutex protection for concurrent access to connections
  • Memory Leaks: Fixed connection cleanup in error paths
  • Buffer Management: Persistent bufio.Reader to prevent data loss
  • Name Escaping: Proper quoting for UPS/variable names with spaces
Error Handling
  • ✅ Response length validation before array access (prevents panics)
  • ✅ Proper error propagation throughout the stack
  • ✅ Connection state validation
  • ✅ Graceful error recovery
Type Detection
  • ✅ Improved variable type detection (RW/RO flags)
  • ✅ Better boolean type inference
  • ✅ Enhanced numeric parsing with regex

Performance Features

  • Connection Pooling: Reuse connections for high-throughput scenarios (10+ req/s)
  • Atomic Metrics: Lock-free statistics tracking
  • Context Cancellation: Cancel long-running operations efficiently
  • Configurable Timeouts: Fine-tune for your network conditions

Other resources

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Make sure golint and go vet run successfully
  4. go fmt your code
  5. Commit your changes (git commit -am "Add some feature")
  6. Push to the branch (git push origin my-new-feature)
  7. Create a new Pull Request

License

MIT

Compatibility

  • NUT Version: Tested with NUT 2.7.0+ (supports older versions without STARTTLS)
  • Go Version: 1.21+
  • OS: Linux, Windows, macOS
  • Thread-Safe: Yes, all operations protected by mutex
  • Production-Ready: Includes metrics, logging, and connection pooling

Migration from Original go.nut

This fork is backward compatible. Existing code will continue to work:

// Old code still works!
client, err := nut.Connect("localhost")
defer client.Disconnect()

To use new features, see the Optional Features Guide.

Documentation

Overview

Package nut provides a Golang interface for interacting with Network UPS Tools (NUT).

It communicates with NUT over the TCP protocol and supports TLS/SSL via STARTTLS

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	Version         string
	ProtocolVersion string
	Hostname        net.Addr

	UseTLS         bool
	TLSConfig      *tls.Config
	ConnectTimeout time.Duration
	ReadTimeout    time.Duration
	Logger         *log.Logger // Optional logger for debugging
	// contains filtered or unexported fields
}

Client contains information about the NUT server as well as the connection.

func Connect

func Connect(hostname string, _port ...int) (*Client, error)

Connect accepts a hostname/IP string and an optional port, then creates a connection to NUT, returning a Client.

Example

This example demonstrates how to connect to NUT, authenticate, and list UPS devices.

// Connect to NUT server (typically running on port 3493)
client, err := Connect("127.0.0.1")
if err != nil {
	log.Fatal(err)
}

// Authenticate
authenticated, err := client.Authenticate("username", "password")
if err != nil || !authenticated {
	log.Fatal("authentication failed")
}

// Get list of UPS devices
upsList, err := client.GetUPSList()
if err != nil {
	log.Fatal(err)
}

fmt.Println("Available UPS devices:", len(upsList))
if len(upsList) > 0 {
	fmt.Println("First UPS:", upsList[0].Name)
}

// Clean disconnect
client.Disconnect()

func ConnectWithOptions

func ConnectWithOptions(ctx context.Context, hostname string, port ...int) (*Client, error)

ConnectWithOptions creates a connection with custom options and context support.

Example

ExampleConnectWithOptions demonstrates using custom options

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	nut "github.com/bearx3f/go.nut"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	// Create logger
	logger := log.New(log.Writer(), "[NUT] ", log.LstdFlags)

	// Connect with custom options
	client, err := nut.ConnectWithOptionsAndConfig(ctx, "localhost", []nut.ClientOption{
		nut.WithConnectTimeout(5 * time.Second),
		nut.WithReadTimeout(3 * time.Second),
		nut.WithLogger(logger),
	}, 3493)
	if err != nil {
		log.Fatal(err)
	}
	defer client.Disconnect()

	// Use context for command execution
	resp, err := client.SendCommandWithContext(ctx, "VER")
	if err != nil {
		log.Fatal(err)
	}

	if len(resp) > 0 {
		fmt.Printf("Server version: %s\n", resp[0])
	}
}

func ConnectWithOptionsAndConfig

func ConnectWithOptionsAndConfig(ctx context.Context, hostname string, opts []ClientOption, port ...int) (*Client, error)

ConnectWithOptionsAndConfig creates a connection with full configuration support.

func (*Client) Authenticate

func (c *Client) Authenticate(username, password string) (bool, error)

Authenticate accepts a username and passwords and uses them to authenticate the existing NUT session.

func (*Client) Close

func (c *Client) Close() error

Close closes the connection without sending LOGOUT command. Use this if you just want to close the connection immediately.

func (*Client) Disconnect

func (c *Client) Disconnect() (bool, error)

Disconnect gracefully disconnects from NUT by sending the LOGOUT command.

func (*Client) GetMetrics

func (c *Client) GetMetrics() ClientMetrics

GetMetrics returns a copy of the current metrics

func (*Client) GetNetworkProtocolVersion

func (c *Client) GetNetworkProtocolVersion() (string, error)

GetNetworkProtocolVersion returns the version of the network protocol currently in use.

func (*Client) GetUPSList

func (c *Client) GetUPSList() ([]UPS, error)

GetUPSList returns a list of all UPSes provided by this NUT instance.

func (*Client) GetVersion

func (c *Client) GetVersion() (string, error)

GetVersion returns the the version of the server currently in use.

func (*Client) Help

func (c *Client) Help() (string, error)

Help returns a list of the commands supported by NUT.

func (*Client) ReadResponse

func (c *Client) ReadResponse(endLine string, multiLineResponse bool) (resp []string, err error)

ReadResponse is a convenience function for reading newline delimited responses.

func (*Client) SendCommand

func (c *Client) SendCommand(cmd string) (resp []string, err error)

SendCommand sends the string cmd to the device, and returns the response.

func (*Client) SendCommandWithContext

func (c *Client) SendCommandWithContext(ctx context.Context, cmd string) (resp []string, err error)

SendCommandWithContext sends a command with context support for cancellation.

func (*Client) StartTLS

func (c *Client) StartTLS() error

StartTLS initiates a TLS/SSL connection with the NUT server using STARTTLS command. This requires the NUT server to support STARTTLS (NUT >= 2.7.0).

type ClientMetrics

type ClientMetrics struct {
	CommandsSent    uint64
	CommandsFailed  uint64
	BytesSent       uint64
	BytesReceived   uint64
	Reconnects      uint64
	LastCommandTime atomic.Value // time.Time
}

ClientMetrics holds statistics for a client connection

Example

ExampleClientMetrics demonstrates monitoring client metrics

package main

import (
	"fmt"
	"log"

	nut "github.com/bearx3f/go.nut"
)

func main() {
	client, err := nut.Connect("localhost")
	if err != nil {
		log.Fatal(err)
	}
	defer client.Disconnect()

	// Authenticate
	_, err = client.Authenticate("monuser", "secret")
	if err != nil {
		log.Fatal(err)
	}

	// Perform some operations
	upsList, err := client.GetUPSList()
	if err != nil {
		log.Fatal(err)
	}

	for _, ups := range upsList {
		vars, _ := ups.GetVariables()
		fmt.Printf("UPS %s has %d variables\n", ups.Name, len(vars))
	}

	// Get metrics
	metrics := client.GetMetrics()
	fmt.Printf("Commands sent: %d\n", metrics.CommandsSent)
	fmt.Printf("Commands failed: %d\n", metrics.CommandsFailed)
	fmt.Printf("Bytes sent: %d\n", metrics.BytesSent)
	fmt.Printf("Bytes received: %d\n", metrics.BytesReceived)
	fmt.Printf("Reconnects: %d\n", metrics.Reconnects)
}

type ClientOption

type ClientOption func(*Client)

ClientOption is a function that configures a Client

func WithConnectTimeout

func WithConnectTimeout(timeout time.Duration) ClientOption

WithConnectTimeout sets a custom connection timeout

func WithLogger

func WithLogger(logger *log.Logger) ClientOption

WithLogger sets a logger for debugging

func WithReadTimeout

func WithReadTimeout(timeout time.Duration) ClientOption

WithReadTimeout sets a custom read timeout

func WithTLSConfig

func WithTLSConfig(config *tls.Config) ClientOption

WithTLSConfig sets a custom TLS configuration

type Command

type Command struct {
	Name        string
	Description string
}

Command describes an available command for a UPS.

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool manages a pool of Client connections for high-concurrency scenarios.

Example

ExamplePool demonstrates using connection pool for high-concurrency scenarios

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	nut "github.com/bearx3f/go.nut"
)

func main() {
	// Create a connection pool
	pool, err := nut.NewPool(nut.PoolConfig{
		Hostname: "localhost",
		Port:     3493,
		MaxSize:  10, // Maximum 10 connections
		ClientOptions: []nut.ClientOption{
			nut.WithConnectTimeout(5 * time.Second),
			nut.WithReadTimeout(2 * time.Second),
			nut.WithLogger(log.Default()),
		},
	})
	if err != nil {
		log.Fatal(err)
	}
	defer pool.Close()

	// Get a client from the pool
	ctx := context.Background()
	client, err := pool.Get(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Use the client
	upsList, err := client.GetUPSList()
	if err != nil {
		pool.Put(client) // Return even on error
		log.Fatal(err)
	}

	for _, ups := range upsList {
		fmt.Printf("UPS: %s\n", ups.Name)
	}

	// Return client to pool for reuse
	if err := pool.Put(client); err != nil {
		log.Printf("Failed to return client: %v", err)
	}

	// Check pool statistics
	idle, active := pool.Stats()
	fmt.Printf("Pool stats - Idle: %d, Active: %d\n", idle, active)
}

func NewPool

func NewPool(config PoolConfig) (*Pool, error)

NewPool creates a new connection pool with the given configuration.

func (*Pool) Close

func (p *Pool) Close() error

Close closes all clients in the pool and prevents new clients from being created.

func (*Pool) Get

func (p *Pool) Get(ctx context.Context) (*Client, error)

Get retrieves a client from the pool, creating a new one if needed.

func (*Pool) Put

func (p *Pool) Put(client *Client) error

Put returns a client to the pool. If the pool is full, the client is closed.

func (*Pool) Stats

func (p *Pool) Stats() (idle int, active int)

Stats returns statistics about the pool

type PoolConfig

type PoolConfig struct {
	MaxSize       int            // Maximum number of connections in pool
	Hostname      string         // NUT server hostname
	Port          int            // NUT server port (default 3493)
	ClientOptions []ClientOption // Options to apply to each client
}

PoolConfig contains configuration for connection pool

type UPS

type UPS struct {
	Name           string
	Description    string
	Master         bool
	NumberOfLogins int
	Clients        []string
	Variables      []Variable
	Commands       []Command
	// contains filtered or unexported fields
}

UPS contains information about a specific UPS provided by the NUT instance.

func NewUPS

func NewUPS(name string, client *Client) (UPS, error)

NewUPS takes a UPS name and NUT client and returns an instantiated UPS struct.

func (*UPS) CheckIfMaster

func (u *UPS) CheckIfMaster() (bool, error)

CheckIfMaster returns true if the session is authenticated with the master permission set.

func (*UPS) ForceShutdown

func (u *UPS) ForceShutdown() (bool, error)

ForceShutdown sets the FSD flag on the UPS.

This requires "upsmon master" in upsd.users, or "FSD" action granted in upsd.users

upsmon in master mode is the primary user of this function. It sets this "forced shutdown" flag on any UPS when it plans to power it off. This is done so that slave systems will know about it and shut down before the power disappears.

Setting this flag makes "FSD" appear in a STATUS request for this UPS. Finding "FSD" in a status request should be treated just like a "OB LB".

It should be noted that FSD is currently a latch - once set, there is no way to clear it short of restarting upsd or dropping then re-adding it in the ups.conf. This may cause issues when upsd is running on a system that is not shut down due to the UPS event.

func (*UPS) GetClients

func (u *UPS) GetClients() ([]string, error)

GetClients returns a list of NUT clients.

func (*UPS) GetCommandDescription

func (u *UPS) GetCommandDescription(commandName string) (string, error)

GetCommandDescription returns a string that gives a brief explanation for the given commandName.

func (*UPS) GetCommands

func (u *UPS) GetCommands() ([]Command, error)

GetCommands returns a slice of Command structs for the UPS.

func (*UPS) GetDescription

func (u *UPS) GetDescription() (string, error)

GetDescription the value of "desc=" from ups.conf for this UPS. If it is not set, upsd will return "Unavailable".

func (*UPS) GetNumberOfLogins

func (u *UPS) GetNumberOfLogins() (int, error)

GetNumberOfLogins returns the number of clients which have done LOGIN for this UPS.

func (*UPS) GetVariableDescription

func (u *UPS) GetVariableDescription(variableName string) (string, error)

GetVariableDescription returns a string that gives a brief explanation for the given variableName. upsd may return "Unavailable" if the file which provides this description is not installed.

func (*UPS) GetVariableType

func (u *UPS) GetVariableType(variableName string) (string, bool, int, error)

GetVariableType returns the variable type, writeability and maximum length for the given variableName.

func (*UPS) GetVariables

func (u *UPS) GetVariables() ([]Variable, error)

GetVariables returns a slice of Variable structs for the UPS.

func (*UPS) SendCommand

func (u *UPS) SendCommand(commandName string) (bool, error)

SendCommand sends a command to the UPS.

func (*UPS) SetVariable

func (u *UPS) SetVariable(variableName, value string) (bool, error)

SetVariable sets the given variableName to the given value on the UPS.

type Variable

type Variable struct {
	Name          string
	Value         interface{}
	Type          string
	Description   string
	Writeable     bool
	MaximumLength int
	OriginalType  string
}

Variable describes a single variable related to a UPS.

Directories

Path Synopsis
examples
simple_check command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL