mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
295 lines
5.9 KiB
Go
295 lines
5.9 KiB
Go
package router
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"text/template"
|
|
|
|
"github.com/kardianos/service"
|
|
|
|
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
|
)
|
|
|
|
type ddwrtSvc struct {
|
|
i service.Interface
|
|
platform string
|
|
*service.Config
|
|
rcStartup string
|
|
}
|
|
|
|
func newddwrtService(i service.Interface, platform string, c *service.Config) (service.Service, error) {
|
|
s := &ddwrtSvc{
|
|
i: i,
|
|
platform: platform,
|
|
Config: c,
|
|
}
|
|
if err := os.MkdirAll("/jffs/etc/config", 0644); err != nil {
|
|
return nil, err
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (s *ddwrtSvc) String() string {
|
|
if len(s.DisplayName) > 0 {
|
|
return s.DisplayName
|
|
}
|
|
return s.Name
|
|
}
|
|
|
|
func (s *ddwrtSvc) Platform() string {
|
|
return s.platform
|
|
}
|
|
|
|
func (s *ddwrtSvc) configPath() string {
|
|
return fmt.Sprintf("/jffs/etc/config/%s.startup", s.Config.Name)
|
|
}
|
|
|
|
func (s *ddwrtSvc) template() *template.Template {
|
|
return template.Must(template.New("").Parse(ddwrtSvcScript))
|
|
}
|
|
|
|
func (s *ddwrtSvc) Install() error {
|
|
confPath := s.configPath()
|
|
if _, err := os.Stat(confPath); err == nil {
|
|
return fmt.Errorf("already installed: %s", confPath)
|
|
}
|
|
|
|
path, err := os.Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !strings.HasPrefix(path, "/jffs/") {
|
|
return errors.New("could not install service outside /jffs")
|
|
}
|
|
|
|
var to = &struct {
|
|
*service.Config
|
|
Path string
|
|
}{
|
|
s.Config,
|
|
path,
|
|
}
|
|
|
|
f, err := os.Create(confPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
if err := s.template().Execute(f, to); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = os.Chmod(confPath, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
var sb strings.Builder
|
|
if err := template.Must(template.New("").Parse(ddwrtStartupCmd)).Execute(&sb, to); err != nil {
|
|
return err
|
|
}
|
|
s.rcStartup = sb.String()
|
|
curVal, err := nvram.Run("get", nvram.RCStartupKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := nvram.Run("set", nvram.CtrldKeyPrefix+nvram.RCStartupKey+"="+curVal); err != nil {
|
|
return err
|
|
}
|
|
val := strings.Join([]string{curVal, s.rcStartup + " &", fmt.Sprintf(`echo $! > "/tmp/%s.pid"`, s.Config.Name)}, "\n")
|
|
|
|
if _, err := nvram.Run("set", nvram.RCStartupKey+"="+val); err != nil {
|
|
return err
|
|
}
|
|
if out, err := nvram.Run("commit"); err != nil {
|
|
return fmt.Errorf("%s: %w", out, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ddwrtSvc) Uninstall() error {
|
|
if err := os.Remove(s.configPath()); err != nil {
|
|
return err
|
|
}
|
|
|
|
ctrldStartupKey := nvram.CtrldKeyPrefix + nvram.RCStartupKey
|
|
rcStartup, err := nvram.Run("get", ctrldStartupKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, _ = nvram.Run("unset", ctrldStartupKey)
|
|
if _, err := nvram.Run("set", nvram.RCStartupKey+"="+rcStartup); err != nil {
|
|
return err
|
|
}
|
|
if out, err := nvram.Run("commit"); err != nil {
|
|
return fmt.Errorf("%s: %w", out, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ddwrtSvc) Logger(errs chan<- error) (service.Logger, error) {
|
|
if service.Interactive() {
|
|
return service.ConsoleLogger, nil
|
|
}
|
|
return s.SystemLogger(errs)
|
|
}
|
|
|
|
func (s *ddwrtSvc) SystemLogger(errs chan<- error) (service.Logger, error) {
|
|
// TODO(cuonglm): detect syslog enable and return proper logger?
|
|
// this at least works with default configuration.
|
|
if service.Interactive() {
|
|
return service.ConsoleLogger, nil
|
|
|
|
}
|
|
return &noopLogger{}, nil
|
|
}
|
|
|
|
func (s *ddwrtSvc) Run() (err error) {
|
|
err = s.i.Start(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if interactice, _ := isInteractive(); !interactice {
|
|
signal.Ignore(syscall.SIGHUP)
|
|
}
|
|
var sigChan = make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt)
|
|
<-sigChan
|
|
|
|
return s.i.Stop(s)
|
|
}
|
|
|
|
func (s *ddwrtSvc) Status() (service.Status, error) {
|
|
if _, err := os.Stat(s.configPath()); os.IsNotExist(err) {
|
|
return service.StatusUnknown, service.ErrNotInstalled
|
|
}
|
|
out, err := exec.Command(s.configPath(), "status").CombinedOutput()
|
|
if err != nil {
|
|
return service.StatusUnknown, err
|
|
}
|
|
switch string(bytes.TrimSpace(out)) {
|
|
case "running":
|
|
return service.StatusRunning, nil
|
|
default:
|
|
return service.StatusStopped, nil
|
|
}
|
|
}
|
|
|
|
func (s *ddwrtSvc) Start() error {
|
|
return exec.Command(s.configPath(), "start").Run()
|
|
}
|
|
|
|
func (s *ddwrtSvc) Stop() error {
|
|
return exec.Command(s.configPath(), "stop").Run()
|
|
}
|
|
|
|
func (s *ddwrtSvc) Restart() error {
|
|
err := s.Stop()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.Start()
|
|
}
|
|
|
|
type noopLogger struct {
|
|
}
|
|
|
|
func (c noopLogger) Error(v ...interface{}) error {
|
|
return nil
|
|
}
|
|
func (c noopLogger) Warning(v ...interface{}) error {
|
|
return nil
|
|
}
|
|
func (c noopLogger) Info(v ...interface{}) error {
|
|
return nil
|
|
}
|
|
func (c noopLogger) Errorf(format string, a ...interface{}) error {
|
|
return nil
|
|
}
|
|
func (c noopLogger) Warningf(format string, a ...interface{}) error {
|
|
return nil
|
|
}
|
|
func (c noopLogger) Infof(format string, a ...interface{}) error {
|
|
return nil
|
|
}
|
|
|
|
const ddwrtStartupCmd = `{{.Path}}{{range .Arguments}} {{.}}{{end}}`
|
|
const ddwrtSvcScript = `#!/bin/sh
|
|
|
|
name="{{.Name}}"
|
|
cmd="{{.Path}}{{range .Arguments}} {{.}}{{end}}"
|
|
pid_file="/tmp/$name.pid"
|
|
|
|
get_pid() {
|
|
cat "$pid_file"
|
|
}
|
|
|
|
is_running() {
|
|
[ -f "$pid_file" ] && ps | grep -q "^ *$(get_pid) "
|
|
}
|
|
|
|
case "$1" in
|
|
start)
|
|
if is_running; then
|
|
echo "Already started"
|
|
else
|
|
echo "Starting $name"
|
|
$cmd &
|
|
echo $! > "$pid_file"
|
|
chmod 600 "$pid_file"
|
|
if ! is_running; then
|
|
echo "Failed to start $name"
|
|
exit 1
|
|
fi
|
|
fi
|
|
;;
|
|
stop)
|
|
if is_running; then
|
|
echo -n "Stopping $name..."
|
|
kill "$(get_pid)"
|
|
for _ in 1 2 3 4 5; do
|
|
if ! is_running; then
|
|
echo "stopped"
|
|
if [ -f "$pid_file" ]; then
|
|
rm "$pid_file"
|
|
fi
|
|
exit 0
|
|
fi
|
|
printf "."
|
|
sleep 2
|
|
done
|
|
echo "failed to stop $name"
|
|
exit 1
|
|
fi
|
|
exit 1
|
|
;;
|
|
restart)
|
|
$0 stop
|
|
$0 start
|
|
;;
|
|
status)
|
|
if is_running; then
|
|
echo "running"
|
|
else
|
|
echo "stopped"
|
|
exit 1
|
|
fi
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {start|stop|restart|status}"
|
|
exit 1
|
|
;;
|
|
esac
|
|
exit 0
|
|
`
|