Skip to content
59 changes: 59 additions & 0 deletions addons/apcupsd/addon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package apcupsd

import (
"fmt"

"github.com/rocket-pool/smartnode/shared/types/addons"
cfgtypes "github.com/rocket-pool/smartnode/shared/types/config"
)

const (
ContainerID_Apcupsd cfgtypes.ContainerID = "apcupsd"
ContainerID_ApcupsdExporter cfgtypes.ContainerID = "apcupsd_exporter"
ApcupsdContainerName string = "addon_apcupsd"
ApcupsdNetworkComposeTemplateName string = "addon_apcupsd.network"
ApcupsdContainerComposeTemplateName string = "addon_apcupsd.container"
ApcupsdConfigTemplateName string = "addon_apcupsd_config"
ApcupsdConfigName string = "addon_apcupsd.conf"
)

type Apcupsd struct {
cfg *ApcupsdConfig `yaml:"config,omitempty"`
}

func NewApcupsd() addons.SmartnodeAddon {
return &Apcupsd{
cfg: NewConfig(),
}
}

func (apcupsd *Apcupsd) GetName() string {
return "APCUPS Monitor"
}

func (apcupsd *Apcupsd) GetDescription() string {
return "This addon adds UPS monitoring to your node so you can monitor the status of your APCUPSD compatible UPS within grafana \n\nMade with love by killjoy.eth."
}

func (apcupsd *Apcupsd) GetConfig() cfgtypes.Config {
return apcupsd.cfg
}

func (apcupsd *Apcupsd) GetContainerName() string {
return fmt.Sprint(ContainerID_Apcupsd)
}

func (apcupsd *Apcupsd) GetEnabledParameter() *cfgtypes.Parameter {
return &apcupsd.cfg.Enabled
}

func (apcupsd *Apcupsd) GetContainerTag() string {
return containerTag
}

func (apcupsd *Apcupsd) UpdateEnvVars(envVars map[string]string) error {
if apcupsd.cfg.Enabled.Value == true {
cfgtypes.AddParametersToEnvVars(apcupsd.cfg.GetParameters(), envVars)
}
return nil
}
136 changes: 136 additions & 0 deletions addons/apcupsd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package apcupsd

import (
"github.com/rocket-pool/smartnode/shared/types/config"
)

// Constants
const (
containerTag string = "gersilex/apcupsd:v1.0.0"
exporterContainerTag string = "jangrewe/apcupsd-exporter:latest"
)

type Mode string

const (
Mode_Network Mode = "network"
Mode_Container Mode = "docker"
)

// Configuration for the Graffiti Wall Writer
type ApcupsdConfig struct {
Title string `yaml:"-"`

Enabled config.Parameter `yaml:"enabled,omitempty"`
ApcupsdContainerTag config.Parameter `yaml:"apcupsdContainerTag,omitempty"`
ApcupsdExporterContainerTag config.Parameter `yaml:"apcupsdExporterContainerTag,omitempty"`
MetricsPort config.Parameter `yaml:"metricsPort,omitempty"`
MountPoint config.Parameter `yaml:"mountPoint,omitempty"`
Mode config.Parameter `yaml:"mode,omitempty"`
NetworkAddress config.Parameter `yaml:"NetworkAddress,omitempty"`
}

