Bose SoundTouch Toolkit

Documentation for controlling and preserving Bose SoundTouch devices

View the Project on GitHub gesellix/Bose-SoundTouch

SoundTouch Production Deployment Guide

Best practices for deploying SoundTouch Go applications in production environments

This guide covers everything you need to know to deploy robust, scalable SoundTouch applications in production, including configuration management, monitoring, security, and operational considerations.

đź“‹ Table of Contents


Architecture Considerations

Single-Device Applications

Use Case: Home automation, personal music control

type SingleDeviceApp struct {
    client    *client.Client
    wsClient  *client.WebSocketClient
    config    Config
    logger    *log.Logger
    metrics   *Metrics
}

func NewSingleDeviceApp(config Config) *SingleDeviceApp {
    // Use resilient client with retries
    resilientClient := NewResilientClient(client.NewClient(client.ClientConfig{
        Host:    config.DeviceHost,
        Port:    config.DevicePort,
        Timeout: config.RequestTimeout,
    }))

    return &SingleDeviceApp{
        client: resilientClient,
        config: config,
        logger: log.New(os.Stdout, "[SoundTouch] ", log.LstdFlags),
    }
}

Multi-Device Applications

Use Case: Commercial installations, whole-house systems

type MultiDeviceManager struct {
    pool        *ConnectionPool
    devices     map[string]*DeviceInfo
    healthCheck *HealthChecker
    config      Config
    metrics     *prometheus.Registry
}

type DeviceInfo struct {
    Client      *client.Client
    Name        string
    Location    string
    Capabilities []string
    LastSeen    time.Time
    Status      DeviceStatus
}

func NewMultiDeviceManager(config Config) *MultiDeviceManager {
    return &MultiDeviceManager{
        pool:    NewConnectionPool(config.MaxConnections, config.IdleTimeout),
        devices: make(map[string]*DeviceInfo),
        config:  config,
    }
}

Microservice Architecture

Use Case: Enterprise integrations, API services

// Service interface for dependency injection
type SoundTouchService interface {
    GetDevices() ([]*DeviceInfo, error)
    ControlDevice(deviceID string, action Action) error
    GetDeviceStatus(deviceID string) (*Status, error)
}

// Implementation with circuit breakers, metrics, tracing
type ProductionSoundTouchService struct {
    manager     *MultiDeviceManager
    circuitBreaker *gobreaker.CircuitBreaker
    tracer      opentracing.Tracer
    metrics     metrics.Counter
}

Configuration Management

Environment-Based Configuration

type Config struct {
    // Server settings
    ListenAddr      string        `env:"LISTEN_ADDR" default:":8080"`
    
    // SoundTouch settings
    DeviceHosts     []string      `env:"DEVICE_HOSTS" separator:","`
    DiscoveryTimeout time.Duration `env:"DISCOVERY_TIMEOUT" default:"30s"`
    RequestTimeout   time.Duration `env:"REQUEST_TIMEOUT" default:"15s"`
    MaxRetries       int           `env:"MAX_RETRIES" default:"3"`
    
    // Connection pool
    MaxConnections   int           `env:"MAX_CONNECTIONS" default:"10"`
    IdleTimeout      time.Duration `env:"IDLE_TIMEOUT" default:"5m"`
    
    // Monitoring
    MetricsEnabled   bool          `env:"METRICS_ENABLED" default:"true"`
    HealthCheckInterval time.Duration `env:"HEALTH_CHECK_INTERVAL" default:"30s"`
    
    // Logging
    LogLevel        string         `env:"LOG_LEVEL" default:"info"`
    LogFormat       string         `env:"LOG_FORMAT" default:"json"`
    
    // Security
    EnableTLS       bool          `env:"ENABLE_TLS" default:"false"`
    TLSCertFile     string        `env:"TLS_CERT_FILE"`
    TLSKeyFile      string        `env:"TLS_KEY_FILE"`
}

func LoadConfig() (*Config, error) {
    cfg := &Config{}
    if err := env.Parse(cfg); err != nil {
        return nil, fmt.Errorf("failed to parse config: %w", err)
    }
    
    return cfg, cfg.Validate()
}

func (c *Config) Validate() error {
    if len(c.DeviceHosts) == 0 {
        return fmt.Errorf("at least one device host must be specified")
    }
    
    if c.RequestTimeout < time.Second {
        return fmt.Errorf("request timeout must be at least 1 second")
    }
    
    if c.EnableTLS && (c.TLSCertFile == "" || c.TLSKeyFile == "") {
        return fmt.Errorf("TLS cert and key files required when TLS is enabled")
    }
    
    return nil
}

Configuration File Support

# config/production.yaml
server:
  listen_addr: ":8080"
  enable_tls: true
  tls_cert_file: "/etc/ssl/certs/app.crt"
  tls_key_file: "/etc/ssl/private/app.key"

soundtouch:
  device_hosts:
    - "192.168.1.100"
    - "192.168.1.101"
  discovery_timeout: "30s"
  request_timeout: "15s"
  max_retries: 3

pool:
  max_connections: 20
  idle_timeout: "10m"

monitoring:
  metrics_enabled: true
  health_check_interval: "30s"
  
logging:
  level: "info"
  format: "json"
func LoadConfigFromFile(path string) (*Config, error) {
    data, err := os.ReadFile(path)
    if err != nil {
        return nil, err
    }
    
    var cfg Config
    if err := yaml.Unmarshal(data, &cfg); err != nil {
        return nil, err
    }
    
    return &cfg, cfg.Validate()
}

Security Network

Network Security

// Network configuration with security considerations
type SecureNetworkConfig struct {
    // Allowed source IP ranges
    AllowedCIDRs    []string
    
    // Rate limiting
    RateLimit       int
    RateLimitWindow time.Duration
    
    // TLS configuration
    TLSConfig       *tls.Config
    
    // Timeouts for security
    ReadTimeout     time.Duration
    WriteTimeout    time.Duration
    IdleTimeout     time.Duration
}

func NewSecureServer(config SecureNetworkConfig) *http.Server {
    mux := http.NewServeMux()
    
    // Add middleware
    handler := applyMiddleware(mux,
        corsMiddleware(),
        rateLimitMiddleware(config.RateLimit, config.RateLimitWindow),
        ipWhitelistMiddleware(config.AllowedCIDRs),
        loggingMiddleware(),
        metricsMiddleware(),
    )
    
    return &http.Server{
        Handler:      handler,
        TLSConfig:    config.TLSConfig,
        ReadTimeout:  config.ReadTimeout,
        WriteTimeout: config.WriteTimeout,
        IdleTimeout:  config.IdleTimeout,
    }
}

Input Validation

type DeviceControlRequest struct {
    DeviceID string `json:"device_id" validate:"required,uuid"`
    Action   string `json:"action" validate:"required,oneof=play pause stop"`
    Volume   *int   `json:"volume,omitempty" validate:"omitempty,min=0,max=100"`
    Source   string `json:"source,omitempty" validate:"omitempty,oneof=SPOTIFY BLUETOOTH AUX"`
}

func (r *DeviceControlRequest) Validate() error {
    validate := validator.New()
    if err := validate.Struct(r); err != nil {
        return fmt.Errorf("validation failed: %w", err)
    }
    
    // Additional business logic validation
    if r.Action == "volume" && r.Volume == nil {
        return fmt.Errorf("volume value required for volume action")
    }
    
    return nil
}

Secrets Management

// Use environment variables or secret management systems
type SecretsConfig struct {
    APIKeys map[string]string `env:"API_KEYS"`
    TLSCert string            `env:"TLS_CERT_PATH"`
    TLSKey  string            `env:"TLS_KEY_PATH"`
}

// For Kubernetes
func loadSecretsFromK8s() (*SecretsConfig, error) {
    // Read from mounted secret volumes
    tlsCert, err := os.ReadFile("/etc/secrets/tls.crt")
    if err != nil {
        return nil, err
    }
    
    tlsKey, err := os.ReadFile("/etc/secrets/tls.key")
    if err != nil {
        return nil, err
    }
    
    return &SecretsConfig{
        TLSCert: string(tlsCert),
        TLSKey:  string(tlsKey),
    }, nil
}

Monitoring Logging

Structured Logging

import (
    "github.com/sirupsen/logrus"
    "github.com/prometheus/client_golang/prometheus"
)

type Logger struct {
    *logrus.Logger
    deviceID string
    component string
}

func NewLogger(level, format, component string) (*Logger, error) {
    logger := logrus.New()
    
    // Set level
    logLevel, err := logrus.ParseLevel(level)
    if err != nil {
        return nil, err
    }
    logger.SetLevel(logLevel)
    
    // Set format
    if format == "json" {
        logger.SetFormatter(&logrus.JSONFormatter{
            TimestampFormat: time.RFC3339,
        })
    }
    
    return &Logger{
        Logger:    logger,
        component: component,
    }, nil
}

func (l *Logger) WithDevice(deviceID string) *logrus.Entry {
    return l.WithFields(logrus.Fields{
        "component": l.component,
        "device_id": deviceID,
    })
}

func (l *Logger) WithError(err error) *logrus.Entry {
    return l.WithField("error", err.Error())
}

Metrics Collection

type Metrics struct {
    // Request metrics
    RequestsTotal     prometheus.CounterVec
    RequestDuration   prometheus.HistogramVec
    RequestsInFlight  prometheus.GaugeVec
    
    // Device metrics
    DevicesConnected  prometheus.Gauge
    DeviceHealth      prometheus.GaugeVec
    WebSocketConnections prometheus.Gauge
    
    // Error metrics
    ErrorsTotal       prometheus.CounterVec
    
    // Business metrics
    VolumeChanges     prometheus.CounterVec
    SourceChanges     prometheus.CounterVec
    ZoneOperations    prometheus.CounterVec
}

func NewMetrics() *Metrics {
    m := &Metrics{
        RequestsTotal: *prometheus.NewCounterVec(
            prometheus.CounterOpts{
                Name: "soundtouch_requests_total",
                Help: "Total number of requests processed",
            },
            []string{"method", "endpoint", "status"},
        ),
        
        RequestDuration: *prometheus.NewHistogramVec(
            prometheus.HistogramOpts{
                Name: "soundtouch_request_duration_seconds",
                Help: "Request duration in seconds",
                Buckets: prometheus.DefBuckets,
            },
            []string{"method", "endpoint"},
        ),
        
        DevicesConnected: prometheus.NewGauge(
            prometheus.GaugeOpts{
                Name: "soundtouch_devices_connected",
                Help: "Number of connected devices",
            },
        ),
        
        DeviceHealth: *prometheus.NewGaugeVec(
            prometheus.GaugeOpts{
                Name: "soundtouch_device_health",
                Help: "Device health status (1=healthy, 0=unhealthy)",
            },
            []string{"device_id", "device_name"},
        ),
    }
    
    // Register metrics
    prometheus.MustRegister(
        m.RequestsTotal,
        m.RequestDuration,
        m.DevicesConnected,
        m.DeviceHealth,
    )
    
    return m
}

func (m *Metrics) RecordRequest(method, endpoint string, duration time.Duration, status int) {
    m.RequestsTotal.WithLabelValues(method, endpoint, fmt.Sprintf("%d", status)).Inc()
    m.RequestDuration.WithLabelValues(method, endpoint).Observe(duration.Seconds())
}

Health Checks

type HealthChecker struct {
    manager     *MultiDeviceManager
    interval    time.Duration
    timeout     time.Duration
    metrics     *Metrics
    logger      *Logger
}

func (hc *HealthChecker) Start(ctx context.Context) {
    ticker := time.NewTicker(hc.interval)
    defer ticker.Stop()
    
    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            hc.checkAllDevices()
        }
    }
}

func (hc *HealthChecker) checkAllDevices() {
    var wg sync.WaitGroup
    
    for deviceID, device := range hc.manager.devices {
        wg.Add(1)
        go func(id string, dev *DeviceInfo) {
            defer wg.Done()
            hc.checkDevice(id, dev)
        }(deviceID, device)
    }
    
    wg.Wait()
}

func (hc *HealthChecker) checkDevice(deviceID string, device *DeviceInfo) {
    ctx, cancel := context.WithTimeout(context.Background(), hc.timeout)
    defer cancel()
    
    start := time.Now()
    err := device.Client.Ping()
    duration := time.Since(start)
    
    if err != nil {
        device.Status = DeviceStatusUnhealthy
        hc.metrics.DeviceHealth.WithLabelValues(deviceID, device.Name).Set(0)
        hc.logger.WithDevice(deviceID).WithError(err).Error("Device health check failed")
    } else {
        device.Status = DeviceStatusHealthy
        device.LastSeen = time.Now()
        hc.metrics.DeviceHealth.WithLabelValues(deviceID, device.Name).Set(1)
        hc.logger.WithDevice(deviceID).WithField("duration", duration).Debug("Device health check passed")
    }
}

// HTTP health endpoint
func (hc *HealthChecker) HealthHandler() http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        healthy := 0
        total := 0
        
        for _, device := range hc.manager.devices {
            total++
            if device.Status == DeviceStatusHealthy {
                healthy++
            }
        }
        
        status := map[string]interface{}{
            "status": "ok",
            "devices": map[string]interface{}{
                "total":   total,
                "healthy": healthy,
                "unhealthy": total - healthy,
            },
            "timestamp": time.Now().UTC(),
        }
        
        w.Header().Set("Content-Type", "application/json")
        
        if healthy < total {
            w.WriteHeader(http.StatusServiceUnavailable)
            status["status"] = "degraded"
        }
        
        json.NewEncoder(w).Encode(status)
    }
}

Performance Optimization

Connection Pooling

type ConnectionPool struct {
    clients       sync.Map
    maxIdle       int
    maxActive     int
    idleTimeout   time.Duration
    activeCount   int64
    metrics       *Metrics
    mu            sync.RWMutex
}

func NewConnectionPool(maxIdle, maxActive int, idleTimeout time.Duration) *ConnectionPool {
    cp := &ConnectionPool{
        maxIdle:     maxIdle,
        maxActive:   maxActive,
        idleTimeout: idleTimeout,
    }
    
    // Start cleanup goroutine
    go cp.cleanup()
    
    return cp
}

func (cp *ConnectionPool) Get(host string, port int) (*client.Client, error) {
    key := fmt.Sprintf("%s:%d", host, port)
    
    // Check if connection exists and is valid
    if val, ok := cp.clients.Load(key); ok {
        conn := val.(*pooledConnection)
        if time.Since(conn.lastUsed) < cp.idleTimeout {
            conn.lastUsed = time.Now()
            return conn.client, nil
        }
        // Connection expired, remove it
        cp.clients.Delete(key)
    }
    
    // Check active connection limit
    if atomic.LoadInt64(&cp.activeCount) >= int64(cp.maxActive) {
        return nil, fmt.Errorf("connection pool exhausted")
    }
    
    // Create new connection
    config := client.ClientConfig{
        Host:    host,
        Port:    port,
        Timeout: 15 * time.Second,
    }
    
    newClient := client.NewClient(config)
    
    // Test connection
    if err := newClient.Ping(); err != nil {
        return nil, fmt.Errorf("failed to connect to %s:%d: %w", host, port, err)
    }
    
    conn := &pooledConnection{
        client:   newClient,
        lastUsed: time.Now(),
        created:  time.Now(),
    }
    
    cp.clients.Store(key, conn)
    atomic.AddInt64(&cp.activeCount, 1)
    
    return newClient, nil
}

type pooledConnection struct {
    client   *client.Client
    lastUsed time.Time
    created  time.Time
}

func (cp *ConnectionPool) cleanup() {
    ticker := time.NewTicker(cp.idleTimeout / 2)
    defer ticker.Stop()
    
    for range ticker.C {
        now := time.Now()
        cp.clients.Range(func(key, val interface{}) bool {
            conn := val.(*pooledConnection)
            if now.Sub(conn.lastUsed) > cp.idleTimeout {
                cp.clients.Delete(key)
                atomic.AddInt64(&cp.activeCount, -1)
            }
            return true
        })
    }
}

Caching Strategy

type CacheManager struct {
    deviceInfoCache   *cache.Cache
    capabilitiesCache *cache.Cache
    volumeCache       *cache.Cache
}

func NewCacheManager() *CacheManager {
    return &CacheManager{
        // Device info rarely changes, cache for 1 hour
        deviceInfoCache: cache.New(1*time.Hour, 2*time.Hour),
        
        // Capabilities never change, cache for 24 hours
        capabilitiesCache: cache.New(24*time.Hour, 48*time.Hour),
        
        // Volume changes frequently, cache for 5 seconds
        volumeCache: cache.New(5*time.Second, 10*time.Second),
    }
}

