Skip to content
Snippets Groups Projects
Commit f9a9a9ef authored by ale's avatar ale
Browse files

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
No related branches found
No related tags found
No related merge requests found
......@@ -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")
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 {
type icecastStatusRaw struct {
XMLName xml.Name `xml:"status"`
Mounts []icecastMountStatusUnparsed `xml:"mount"`
Mounts []icecastMountStatusRaw `xml:"mount"`
}
type icecastStatus struct {
......@@ -55,6 +98,7 @@ type icecastStatus struct {
type icecastController struct {
config *icecastConfig
status *icecastStatus
reloadCmd string
stop chan bool
}
......@@ -62,6 +106,7 @@ func newIcecastController(publicIP string, maxClients int) *icecastController {
return &icecastController{
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
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment