Commit f9a9a9ef authored by ale's avatar ale

autodetect service reload command

It should work with Debian wheezy (sysvinit) and jessie (systemd) without having to pass a reload command as a command-line flag.
parent d7458ef3
......@@ -3,6 +3,7 @@ package node
import (
"encoding/xml"
"errors"
"flag"
"fmt"
"io"
"log"
......@@ -18,21 +19,63 @@ import (
"git.autistici.org/ale/autoradio/util"
)
// Managing the local Icecast instance is a less simple task than it
// might look, as the details tend to be distribution-specific. We'd
// like to avoid implementing yet another service manager within
// radiod, so we are just going to assume that the Icecast daemon is
// managed using the local distribution-specific tools, and is running
// independently of radiod. Furthermore, we're going to make the
// following assumptions:
//
// - We have permissions to modify the Icecast config file;
//
// - We have installed our status XSLT page;
//
// - We can reload the Icecast service (send the icecast2 process a
// SIGHUP). Ideally we'd like to use the distribution-specific method,
// but with systemd we need to use "sudo". The code tries to
// autodetect a supported mechanism.
//
// The above is usually accomplished by running radiod as the same
// user that is running the Icecast daemon.
//
var (
statusPage = "/status-autoradio.xsl"
icecastConfigFile = "/etc/icecast2/icecast.xml"
icecastReloadCmd = "/usr/sbin/service icecast2 reload || /usr/sbin/service icecast2 start"
icecastConfigFile = flag.String("icecast-config", "/etc/icecast2/icecast.xml", "Icecast configuration file")
icecastCustomReloadCmd = flag.String("icecast-reload-command", "", "Command to reload / restart the icecast2 daemon")
icecastOk = instrumentation.NewGauge("icecast.ok")
icecastStatusPage = "/status-autoradio.xsl"
icecastOk = instrumentation.NewGauge("icecast.ok")
)
const (
systemCtlReloadCmd = "sudo systemctl reload icecast2.service || sudo systemctl restart icecast2.service"
debianReloadCmd = "/usr/sbin/service icecast2 reload || /usr/sbin/service icecast2 restart"
genericReloadCmd = "pkill -HUP icecast2"
)
func getIcecastReloadCmd() string {
if *icecastCustomReloadCmd != "" {
return *icecastCustomReloadCmd
}
if _, err := os.Stat("/bin/systemctl"); err == nil {
log.Printf("using /bin/systemctl to reload icecast2")
return systemCtlReloadCmd
}
if _, err := os.Stat("/usr/sbin/service"); err == nil {
log.Printf("using /usr/sbin/service to reload icecast2")
return debianReloadCmd
}
log.Printf("using pkill to reload icecast2")
return genericReloadCmd
}
// Icecast returns empty fields in our status handler, which we'll
// need to turn into integers (the xml unmarshaler will return an
// error in this specific case), so we use a separate type for
// decoding the status page output. This would be much simpler if I
// knew how to get the XSLT to put a default value in the output
// instead of an empty field...
type icecastMountStatusUnparsed struct {
type icecastMountStatusRaw struct {
Name string `xml:"name,attr"`
Listeners string `xml:"listeners"`
BitRate string `xml:"bitrate"`
......@@ -42,9 +85,9 @@ type icecastMountStatusUnparsed struct {
FrameRate string `xml:"frame-rate"`
}
type icecastStatusUnparsed struct {
XMLName xml.Name `xml:"status"`
Mounts []icecastMountStatusUnparsed `xml:"mount"`
type icecastStatusRaw struct {
XMLName xml.Name `xml:"status"`
Mounts []icecastMountStatusRaw `xml:"mount"`
}
type icecastStatus struct {
......@@ -53,15 +96,17 @@ type icecastStatus struct {
}
type icecastController struct {
config *icecastConfig
status *icecastStatus
stop chan bool
config *icecastConfig
status *icecastStatus
reloadCmd string
stop chan bool
}
func newIcecastController(publicIP string, maxClients int) *icecastController {
return &icecastController{
config: newIcecastConfig(publicIP, maxClients),
status: &icecastStatus{},
config: newIcecastConfig(publicIP, maxClients),
status: &icecastStatus{},
reloadCmd: getIcecastReloadCmd(),
}
}
......@@ -69,7 +114,8 @@ func newIcecastController(publicIP string, maxClients int) *icecastController {
// for debugging purposes.
func (ic *icecastController) reload() error {
log.Printf("reloading icecast")
cmd := exec.Command("/bin/sh", "-c", icecastReloadCmd)
cmd := exec.Command("/bin/sh", "-c", ic.reloadCmd)
cmd.Dir = "/"
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
return cmd.Run()
......@@ -113,7 +159,7 @@ func (ic *icecastController) Update(conf *clusterConfig, isMaster bool, masterAd
return err
}
changed, err := util.WriteFileIfChanged(icecastConfigFile, data)
changed, err := util.WriteFileIfChanged(*icecastConfigFile, data)
if err != nil {
return err
}
......@@ -163,7 +209,7 @@ func (ic *icecastController) Run(stop chan bool) {
}
func (ic *icecastController) fetchStatus() (*icecastStatus, error) {
resp, err := http.Get(fmt.Sprintf("http://localhost:%d%s", autoradio.IcecastPort, statusPage))
resp, err := http.Get(fmt.Sprintf("http://localhost:%d%s", autoradio.IcecastPort, icecastStatusPage))
if err != nil {
return nil, err
}
......@@ -172,7 +218,7 @@ func (ic *icecastController) fetchStatus() (*icecastStatus, error) {
}
func (ic *icecastController) parseStatusPage(input io.Reader) (*icecastStatus, error) {
var ustatus icecastStatusUnparsed
var ustatus icecastStatusRaw
if err := xml.NewDecoder(input).Decode(&ustatus); err != nil {
return nil, err
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment