mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
all: add support to Netgear Orbi Voxel
While at it, also ensure checking the service is installed or not before executing uninstall function, so we won't emit un-necessary errors.
This commit is contained in:
committed by
Cuong Manh Le
parent
b50cccac85
commit
20f8f22bae
@@ -1783,6 +1783,10 @@ func readConfigWithNotice(writeDefaultConfig, notice bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func uninstall(p *prog, s service.Service) {
|
func uninstall(p *prog, s service.Service) {
|
||||||
|
if _, err := s.Status(); err != nil && errors.Is(err, service.ErrNotInstalled) {
|
||||||
|
mainLog.Load().Error().Msg(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
tasks := []task{
|
tasks := []task{
|
||||||
{s.Stop, false},
|
{s.Stop, false},
|
||||||
{s.Uninstall, true},
|
{s.Uninstall, true},
|
||||||
@@ -2233,7 +2237,6 @@ func newSocketControlClient(s service.Service, dir string) *controlClient {
|
|||||||
for {
|
for {
|
||||||
curStatus, err := s.Status()
|
curStatus, err := s.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mainLog.Load().Warn().Err(err).Msg("could not get service status while doing self-check")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if curStatus != service.StatusRunning {
|
if curStatus != service.StatusRunning {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func newService(i service.Interface, c *service.Config) (service.Service, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case router.IsOldOpenwrt():
|
case router.IsOldOpenwrt(), router.IsNetGearOrbi():
|
||||||
return &procd{&sysV{s}}, nil
|
return &procd{&sysV{s}}, nil
|
||||||
case router.IsGLiNet():
|
case router.IsGLiNet():
|
||||||
return &sysV{s}, nil
|
return &sysV{s}, nil
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
"github.com/Control-D-Inc/ctrld"
|
"github.com/Control-D-Inc/ctrld"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const CtrldMarker = `# GENERATED BY ctrld - DO NOT MODIFY`
|
||||||
|
|
||||||
const ConfigContentTmpl = `# GENERATED BY ctrld - DO NOT MODIFY
|
const ConfigContentTmpl = `# GENERATED BY ctrld - DO NOT MODIFY
|
||||||
no-resolv
|
no-resolv
|
||||||
{{- range .Upstreams}}
|
{{- range .Upstreams}}
|
||||||
|
|||||||
22
internal/router/netgear_orbi_voxel/procd.go
Normal file
22
internal/router/netgear_orbi_voxel/procd.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package netgear
|
||||||
|
|
||||||
|
const openWrtScript = `#!/bin/sh /etc/rc.common
|
||||||
|
USE_PROCD=1
|
||||||
|
# After dnsmasq starts
|
||||||
|
START=61
|
||||||
|
# Before network stops
|
||||||
|
STOP=89
|
||||||
|
cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}"
|
||||||
|
name="{{.Name}}"
|
||||||
|
pid_file="/var/run/${name}.pid"
|
||||||
|
|
||||||
|
start_service() {
|
||||||
|
echo "Starting ${name}"
|
||||||
|
procd_open_instance
|
||||||
|
procd_set_param command ${cmd}
|
||||||
|
procd_set_param respawn # respawn automatically if something died
|
||||||
|
procd_set_param pidfile ${pid_file} # write a pid file on instance start and remove it on stop
|
||||||
|
procd_close_instance
|
||||||
|
echo "${name} has been started"
|
||||||
|
}
|
||||||
|
`
|
||||||
220
internal/router/netgear_orbi_voxel/voxel.go
Normal file
220
internal/router/netgear_orbi_voxel/voxel.go
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
package netgear
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kardianos/service"
|
||||||
|
|
||||||
|
"github.com/Control-D-Inc/ctrld"
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/dnsmasq"
|
||||||
|
"github.com/Control-D-Inc/ctrld/internal/router/nvram"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name = "netgear_orbi_voxel"
|
||||||
|
netgearOrbiVoxelDNSMasqConfigPath = "/etc/dnsmasq.conf"
|
||||||
|
netgearOrbiVoxelHomedir = "/mnt/bitdefender"
|
||||||
|
netgearOrbiVoxelStartupScript = "/mnt/bitdefender/rc.user"
|
||||||
|
netgearOrbiVoxelStartupScriptBackup = "/mnt/bitdefender/rc.user.bak"
|
||||||
|
netgearOrbiVoxelStartupScriptMarker = "\n# GENERATED BY ctrld"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nvramKvMap = map[string]string{
|
||||||
|
"dns_hijack": "0", // Disable dns hijacking
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetgearOrbiVoxel struct {
|
||||||
|
cfg *ctrld.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a router.Router for configuring/setup/run ctrld on ddwrt routers.
|
||||||
|
func New(cfg *ctrld.Config) *NetgearOrbiVoxel {
|
||||||
|
return &NetgearOrbiVoxel{cfg: cfg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *NetgearOrbiVoxel) ConfigureService(svc *service.Config) error {
|
||||||
|
if err := d.checkInstalledDir(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
svc.Option["SysvScript"] = openWrtScript
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *NetgearOrbiVoxel) Install(_ *service.Config) error {
|
||||||
|
// Ignoring error here at this moment is ok, since everything will be wiped out on reboot.
|
||||||
|
_ = exec.Command("/etc/init.d/ctrld", "enable").Run()
|
||||||
|
if err := d.checkInstalledDir(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := backupVoxelStartupScript(); err != nil {
|
||||||
|
return fmt.Errorf("backup startup script: %w", err)
|
||||||
|
}
|
||||||
|
if err := writeVoxelStartupScript(); err != nil {
|
||||||
|
return fmt.Errorf("writing startup script: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *NetgearOrbiVoxel) Uninstall(_ *service.Config) error {
|
||||||
|
if err := os.Remove(netgearOrbiVoxelStartupScript); err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err := os.Rename(netgearOrbiVoxelStartupScriptBackup, netgearOrbiVoxelStartupScript)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *NetgearOrbiVoxel) PreRun() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *NetgearOrbiVoxel) Setup() error {
|
||||||
|
if d.cfg.FirstListener().IsDirectDnsListener() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Already setup.
|
||||||
|
if val, _ := nvram.Run("get", nvram.CtrldSetupKey); val == "1" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := dnsmasq.ConfTmplWithCacheDisabled(dnsmasq.ConfigContentTmpl, d.cfg, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
currentConfig, _ := os.ReadFile(netgearOrbiVoxelDNSMasqConfigPath)
|
||||||
|
configContent := append(currentConfig, data...)
|
||||||
|
if err := os.WriteFile(netgearOrbiVoxelDNSMasqConfigPath, configContent, 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Restart dnsmasq service.
|
||||||
|
if err := restartDNSMasq(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := nvram.SetKV(nvramKvMap, nvram.CtrldSetupKey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *NetgearOrbiVoxel) Cleanup() error {
|
||||||
|
if d.cfg.FirstListener().IsDirectDnsListener() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if val, _ := nvram.Run("get", nvram.CtrldSetupKey); val != "1" {
|
||||||
|
return nil // was restored, nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore old configs.
|
||||||
|
if err := nvram.Restore(nvramKvMap, nvram.CtrldSetupKey); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore dnsmasq config.
|
||||||
|
if err := restoreDnsmasqConf(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart dnsmasq service.
|
||||||
|
if err := restartDNSMasq(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkInstalledDir checks that ctrld binary was installed in the correct directory.
|
||||||
|
func (d *NetgearOrbiVoxel) checkInstalledDir() error {
|
||||||
|
exePath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("checkHomeDir: failed to get binary path %w", err)
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(filepath.Dir(exePath), netgearOrbiVoxelHomedir) {
|
||||||
|
return fmt.Errorf("checkHomeDir: could not install service outside %s", netgearOrbiVoxelHomedir)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// backupVoxelStartupScript creates a backup of original startup script if existed.
|
||||||
|
func backupVoxelStartupScript() error {
|
||||||
|
// Do nothing if the startup script was modified by ctrld.
|
||||||
|
script, _ := os.ReadFile(netgearOrbiVoxelStartupScript)
|
||||||
|
if bytes.Contains(script, []byte(netgearOrbiVoxelStartupScriptMarker)) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := os.Rename(netgearOrbiVoxelStartupScript, netgearOrbiVoxelStartupScriptBackup)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("backupVoxelStartupScript: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeVoxelStartupScript writes startup script to re-install ctrld upon reboot.
|
||||||
|
// See: https://github.com/SVoxel/ORBI-RBK50/pull/7
|
||||||
|
func writeVoxelStartupScript() error {
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("configure service: failed to get binary path %w", err)
|
||||||
|
}
|
||||||
|
// This is called when "ctrld start ..." runs, so recording
|
||||||
|
// the same command line arguments to use in startup script.
|
||||||
|
argStr := strings.Join(os.Args[1:], " ")
|
||||||
|
script, _ := os.ReadFile(netgearOrbiVoxelStartupScriptBackup)
|
||||||
|
script = append(script, fmt.Sprintf("%s\n%q %s\n", netgearOrbiVoxelStartupScriptMarker, exe, argStr)...)
|
||||||
|
f, err := os.Create(netgearOrbiVoxelStartupScript)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create startup script: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := f.Write(script); err != nil {
|
||||||
|
return fmt.Errorf("failed to write startup script: %w", err)
|
||||||
|
}
|
||||||
|
if err := f.Close(); err != nil {
|
||||||
|
return fmt.Errorf("failed to save startup script: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// restoreDnsmasqConf restores original dnsmasq configuration.
|
||||||
|
func restoreDnsmasqConf() error {
|
||||||
|
f, err := os.Open(netgearOrbiVoxelDNSMasqConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var bs []byte
|
||||||
|
buf := bytes.NewBuffer(bs)
|
||||||
|
|
||||||
|
removed := false
|
||||||
|
scanner := bufio.NewScanner(f)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if line == dnsmasq.CtrldMarker {
|
||||||
|
removed = true
|
||||||
|
}
|
||||||
|
if !removed {
|
||||||
|
_, err := buf.WriteString(line + "\n")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return os.WriteFile(netgearOrbiVoxelDNSMasqConfigPath, buf.Bytes(), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func restartDNSMasq() error {
|
||||||
|
if out, err := exec.Command("/etc/init.d/dnsmasq", "restart").CombinedOutput(); err != nil {
|
||||||
|
return fmt.Errorf("restartDNSMasq: %s, %w", string(out), err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/Control-D-Inc/ctrld/internal/router/edgeos"
|
"github.com/Control-D-Inc/ctrld/internal/router/edgeos"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router/firewalla"
|
"github.com/Control-D-Inc/ctrld/internal/router/firewalla"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router/merlin"
|
"github.com/Control-D-Inc/ctrld/internal/router/merlin"
|
||||||
|
netgear "github.com/Control-D-Inc/ctrld/internal/router/netgear_orbi_voxel"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router/openwrt"
|
"github.com/Control-D-Inc/ctrld/internal/router/openwrt"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router/synology"
|
"github.com/Control-D-Inc/ctrld/internal/router/synology"
|
||||||
"github.com/Control-D-Inc/ctrld/internal/router/tomato"
|
"github.com/Control-D-Inc/ctrld/internal/router/tomato"
|
||||||
@@ -66,10 +67,17 @@ func New(cfg *ctrld.Config, cdMode bool) Router {
|
|||||||
return tomato.New(cfg)
|
return tomato.New(cfg)
|
||||||
case firewalla.Name:
|
case firewalla.Name:
|
||||||
return firewalla.New(cfg)
|
return firewalla.New(cfg)
|
||||||
|
case netgear.Name:
|
||||||
|
return netgear.New(cfg)
|
||||||
}
|
}
|
||||||
return newOsRouter(cfg, cdMode)
|
return newOsRouter(cfg, cdMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsNetGearOrbi reports whether the router is a Netgear Orbi router.
|
||||||
|
func IsNetGearOrbi() bool {
|
||||||
|
return Name() == netgear.Name
|
||||||
|
}
|
||||||
|
|
||||||
// IsGLiNet reports whether the router is an GL.iNet router.
|
// IsGLiNet reports whether the router is an GL.iNet router.
|
||||||
func IsGLiNet() bool {
|
func IsGLiNet() bool {
|
||||||
if Name() != openwrt.Name {
|
if Name() != openwrt.Name {
|
||||||
@@ -145,7 +153,7 @@ func LocalResolverIP() string {
|
|||||||
// HomeDir returns the home directory of ctrld on current router.
|
// HomeDir returns the home directory of ctrld on current router.
|
||||||
func HomeDir() (string, error) {
|
func HomeDir() (string, error) {
|
||||||
switch Name() {
|
switch Name() {
|
||||||
case ddwrt.Name, firewalla.Name, merlin.Name, tomato.Name:
|
case ddwrt.Name, firewalla.Name, merlin.Name, netgear.Name, tomato.Name:
|
||||||
exe, err := os.Executable()
|
exe, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -198,6 +206,9 @@ func distroName() string {
|
|||||||
case bytes.HasPrefix(unameO(), []byte("ASUSWRT-Merlin")):
|
case bytes.HasPrefix(unameO(), []byte("ASUSWRT-Merlin")):
|
||||||
return merlin.Name
|
return merlin.Name
|
||||||
case haveFile("/etc/openwrt_version"):
|
case haveFile("/etc/openwrt_version"):
|
||||||
|
if haveFile("/bin/config") { // TODO: is there any more reliable way?
|
||||||
|
return netgear.Name
|
||||||
|
}
|
||||||
return openwrt.Name
|
return openwrt.Name
|
||||||
case isUbios():
|
case isUbios():
|
||||||
return ubios.Name
|
return ubios.Name
|
||||||
|
|||||||
Reference in New Issue
Block a user