mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-02-03 22:18:39 +00:00
all: add support for freshtomato
This commit is contained in:
committed by
Cuong Manh Le
parent
fc502b920b
commit
ee53db1e35
@@ -42,12 +42,11 @@ func initRouterCLI() {
|
||||
if platform == "auto" {
|
||||
platform = router.Name()
|
||||
}
|
||||
switch platform {
|
||||
case router.DDWrt, router.Merlin, router.OpenWrt, router.Ubios, router.Synology:
|
||||
default:
|
||||
if !router.IsSupported(platform) {
|
||||
unsupportedPlatformHelp(cmd)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
mainLog.Fatal().Msgf("could not find executable path: %v", err)
|
||||
|
||||
@@ -175,7 +175,7 @@ func (p *prog) setDNS() {
|
||||
switch router.Name() {
|
||||
case router.DDWrt, router.OpenWrt, router.Ubios:
|
||||
// On router, ctrld run as a DNS forwarder, it does not have to change system DNS.
|
||||
// Except for Merlin, which has WAN DNS setup on boot for NTP.
|
||||
// Except for Merlin/Tomato, which has WAN DNS setup on boot for NTP.
|
||||
return
|
||||
}
|
||||
if cfg.Listener == nil || cfg.Listener["0"] == nil {
|
||||
|
||||
@@ -22,6 +22,7 @@ var clientInfoFiles = []string{
|
||||
"/mnt/data/udapi-config/dnsmasq.lease", // UDM Pro
|
||||
"/data/udapi-config/dnsmasq.lease", // UDR
|
||||
"/etc/dhcpd/dhcpd-leases.log", // Synology
|
||||
"/tmp/var/lib/misc/dnsmasq.leases", // Tomato
|
||||
}
|
||||
|
||||
func (r *router) watchClientInfoTable() {
|
||||
|
||||
@@ -7,9 +7,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
nvramCtrldKeyPrefix = "ctrld_"
|
||||
nvramCtrldSetupKey = "ctrld_setup"
|
||||
nvramRCStartupKey = "rc_startup"
|
||||
nvramCtrldKeyPrefix = "ctrld_"
|
||||
nvramCtrldSetupKey = "ctrld_setup"
|
||||
nvramCtrldInstallKey = "ctrld_install"
|
||||
nvramRCStartupKey = "rc_startup"
|
||||
)
|
||||
|
||||
//lint:ignore ST1005 This error is for human.
|
||||
@@ -29,14 +30,14 @@ func setupDDWrt() error {
|
||||
return err
|
||||
}
|
||||
|
||||
nvramKvMap := nvramKV()
|
||||
nvramKvMap := nvramSetupKV()
|
||||
nvramKvMap["dnsmasq_options"] = data
|
||||
if err := nvramSetup(nvramKvMap); err != nil {
|
||||
if err := nvramSetKV(nvramKvMap, nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart dnsmasq service.
|
||||
if err := ddwrtRestartDNSMasq(); err != nil {
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -44,11 +45,11 @@ func setupDDWrt() error {
|
||||
|
||||
func cleanupDDWrt() error {
|
||||
// Restore old configs.
|
||||
if err := nvramRestore(nvramKV()); err != nil {
|
||||
if err := nvramRestore(nvramSetupKV(), nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := ddwrtRestartDNSMasq(); err != nil {
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -49,7 +49,7 @@ func dnsMasqConf() (string, error) {
|
||||
var sb strings.Builder
|
||||
var tmplText string
|
||||
switch Name() {
|
||||
case DDWrt, OpenWrt, Ubios, Synology:
|
||||
case DDWrt, OpenWrt, Ubios, Synology, Tomato:
|
||||
tmplText = dnsMasqConfigContentTmpl
|
||||
case Merlin:
|
||||
tmplText = merlinDNSMasqPostConfTmpl
|
||||
@@ -65,3 +65,21 @@ func dnsMasqConf() (string, error) {
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
func restartDNSMasq() error {
|
||||
switch Name() {
|
||||
case DDWrt:
|
||||
return ddwrtRestartDNSMasq()
|
||||
case Merlin:
|
||||
return merlinRestartDNSMasq()
|
||||
case OpenWrt:
|
||||
return openwrtRestartDNSMasq()
|
||||
case Ubios:
|
||||
return ubiosRestartDNSMasq()
|
||||
case Synology:
|
||||
return synologyRestartDNSMasq()
|
||||
case Tomato:
|
||||
return tomatoRestartService(tomatoDNSMasqSvcName)
|
||||
}
|
||||
panic("not supported platform")
|
||||
}
|
||||
|
||||
@@ -2,11 +2,16 @@ package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"tailscale.com/logtail/backoff"
|
||||
)
|
||||
|
||||
func setupMerlin() error {
|
||||
@@ -35,11 +40,11 @@ func setupMerlin() error {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := merlinRestartDNSMasq(); err != nil {
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := nvramSetup(nvramKV()); err != nil {
|
||||
if err := nvramSetKV(nvramSetupKV(), nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -48,7 +53,7 @@ func setupMerlin() error {
|
||||
|
||||
func cleanupMerlin() error {
|
||||
// Restore old configs.
|
||||
if err := nvramRestore(nvramKV()); err != nil {
|
||||
if err := nvramRestore(nvramSetupKV(), nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
buf, err := os.ReadFile(merlinDNSMasqPostConfPath)
|
||||
@@ -60,7 +65,7 @@ func cleanupMerlin() error {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := merlinRestartDNSMasq(); err != nil {
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -87,3 +92,43 @@ func merlinParsePostConf(buf []byte) []byte {
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func merlinPreStart() (err error) {
|
||||
pidFile := "/tmp/ctrld.pid"
|
||||
|
||||
// Remove pid file and trigger dnsmasq restart, so NTP can resolve
|
||||
// server name and perform time synchronization.
|
||||
pid, err := os.ReadFile(pidFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PreStart: os.Readfile: %w", err)
|
||||
}
|
||||
if err := os.Remove(pidFile); err != nil {
|
||||
return fmt.Errorf("PreStart: os.Remove: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if werr := os.WriteFile(pidFile, pid, 0600); werr != nil {
|
||||
err = errors.Join(err, werr)
|
||||
return
|
||||
}
|
||||
if rerr := restartDNSMasq(); rerr != nil {
|
||||
err = errors.Join(err, rerr)
|
||||
return
|
||||
}
|
||||
}()
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return fmt.Errorf("PreStart: restartDNSMasqFn: %w", err)
|
||||
}
|
||||
|
||||
// Wait until `ntp_ready=1` set.
|
||||
b := backoff.NewBackoff("PreStart", func(format string, args ...any) {}, 10*time.Second)
|
||||
for {
|
||||
out, err := nvram("get", "ntp_ready")
|
||||
if err != nil {
|
||||
return fmt.Errorf("PreStart: nvram: %w", err)
|
||||
}
|
||||
if out == "1" {
|
||||
return nil
|
||||
}
|
||||
b.BackOff(context.Background(), errors.New("ntp not ready"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ NOTE:
|
||||
+https://community.ui.com/questions/Implement-DNSSEC-into-UniFi/951c72b0-4d88-4c86-9174-45417bd2f9ca
|
||||
+https://community.ui.com/questions/Enable-DNSSEC-for-Unifi-Dream-Machine-FW-updates/e68e367c-d09b-4459-9444-18908f7c1ea1
|
||||
*/
|
||||
func nvramKV() map[string]string {
|
||||
func nvramSetupKV() map[string]string {
|
||||
switch Name() {
|
||||
case DDWrt:
|
||||
return map[string]string{
|
||||
@@ -39,11 +39,28 @@ func nvramKV() map[string]string {
|
||||
return map[string]string{
|
||||
"dnspriv_enable": "0", // Ensure Merlin native DoT disabled.
|
||||
}
|
||||
case Tomato:
|
||||
return map[string]string{
|
||||
"dnsmasq_custom": "", // Configuration of dnsmasq set by ctrld, filled by setupTomato.
|
||||
"dnscrypt_proxy": "0", // Disable DNSCrypt.
|
||||
"dnssec_enable": "0", // Disable DNSSEC.
|
||||
"stubby_proxy": "0", // Disable Stubby
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nvramSetup(m map[string]string) error {
|
||||
func nvramInstallKV() map[string]string {
|
||||
switch Name() {
|
||||
case Tomato:
|
||||
return map[string]string{
|
||||
tomatoNvramScriptWanupKey: "", // script to start ctrld, filled by tomatoSvc.Install method.
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nvramSetKV(m map[string]string, setupKey string) error {
|
||||
// Backup current value, store ctrld's configs.
|
||||
for key, value := range m {
|
||||
old, err := nvram("get", key)
|
||||
@@ -58,7 +75,7 @@ func nvramSetup(m map[string]string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if out, err := nvram("set", nvramCtrldSetupKey+"=1"); err != nil {
|
||||
if out, err := nvram("set", setupKey+"=1"); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
// Commit.
|
||||
@@ -68,7 +85,7 @@ func nvramSetup(m map[string]string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func nvramRestore(m map[string]string) error {
|
||||
func nvramRestore(m map[string]string, setupKey string) error {
|
||||
// Restore old configs.
|
||||
for key := range m {
|
||||
ctrldKey := nvramCtrldKeyPrefix + key
|
||||
@@ -82,7 +99,7 @@ func nvramRestore(m map[string]string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if out, err := nvram("unset", "ctrld_setup"); err != nil {
|
||||
if out, err := nvram("unset", setupKey); err != nil {
|
||||
return fmt.Errorf("%s: %w", out, err)
|
||||
}
|
||||
// Commit.
|
||||
|
||||
@@ -40,7 +40,7 @@ func setupOpenWrt() error {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := openwrtRestartDNSMasq(); err != nil {
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -52,7 +52,7 @@ func cleanupOpenWrt() error {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := openwrtRestartDNSMasq(); err != nil {
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -2,18 +2,14 @@ package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/kardianos/service"
|
||||
"tailscale.com/logtail/backoff"
|
||||
|
||||
"github.com/Control-D-Inc/ctrld"
|
||||
)
|
||||
@@ -24,6 +20,7 @@ const (
|
||||
Merlin = "merlin"
|
||||
Ubios = "ubios"
|
||||
Synology = "synology"
|
||||
Tomato = "tomato"
|
||||
)
|
||||
|
||||
// ErrNotSupported reports the current router is not supported error.
|
||||
@@ -38,9 +35,18 @@ type router struct {
|
||||
watcher *fsnotify.Watcher
|
||||
}
|
||||
|
||||
// IsSupported reports whether the given platform is supported by ctrld.
|
||||
func IsSupported(platform string) bool {
|
||||
switch platform {
|
||||
case DDWrt, Merlin, OpenWrt, Ubios, Synology, Tomato:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SupportedPlatforms return all platforms that can be configured to run with ctrld.
|
||||
func SupportedPlatforms() []string {
|
||||
return []string{DDWrt, Merlin, OpenWrt, Ubios, Synology}
|
||||
return []string{DDWrt, Merlin, OpenWrt, Ubios, Synology, Tomato}
|
||||
}
|
||||
|
||||
var configureFunc = map[string]func() error{
|
||||
@@ -49,13 +55,14 @@ var configureFunc = map[string]func() error{
|
||||
OpenWrt: setupOpenWrt,
|
||||
Ubios: setupUbiOS,
|
||||
Synology: setupSynology,
|
||||
Tomato: setupTomato,
|
||||
}
|
||||
|
||||
// Configure configures things for running ctrld on the router.
|
||||
func Configure(c *ctrld.Config) error {
|
||||
name := Name()
|
||||
switch name {
|
||||
case DDWrt, Merlin, OpenWrt, Ubios, Synology:
|
||||
case DDWrt, Merlin, OpenWrt, Ubios, Synology, Tomato:
|
||||
if c.HasUpstreamSendClientInfo() {
|
||||
r := routerPlatform.Load()
|
||||
r.sendClientInfo = true
|
||||
@@ -90,55 +97,22 @@ func ConfigureService(sc *service.Config) error {
|
||||
}
|
||||
case OpenWrt:
|
||||
sc.Option["SysvScript"] = openWrtScript
|
||||
case Merlin, Ubios, Synology:
|
||||
case Merlin, Ubios, Synology, Tomato:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PreStart blocks until the router is ready for running ctrld.
|
||||
func PreStart() (err error) {
|
||||
if Name() != Merlin {
|
||||
// On some routers, NTP may out of sync, so waiting for it to be ready.
|
||||
switch Name() {
|
||||
case Merlin:
|
||||
return merlinPreStart()
|
||||
case Tomato:
|
||||
return tomatoPreStart()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
pidFile := "/tmp/ctrld.pid"
|
||||
// On Merlin, NTP may out of sync, so waiting for it to be ready.
|
||||
//
|
||||
// Remove pid file and trigger dnsmasq restart, so NTP can resolve
|
||||
// server name and perform time synchronization.
|
||||
pid, err := os.ReadFile(pidFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PreStart: os.Readfile: %w", err)
|
||||
}
|
||||
if err := os.Remove(pidFile); err != nil {
|
||||
return fmt.Errorf("PreStart: os.Remove: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if werr := os.WriteFile(pidFile, pid, 0600); werr != nil {
|
||||
err = errors.Join(err, werr)
|
||||
return
|
||||
}
|
||||
if rerr := merlinRestartDNSMasq(); rerr != nil {
|
||||
err = errors.Join(err, rerr)
|
||||
return
|
||||
}
|
||||
}()
|
||||
if err := merlinRestartDNSMasq(); err != nil {
|
||||
return fmt.Errorf("PreStart: merlinRestartDNSMasq: %w", err)
|
||||
}
|
||||
|
||||
// Wait until `ntp_read=1` set.
|
||||
b := backoff.NewBackoff("PreStart", func(format string, args ...any) {}, 10*time.Second)
|
||||
for {
|
||||
out, err := nvram("get", "ntp_ready")
|
||||
if err != nil {
|
||||
return fmt.Errorf("PreStart: nvram: %w", err)
|
||||
}
|
||||
if out == "1" {
|
||||
return nil
|
||||
}
|
||||
b.BackOff(context.Background(), errors.New("ntp not ready"))
|
||||
}
|
||||
}
|
||||
|
||||
// PostInstall performs task after installing ctrld on router.
|
||||
@@ -155,6 +129,8 @@ func PostInstall() error {
|
||||
return postInstallUbiOS()
|
||||
case Synology:
|
||||
return postInstallSynology()
|
||||
case Tomato:
|
||||
return postInstallTomato()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -173,6 +149,8 @@ func Cleanup() error {
|
||||
return cleanupUbiOS()
|
||||
case Synology:
|
||||
return cleanupSynology()
|
||||
case Tomato:
|
||||
return cleanupTomato()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -181,7 +159,7 @@ func Cleanup() error {
|
||||
func ListenAddress() string {
|
||||
name := Name()
|
||||
switch name {
|
||||
case DDWrt, Merlin, OpenWrt, Ubios, Synology:
|
||||
case DDWrt, Merlin, OpenWrt, Ubios, Synology, Tomato:
|
||||
return "127.0.0.1:5354"
|
||||
}
|
||||
return ""
|
||||
@@ -210,6 +188,8 @@ func distroName() string {
|
||||
return Ubios
|
||||
case bytes.HasPrefix(unameU(), []byte("synology")):
|
||||
return Synology
|
||||
case bytes.HasPrefix(unameO(), []byte("Tomato")):
|
||||
return Tomato
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -48,6 +48,15 @@ func init() {
|
||||
},
|
||||
new: newUbiosService,
|
||||
},
|
||||
&linuxSystemService{
|
||||
name: "tomato",
|
||||
detect: func() bool { return Name() == Tomato },
|
||||
interactive: func() bool {
|
||||
is, _ := isInteractive()
|
||||
return is
|
||||
},
|
||||
new: newTomatoService,
|
||||
},
|
||||
}
|
||||
systems = append(systems, service.AvailableSystems()...)
|
||||
service.ChooseSystem(systems...)
|
||||
|
||||
278
internal/router/service_tomato.go
Normal file
278
internal/router/service_tomato.go
Normal file
@@ -0,0 +1,278 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"text/template"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
)
|
||||
|
||||
const tomatoNvramScriptWanupKey = "script_wanup"
|
||||
|
||||
type tomatoSvc struct {
|
||||
i service.Interface
|
||||
platform string
|
||||
*service.Config
|
||||
}
|
||||
|
||||
func newTomatoService(i service.Interface, platform string, c *service.Config) (service.Service, error) {
|
||||
s := &tomatoSvc{
|
||||
i: i,
|
||||
platform: platform,
|
||||
Config: c,
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) String() string {
|
||||
if len(s.DisplayName) > 0 {
|
||||
return s.DisplayName
|
||||
}
|
||||
return s.Name
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) Platform() string {
|
||||
return s.platform
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) configPath() string {
|
||||
path, err := os.Executable()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return path + ".startup"
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) template() *template.Template {
|
||||
return template.Must(template.New("").Parse(tomatoSvcScript))
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) Install() error {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(exePath, "/jffs/") {
|
||||
return errors.New("could not install service outside /jffs")
|
||||
}
|
||||
if _, err := nvram("set", "jffs2_on=1"); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := nvram("commit"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
confPath := s.configPath()
|
||||
if _, err := os.Stat(confPath); err == nil {
|
||||
return fmt.Errorf("already installed: %s", confPath)
|
||||
}
|
||||
|
||||
var to = &struct {
|
||||
*service.Config
|
||||
Path string
|
||||
}{
|
||||
s.Config,
|
||||
exePath,
|
||||
}
|
||||
|
||||
f, err := os.Create(confPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.Create: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if err := s.template().Execute(f, to); err != nil {
|
||||
return fmt.Errorf("s.template.Execute: %w", err)
|
||||
}
|
||||
|
||||
if err = os.Chmod(confPath, 0755); err != nil {
|
||||
return fmt.Errorf("os.Chmod: startup script: %w", err)
|
||||
}
|
||||
|
||||
nvramKvMap := nvramInstallKV()
|
||||
old, err := nvram("get", tomatoNvramScriptWanupKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("nvram: %w", err)
|
||||
}
|
||||
nvramKvMap[tomatoNvramScriptWanupKey] = strings.Join([]string{old, s.configPath() + " start"}, "\n")
|
||||
if err := nvramSetKV(nvramKvMap, nvramCtrldInstallKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) Uninstall() error {
|
||||
if err := os.Remove(s.configPath()); err != nil {
|
||||
return fmt.Errorf("os.Remove: %w", err)
|
||||
}
|
||||
// Restore old configs.
|
||||
if err := nvramRestore(nvramInstallKV(), nvramCtrldInstallKey); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) Logger(errs chan<- error) (service.Logger, error) {
|
||||
if service.Interactive() {
|
||||
return service.ConsoleLogger, nil
|
||||
}
|
||||
return s.SystemLogger(errs)
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) SystemLogger(errs chan<- error) (service.Logger, error) {
|
||||
return newSysLogger(s.Name, errs)
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) 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 *tomatoSvc) 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 *tomatoSvc) Start() error {
|
||||
return exec.Command(s.configPath(), "start").Run()
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) Stop() error {
|
||||
return exec.Command(s.configPath(), "stop").Run()
|
||||
}
|
||||
|
||||
func (s *tomatoSvc) Restart() error {
|
||||
return exec.Command(s.configPath(), "restart").Run()
|
||||
}
|
||||
|
||||
// https://wiki.freshtomato.org/doku.php/freshtomato_zerotier?s[]=%2Aservice%2A
|
||||
const tomatoSvcScript = `#!/bin/sh
|
||||
|
||||
|
||||
NAME="{{.Name}}"
|
||||
CMD="{{.Path}}{{range .Arguments}} {{.}}{{end}}"
|
||||
LOG_FILE="/var/log/${NAME}.log"
|
||||
PID_FILE="/tmp/$NAME.pid"
|
||||
|
||||
|
||||
alias elog="logger -t $NAME -s"
|
||||
|
||||
|
||||
COND=$1
|
||||
[ $# -eq 0 ] && COND="start"
|
||||
|
||||
get_pid() {
|
||||
cat "$PID_FILE"
|
||||
}
|
||||
|
||||
is_running() {
|
||||
[ -f "$PID_FILE" ] && ps | grep -q "^ *$(get_pid) "
|
||||
}
|
||||
|
||||
start() {
|
||||
if is_running; then
|
||||
elog "$NAME is already running."
|
||||
exit 1
|
||||
fi
|
||||
elog "Starting $NAME Services: "
|
||||
$CMD &
|
||||
echo $! > "$PID_FILE"
|
||||
chmod 600 "$PID_FILE"
|
||||
if is_running; then
|
||||
elog "succeeded."
|
||||
else
|
||||
elog "failed."
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
stop() {
|
||||
if ! is_running; then
|
||||
elog "$NAME is not running."
|
||||
exit 1
|
||||
fi
|
||||
elog "Shutting down $NAME Services: "
|
||||
kill -SIGTERM "$(get_pid)"
|
||||
for _ in 1 2 3 4 5; do
|
||||
if ! is_running; then
|
||||
if [ -f "$pid_file" ]; then
|
||||
rm "$pid_file"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
printf "."
|
||||
sleep 2
|
||||
done
|
||||
if ! is_running; then
|
||||
elog "succeeded."
|
||||
else
|
||||
elog "failed."
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
do_restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
|
||||
do_status() {
|
||||
if ! is_running; then
|
||||
echo "stopped"
|
||||
else
|
||||
echo "running"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
case "$COND" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
do_restart
|
||||
;;
|
||||
status)
|
||||
do_status
|
||||
;;
|
||||
*)
|
||||
elog "Usage: $0 (start|stop|restart|status)"
|
||||
;;
|
||||
esac
|
||||
exit 0
|
||||
`
|
||||
@@ -22,7 +22,7 @@ func setupSynology() error {
|
||||
if err := os.WriteFile(synologyDhcpdInfoPath, []byte(`enable="yes"`), 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := synologyRestartDNSMasq(); err != nil {
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -37,7 +37,7 @@ func cleanupSynology() error {
|
||||
}
|
||||
|
||||
// Restart dnsmasq service.
|
||||
if err := synologyRestartDNSMasq(); err != nil {
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
108
internal/router/tomato.go
Normal file
108
internal/router/tomato.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
|
||||
"tailscale.com/logtail/backoff"
|
||||
)
|
||||
|
||||
const (
|
||||
tomatoDnsCryptProxySvcName = "dnscrypt-proxy"
|
||||
tomatoStubbySvcName = "stubby"
|
||||
tomatoDNSMasqSvcName = "dnsmasq"
|
||||
)
|
||||
|
||||
func setupTomato() error {
|
||||
// Already setup.
|
||||
if val, _ := nvram("get", nvramCtrldSetupKey); val == "1" {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := dnsMasqConf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nvramKvMap := nvramSetupKV()
|
||||
nvramKvMap["dnsmasq_custom"] = data
|
||||
if err := nvramSetKV(nvramKvMap, nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Restart dnscrypt-proxy service.
|
||||
if err := tomatoRestartServiceWithKill(tomatoDnsCryptProxySvcName, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart stubby service.
|
||||
if err := tomatoRestartService(tomatoStubbySvcName); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func postInstallTomato() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func cleanupTomato() error {
|
||||
// Restore old configs.
|
||||
if err := nvramRestore(nvramSetupKV(), nvramCtrldSetupKey); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnscrypt-proxy service.
|
||||
if err := tomatoRestartServiceWithKill(tomatoDnsCryptProxySvcName, true); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart stubby service.
|
||||
if err := tomatoRestartService(tomatoStubbySvcName); err != nil {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func tomatoPreStart() (err error) {
|
||||
// cleanup to trigger dnsmasq restart, so NTP can resolve
|
||||
// server name and perform time synchronization.
|
||||
if err = cleanupTomato(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Wait until `ntp_ready=1` set.
|
||||
b := backoff.NewBackoff("PreStart", func(format string, args ...any) {}, 10*time.Second)
|
||||
for {
|
||||
out, err := nvram("get", "ntp_ready")
|
||||
if err != nil {
|
||||
return fmt.Errorf("PreStart: nvram: %w", err)
|
||||
}
|
||||
if out == "1" {
|
||||
return nil
|
||||
}
|
||||
b.BackOff(context.Background(), errors.New("ntp not ready"))
|
||||
}
|
||||
}
|
||||
|
||||
func tomatoRestartService(name string) error {
|
||||
return tomatoRestartServiceWithKill(name, false)
|
||||
}
|
||||
|
||||
func tomatoRestartServiceWithKill(name string, killBeforeRestart bool) error {
|
||||
if killBeforeRestart {
|
||||
_, _ = exec.Command("killall", name).CombinedOutput()
|
||||
}
|
||||
if out, err := exec.Command("service", name, "restart").CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("service restart %s: %s, %w", name, string(out), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -20,7 +20,7 @@ func setupUbiOS() error {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := ubiosRestartDNSMasq(); err != nil {
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -32,7 +32,7 @@ func cleanupUbiOS() error {
|
||||
return err
|
||||
}
|
||||
// Restart dnsmasq service.
|
||||
if err := ubiosRestartDNSMasq(); err != nil {
|
||||
if err := restartDNSMasq(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user