// Creates a new configuration instance
func NewConfig() *ApcupsdConfig {
return &ApcupsdConfig{
Title: "APCUPSD Settings",

Enabled: config.Parameter{
ID: "enabled",
Name: "Enabled",
Description: "Enable APCUPSD monitoring",
Type: config.ParameterType_Bool,
Default: map[config.Network]interface{}{config.Network_All: false},
AffectsContainers: []config.ContainerID{ContainerID_Apcupsd, ContainerID_ApcupsdExporter},
EnvironmentVariables: []string{"ADDON_APCUPSD_ENABLED"},
CanBeBlank: false,
OverwriteOnUpgrade: false,
},
Mode: config.Parameter{
ID: "mode",
Name: "Mode",
Description: "How would you like to run APCUPSD?\n Select `Container` if you'd like smart node to run apcupsd inside a container for you.\nSelect `network` mode if you want to connect to an instance of apcupsd running on your host machine or on your network.",
Type: config.ParameterType_Choice,
Default: map[config.Network]interface{}{config.Network_All: Mode_Container},
AffectsContainers: []config.ContainerID{ContainerID_Apcupsd, ContainerID_ApcupsdExporter},
EnvironmentVariables: []string{},
CanBeBlank: false,
OverwriteOnUpgrade: false,
Options: []config.ParameterOption{{
Name: "Container",
Description: "Let the smart node run APCUPSD inside a container for you",
Value: Mode_Container,
}, {
Name: "Network",
Description: "Connect the APCUPSD exporter to an instance of APCUSD running on your host machine or on your network",
Value: Mode_Network,
}},
},
ApcupsdContainerTag: config.Parameter{
ID: "containerTag",
Name: "APCUPSD Container Tag",
Description: "The container tag name of the APCUPSD container.",
Type: config.ParameterType_String,
Default: map[config.Network]interface{}{config.Network_All: containerTag},
AffectsContainers: []config.ContainerID{ContainerID_Apcupsd},
EnvironmentVariables: []string{"ADDON_APCUPSD_CONTAINER_TAG"},
CanBeBlank: false,
OverwriteOnUpgrade: true,
},
ApcupsdExporterContainerTag: config.Parameter{
ID: "exporterContainerTag",
Name: "APCUPSD Exporter Container Tag",
Description: "The container tag name of the APCUPSD Prometheus Exporter.",
Type: config.ParameterType_String,
Default: map[config.Network]interface{}{config.Network_All: exporterContainerTag},
AffectsContainers: []config.ContainerID{ContainerID_ApcupsdExporter},
EnvironmentVariables: []string{"ADDON_APCUPSD_EXPORTER_CONTAINER_TAG"},
CanBeBlank: false,
OverwriteOnUpgrade: true,
},
MetricsPort: config.Parameter{
ID: "metricsPort",
Name: "APCUPSD Exporter Metrics Port",
Description: "The port the exporter should use to provide metrics to prometheus.",
Type: config.ParameterType_String,
Default: map[config.Network]interface{}{config.Network_All: "9162"},
AffectsContainers: []config.ContainerID{ContainerID_ApcupsdExporter, config.ContainerID_Prometheus},
EnvironmentVariables: []string{"ADDON_APCUPSD_METRICS_PORT"},
CanBeBlank: false,
OverwriteOnUpgrade: false,
},
MountPoint: config.Parameter{
ID: "mountPoint",
Name: "APC USB Mount Location",
Description: "The USB mount point for your APC device. This must be set correctly for the container to read data from your UPC. To determine the mount point on your system:\n1. Unplug the USB cable of your UPS and plug it back in.\n2. When your server detects the device an entry will show up when you run `sudo dmesg | grep usb`.\n3. Identify the mount point for your UPS. Often it is named `hiddev*` e.g. `hiddev0`,`hiddev1`... but may vary depending on how many peripherals you have connected.\n4. Verify the mount point for your distribution. Often this maps to `/dev/usb/hiddev*`\nThis is the value to enter in the field below. NOTE: If you reconnect your UPC this value may need to be updated.",
Type: config.ParameterType_String,
Default: map[config.Network]interface{}{config.Network_All: "/dev/usb/hiddev0"},
AffectsContainers: []config.ContainerID{ContainerID_Apcupsd},
EnvironmentVariables: []string{"ADDON_APCUPSD_MOUNT_POINT"},
CanBeBlank: false,
OverwriteOnUpgrade: false,
},
NetworkAddress: config.Parameter{
ID: "networkAddress",
Name: "APCUPSD Network Address",
Description: "The network address and port that should be used to connect to APCUPSD.\nIf you have apcupsd installed on your host you should use the default host.docker.internal:3551.",
Type: config.ParameterType_String,
Default: map[config.Network]interface{}{config.Network_All: "host.docker.internal:3551"},
AffectsContainers: []config.ContainerID{ContainerID_ApcupsdExporter},
EnvironmentVariables: []string{"ADDON_APCUPSD_NETWORK_ADDRESS"},
CanBeBlank: false,
OverwriteOnUpgrade: false,
},
}
}

// Get the parameters for this config
func (cfg *ApcupsdConfig) GetParameters() []*config.Parameter {
return []*config.Parameter{&cfg.Enabled, &cfg.Mode, &cfg.ApcupsdExporterContainerTag, &cfg.ApcupsdContainerTag, &cfg.MetricsPort, &cfg.MountPoint, &cfg.NetworkAddress}

}

// The the title for the config
func (cfg *ApcupsdConfig) GetConfigTitle() string {
return cfg.Title
}
5 changes: 5 additions & 0 deletions addons/constructors.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package addons

import (
"github.com/rocket-pool/smartnode/addons/apcupsd"
"github.com/rocket-pool/smartnode/addons/graffiti_wall_writer"
"github.com/rocket-pool/smartnode/shared/types/addons"
)

func NewGraffitiWallWriter() addons.SmartnodeAddon {
return graffiti_wall_writer.NewGraffitiWallWriter()
}

func NewApcupsd() addons.SmartnodeAddon {
return apcupsd.NewApcupsd()
}
174 changes: 174 additions & 0 deletions rocketpool-cli/service/config/addon-apcupsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package config

import (
"fmt"

"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"github.com/rocket-pool/smartnode/shared/services/config"
"github.com/rocket-pool/smartnode/shared/types/addons"
)

// The page wrapper for the APCUPSD addon config
type AddonApcupsdPage struct {
addonsPage *AddonsPage
page *page
layout *standardLayout
masterConfig *config.RocketPoolConfig
addon addons.SmartnodeAddon
enabledBox *parameterizedFormItem
modeBox *parameterizedFormItem
exporterImage *parameterizedFormItem
apcupsdImage *parameterizedFormItem
mountPoint *parameterizedFormItem
metricsPort *parameterizedFormItem
apcupsdAddress *parameterizedFormItem
}

// Creates a new page for the APCUPSD addon settings
func NewAddonApcupsdPage(addonsPage *AddonsPage, addon addons.SmartnodeAddon) *AddonApcupsdPage {

configPage := &AddonApcupsdPage{
addonsPage: addonsPage,
masterConfig: addonsPage.home.md.Config,
addon: addon,
}
configPage.createContent()

configPage.page = newPage(
addonsPage.page,
"settings-addon-apcupsd",
addon.GetName(),
addon.GetDescription(),
configPage.layout.grid,
)

return configPage

}

// Get the underlying page
func (configPage *AddonApcupsdPage) getPage() *page {
return configPage.page
}

// Creates the content for the APCUPSD settings page
func (configPage *AddonApcupsdPage) createContent() {

// Create the layout
configPage.layout = newStandardLayout()
configPage.layout.createForm(&configPage.masterConfig.Smartnode.Network, fmt.Sprintf("%s Settings", configPage.addon.GetName()))

// Return to the home page after pressing Escape
configPage.layout.form.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
if event.Key() == tcell.KeyEsc {
// Close all dropdowns and break if one was open
for _, param := range configPage.layout.parameters {
dropDown, ok := param.item.(*DropDown)
if ok && dropDown.open {
dropDown.CloseList(configPage.addonsPage.home.md.app)
return nil
}
}

// Return to the home page
configPage.addonsPage.home.md.setPage(configPage.addonsPage.page)
return nil
}
return event
})

// Get the parameters
enabledParam := configPage.addon.GetEnabledParameter()
// TODO: Don't like how I reference these by index here. Is there a better way?
modeParam := configPage.addon.GetConfig().GetParameters()[1]
exporterImageParam := configPage.addon.GetConfig().GetParameters()[2]
apcupsdImageParam := configPage.addon.GetConfig().GetParameters()[3]
metricsPortParam := configPage.addon.GetConfig().GetParameters()[4]
mountPointParam := configPage.addon.GetConfig().GetParameters()[5]
networkAddress := configPage.addon.GetConfig().GetParameters()[6]

// Set up the form items
configPage.enabledBox = createParameterizedCheckbox(enabledParam)
configPage.modeBox = createParameterizedDropDown(modeParam, configPage.layout.descriptionBox)
configPage.exporterImage = createParameterizedStringField(exporterImageParam)
configPage.apcupsdImage = createParameterizedStringField(apcupsdImageParam)
configPage.metricsPort = createParameterizedStringField(metricsPortParam)
configPage.mountPoint = createParameterizedStringField(mountPointParam)
configPage.apcupsdAddress = createParameterizedStringField(networkAddress)

// Map the parameters to the form items in the layout
configPage.layout.mapParameterizedFormItems(configPage.enabledBox)
configPage.layout.mapParameterizedFormItems(configPage.modeBox)
configPage.layout.mapParameterizedFormItems(configPage.exporterImage)
configPage.layout.mapParameterizedFormItems(configPage.apcupsdImage)
configPage.layout.mapParameterizedFormItems(configPage.metricsPort)
configPage.layout.mapParameterizedFormItems(configPage.mountPoint)
configPage.layout.mapParameterizedFormItems(configPage.apcupsdAddress)

// Set up the setting callbacks
configPage.enabledBox.item.(*tview.Checkbox).SetChangedFunc(func(checked bool) {
if enabledParam.Value == checked {
return
}
enabledParam.Value = checked
configPage.handleEnableChanged()
})
configPage.modeBox.item.(*DropDown).SetSelectedFunc(func(text string, index int) {
if configPage.modeBox.parameter.Value == configPage.modeBox.parameter.Options[index].Value {
return
}
configPage.modeBox.parameter.Value = configPage.modeBox.parameter.Options[index].Value
configPage.handleModeChanged()
})

// Do the initial draw
configPage.handleDraw()

}

// Handle all of the form changes when the Enabled box has changed
func (configPage *AddonApcupsdPage) handleEnableChanged() {
configPage.handleDraw()
}

// Handle all of the form changes when the Mode box has changed
func (configPage *AddonApcupsdPage) handleModeChanged() {
configPage.handleDraw()
}

func (configPage *AddonApcupsdPage) handleDraw() {
configPage.layout.form.Clear(true)
configPage.layout.form.AddFormItem(configPage.enabledBox.item)

// Only add the supporting stuff if addon is enabled
if configPage.addon.GetEnabledParameter().Value == false {
return
}
configPage.addCommonFields()
if configPage.modeBox.parameter.Value == configPage.modeBox.parameter.Options[0].Value {
configPage.addContainerFields()
} else {
configPage.addNetworkFields()
}
configPage.layout.refresh()
}

func (configPage *AddonApcupsdPage) addCommonFields() {
configPage.layout.form.AddFormItem(configPage.modeBox.item)
configPage.layout.form.AddFormItem(configPage.exporterImage.item)
configPage.layout.form.AddFormItem(configPage.metricsPort.item)
}
func (configPage *AddonApcupsdPage) addContainerFields() {
configPage.layout.form.AddFormItem(configPage.apcupsdImage.item)
configPage.layout.form.AddFormItem(configPage.mountPoint.item)
}

func (configPage *AddonApcupsdPage) addNetworkFields() {
configPage.layout.form.AddFormItem(configPage.apcupsdAddress.item)
}

// Handle a bulk redraw request
func (configPage *AddonApcupsdPage) handleLayoutChanged() {
configPage.handleEnableChanged()
}
Loading