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
Branches
No related tags found
No related merge requests found
...@@ -3,6 +3,7 @@ package node ...@@ -3,6 +3,7 @@ package node
import ( import (
"encoding/xml" "encoding/xml"
"errors" "errors"
"flag"
"fmt" "fmt"
"io" "io"
"log" "log"
...@@ -18,21 +19,63 @@ import ( ...@@ -18,21 +19,63 @@ import (
"git.autistici.org/ale/autoradio/util" "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 ( var (
statusPage = "/status-autoradio.xsl" icecastConfigFile = flag.String("icecast-config", "/etc/icecast2/icecast.xml", "Icecast configuration file")
icecastConfigFile = "/etc/icecast2/icecast.xml" icecastCustomReloadCmd = flag.String("icecast-reload-command", "", "Command to reload / restart the icecast2 daemon")
icecastReloadCmd = "/usr/sbin/service icecast2 reload || /usr/sbin/service icecast2 start"
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 // Icecast returns empty fields in our status handler, which we'll
// need to turn into integers (the xml unmarshaler will return an // need to turn into integers (the xml unmarshaler will return an
// error in this specific case), so we use a separate type for // error in this specific case), so we use a separate type for
// decoding the status page output. This would be much simpler if I // 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 // knew how to get the XSLT to put a default value in the output
// instead of an empty field... // instead of an empty field...
type icecastMountStatusUnparsed struct { type icecastMountStatusRaw struct {
Name string `xml:"name,attr"` Name string `xml:"name,attr"`
Listeners string `xml:"listeners"` Listeners string `xml:"listeners"`
BitRate string `xml:"bitrate"` BitRate string `xml:"bitrate"`
...@@ -42,9 +85,9 @@ type icecastMountStatusUnparsed struct { ...@@ -42,9 +85,9 @@ type icecastMountStatusUnparsed struct {
FrameRate string `xml:"frame-rate"` FrameRate string `xml:"frame-rate"`
} }
type icecastStatusUnparsed struct { type icecastStatusRaw struct {
XMLName xml.Name `xml:"status"` XMLName xml.Name `xml:"status"`
Mounts []icecastMountStatusUnparsed `xml:"mount"` Mounts []icecastMountStatusRaw `xml:"mount"`
} }
type icecastStatus struct { type icecastStatus struct {
...@@ -53,15 +96,17 @@ type icecastStatus struct { ...@@ -53,15 +96,17 @@ type icecastStatus struct {
} }
type icecastController struct { type icecastController struct {
config *icecastConfig config *icecastConfig
status *icecastStatus status *icecastStatus
stop chan bool reloadCmd string
stop chan bool
} }
func newIcecastController(publicIP string, maxClients int) *icecastController { func newIcecastController(publicIP string, maxClients int) *icecastController {
return &icecastController{ return &icecastController{
config: newIcecastConfig(publicIP, maxClients), config: newIcecastConfig(publicIP, maxClients),
status: &icecastStatus{}, status: &icecastStatus{},
reloadCmd: getIcecastReloadCmd(),
} }
} }
...@@ -69,7 +114,8 @@ func newIcecastController(publicIP string, maxClients int) *icecastController { ...@@ -69,7 +114,8 @@ func newIcecastController(publicIP string, maxClients int) *icecastController {
// for debugging purposes. // for debugging purposes.
func (ic *icecastController) reload() error { func (ic *icecastController) reload() error {
log.Printf("reloading icecast") 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.Stdout = os.Stderr
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
return cmd.Run() return cmd.Run()
...@@ -113,7 +159,7 @@ func (ic *icecastController) Update(conf *clusterConfig, isMaster bool, masterAd ...@@ -113,7 +159,7 @@ func (ic *icecastController) Update(conf *clusterConfig, isMaster bool, masterAd
return err return err
} }
changed, err := util.WriteFileIfChanged(icecastConfigFile, data) changed, err := util.WriteFileIfChanged(*icecastConfigFile, data)
if err != nil { if err != nil {
return err return err
} }
...@@ -163,7 +209,7 @@ func (ic *icecastController) Run(stop chan bool) { ...@@ -163,7 +209,7 @@ func (ic *icecastController) Run(stop chan bool) {
} }
func (ic *icecastController) fetchStatus() (*icecastStatus, error) { 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 { if err != nil {
return nil, err return nil, err
} }
...@@ -172,7 +218,7 @@ func (ic *icecastController) fetchStatus() (*icecastStatus, error) { ...@@ -172,7 +218,7 @@ func (ic *icecastController) fetchStatus() (*icecastStatus, error) {
} }
func (ic *icecastController) parseStatusPage(input io.Reader) (*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 { if err := xml.NewDecoder(input).Decode(&ustatus); err != nil {
return nil, err return nil, err
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment