Controlling Server Hardware With Go
Using IPMI and Go to never leave the couch!
Intelligent Platform Management Interface (IPMI) is built into most servers to allow remote management, generally for use when you're not in the same physical location of the server. In 2020, I purchased 4 used servers off of Craigslist to set up my own home lab. One of these servers runs a plex media server which hosts a variety of media that I can stream to my TV or even my phone when I'm away.
My home lab is less than 100 steps from my couch and TV, where I'm writing this article at, and yet I decided that was too far to walk to turn my server on and off. Enter IPMI. I discovered my Supermicro server had 2 IP addresses, one I knew was used to connect via SSH and exposed the plex media server but the other was a mystery. This second IP address stayed online even when the server was turned off which lead me to investigate what was going on there. Visiting the IP address in the browser showed a login page and after entering some default credentials I discovered it was an IPMI.
After seeing that I could turn my server on and off through a webpage, I decided that I didn't want to log in and deal with the ancient Java application that was running with many, many iframes. I decided to write a program to be able to do this from the command line and eventually integrate it into my own mobile app. Unsurprisingly, there was already a Go IPMI library written by VMware. After creating an IPMI connection, the code to execute a command against the server is less than 20 lines of code.
package iipmi
import (
"github.com/cenkalti/backoff/v4"
"github.com/vmware/goipmi"
)
func Execute(conn *ipmi.Connection, ctl ipmi.ChassisControl) error {
client, err := ipmi.NewClient(conn)
if err != nil {
return err
}
if err := client.Open(); err != nil {
return err
}
defer client.Close()
return backoff.Retry(func() error { return client.Control(ctl) }, backoff.NewExponentialBackOff())
}
I added exponential backoff retry logic to help with spotty connections or IPMI being, well, IPMI. Setting up the IPMI connection is just as simple:
package cmd
import (
"github.com/spf13/cobra"
ipmi "github.com/vmware/goipmi"
"ipmi-grpc/pkg/iipmi"
)
var startupCmd = &cobra.Command{
Use: "startup",
RunE: func(cmd *cobra.Command, args []string) error {
conn := &ipmi.Connection{Interface: "lan"}
conn.Path, _ = cmd.Flags().GetString("path")
conn.Hostname, _ = cmd.Flags().GetString("hostname")
conn.Port, _ = cmd.Flags().GetInt("port")
conn.Username, _ = cmd.Flags().GetString("username")
conn.Password, _ = cmd.Flags().GetString("password")
return iipmi.Execute(conn, ipmi.ControlPowerUp)
},
}
func init() {
startupCmd.Flags().String("path", "", "IPMI path")
startupCmd.Flags().String("hostname", "", "IPMI hostname")
startupCmd.Flags().Int("port", 623, "IPMI port")
startupCmd.Flags().String("username", "ADMIN", "IPMI username")
startupCmd.Flags().String("password", "ADMIN", "IPMI password")
_ = deviceIdCmd.MarkFlagRequired("hostname")
rootCmd.AddCommand(startupCmd)
}
This is the cobra command to start up the server with a simple ./ipmi-grpc startup --hostname 192.168.37.113
.
I used this as an excuse to write something in Go and learn more about IPMI. Hardware meeting software is one of the most interesting parts of programming to me. When I run this command and the fans start spinning in my office it brings a smile to my face knowing that I did that using code.