func (cm *CacheManager) GetDeviceInfo(deviceID string, fetcher func() (*models.DeviceInfo, error)) (*models.DeviceInfo, error) {
    if cached, found := cm.deviceInfoCache.Get(deviceID); found {
        return cached.(*models.DeviceInfo), nil
    }
    
    info, err := fetcher()
    if err != nil {
        return nil, err
    }
    
    cm.deviceInfoCache.Set(deviceID, info, cache.DefaultExpiration)
    return info, nil
}

Error Handling Recovery

Circuit Breaker Pattern

import "github.com/sony/gobreaker"

type ResilientSoundTouchService struct {
    client  *client.Client
    cb      *gobreaker.CircuitBreaker
    metrics *Metrics
}

func NewResilientSoundTouchService(client *client.Client) *ResilientSoundTouchService {
    settings := gobreaker.Settings{
        Name:        "soundtouch",
        MaxRequests: 3,
        Interval:    10 * time.Second,
        Timeout:     30 * time.Second,
        ReadyToTrip: func(counts gobreaker.Counts) bool {
            failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
            return counts.Requests >= 3 && failureRatio >= 0.6
        },
        OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {
            log.Printf("Circuit breaker '%s' changed from '%s' to '%s'", name, from, to)
        },
    }
    
    return &ResilientSoundTouchService{
        client: client,
        cb:     gobreaker.NewCircuitBreaker(settings),
    }
}

func (r *ResilientSoundTouchService) SetVolume(deviceID string, volume int) error {
    result, err := r.cb.Execute(func() (interface{}, error) {
        return nil, r.client.SetVolume(volume)
    })
    
    if err != nil {
        r.metrics.ErrorsTotal.WithLabelValues("circuit_breaker", "volume").Inc()
        return err
    }
    
    return result.(error)
}

Graceful Shutdown

func (app *Application) Run(ctx context.Context) error {
    // Setup signal handling
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
    
    // Start services
    g, ctx := errgroup.WithContext(ctx)
    
    // HTTP server
    server := &http.Server{
        Addr:    app.config.ListenAddr,
        Handler: app.handler,
    }
    
    g.Go(func() error {
        app.logger.Info("Starting HTTP server", "addr", app.config.ListenAddr)
        if err := server.ListenAndServe(); err != http.ErrServerClosed {
            return err
        }
        return nil
    })
    
    // Health checker
    g.Go(func() error {
        return app.healthChecker.Start(ctx)
    })
    
    // WebSocket manager
    g.Go(func() error {
        return app.wsManager.Start(ctx)
    })
    
    // Wait for shutdown signal
    go func() {
        <-sigChan
        app.logger.Info("Shutdown signal received")
        
        // Graceful shutdown with timeout
        shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
        defer cancel()
        
        // Shutdown HTTP server
        if err := server.Shutdown(shutdownCtx); err != nil {
            app.logger.Error("HTTP server shutdown error", "error", err)
        }
        
        // Close WebSocket connections
        app.wsManager.Shutdown(shutdownCtx)
        
        // Close connection pool
        app.connectionPool.Close()
    }()
    
    return g.Wait()
}

Deployment Strategies

Docker Deployment

# Dockerfile
FROM golang:1.25-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/

COPY --from=builder /app/main .
COPY --from=builder /app/config ./config

EXPOSE 8080
CMD ["./main"]
# docker-compose.yml
version: '3.8'

services:
  soundtouch-app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - DEVICE_HOSTS=192.168.1.100,192.168.1.101
      - LOG_LEVEL=info
      - METRICS_ENABLED=true
    volumes:
      - ./config:/app/config:ro
      - ./logs:/app/logs
    networks:
      - soundtouch-net
    restart: unless-stopped
    
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    networks:
      - soundtouch-net
      
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-storage:/var/lib/grafana
    networks:
      - soundtouch-net

networks:
  soundtouch-net:

volumes:
  grafana-storage:

Kubernetes Deployment

# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: soundtouch-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: soundtouch-app
  template:
    metadata:
      labels:
        app: soundtouch-app
    spec:
      containers:
      - name: soundtouch-app
        image: your-repo/soundtouch-app:latest
        ports:
        - containerPort: 8080
        env:
        - name: DEVICE_HOSTS
          valueFrom:
            configMapKeyRef:
              name: soundtouch-config
              key: device_hosts
        - name: LOG_LEVEL
          value: "info"
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: soundtouch-service
spec:
  selector:
    app: soundtouch-app
  ports:
  - port: 80
    targetPort: 8080
  type: LoadBalancer

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: soundtouch-config
data:
  device_hosts: "192.168.1.100,192.168.1.101,192.168.1.102"

Systemd Service

A standard systemd unit for manual installation. This example assumes the binary is at /usr/local/bin/soundtouch-service and data is stored in /var/lib/soundtouch-service.

# /etc/systemd/system/soundtouch-service.service
[Unit]
Description=Bose SoundTouch Service
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=soundtouch
Group=soundtouch
WorkingDirectory=/var/lib/soundtouch-service
ExecStart=/usr/local/bin/soundtouch-service
Environment=PORT=80
Environment=SERVER_URL=http://soundtouch.local

# Allow binding to privileged ports (80/443) without running as root
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

Restart=on-failure
RestartSec=5

# Security hardening
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/lib/soundtouch-service

[Install]
WantedBy=multi-user.target

Raspberry Pi Installer

For users deploying on a Raspberry Pi, we provide a specialized automated installer that handles everything from architecture detection to security hardening.

See the Raspberry Pi Installation Guide for step-by-step instructions.


Maintenance Operations

Log Rotation

# /etc/logrotate.d/soundtouch
/opt/soundtouch/logs/*.log {
    daily
    missingok
    rotate 14
    compress
    delaycompress
    notifempty
    create 0644 soundtouch soundtouch
    postrotate
        /bin/kill -HUP `cat /var/run/soundtouch.pid 2>/dev/null` 2>/dev/null || true
    endscript
}

Monitoring Alerts

# Prometheus alerts
groups:
- name: soundtouch
  rules:
  - alert: DeviceUnhealthy
    expr: soundtouch_device_health == 0
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "SoundTouch device  is unhealthy"
      description: "Device  has been unhealthy for more than 2 minutes"
      
  - alert: HighErrorRate
    expr: rate(soundtouch_errors_total[5m]) > 0.1
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "High error rate detected"
      description: "Error rate is  errors/second over the last 5 minutes"
      
  - alert: ServiceDown
    expr: up{job="soundtouch"} == 0
    for: 1m
    labels:
      severity: critical
    annotations:
      summary: "SoundTouch service is down"
      description: "SoundTouch service has been down for more than 1 minute"

Backup Strategy

// Backup device configurations
func (m *Manager) BackupConfigurations() error {
    backup := ConfigBackup{
        Timestamp: time.Now(),
        Devices:   make(map[string]DeviceConfig),
    }
    
    for deviceID, device := range m.devices {
        config := DeviceConfig{}
        
        // Backup presets
        if presets, err := device.Client.GetPresets(); err == nil {
            config.Presets = presets
        }
        
        // Backup settings
        if volume, err := device.Client.GetVolume(); err == nil {
            config.Volume = volume.TargetVolume
        }
        
        if bass, err := device.Client.GetBass(); err == nil {
            config.Bass = bass.TargetBass
        }
        
        backup.Devices[deviceID] = config
    }
    
    // Save to file
    data, err := json.MarshalIndent(backup, "", "  ")
    if err != nil {
        return err
    }
    
    filename := fmt.Sprintf("backup_%s.json", time.Now().Format("2006-01-02_15-04-05"))
    return os.WriteFile(filepath.Join(m.config.BackupDir, filename), data, 0644)
}

Performance Tuning

```go // Tune Go runtime for production func init() { // Set GOMAXPROCS based on container limits if not set if os.Getenv(“GOMAXPROCS”) == “” { if limit := getCgroupCPULimit(); limit > 0 { runtime.GOMAXPROCS(int(limit)) } }

// Set GC target percentage
if os.Getenv("GOGC") == "" {
    debug.SetGCPerc