diff --git a/internal/router/ddwrt/ddwrt.go b/internal/router/ddwrt/ddwrt.go index dc220ce..9f2dc20 100644 --- a/internal/router/ddwrt/ddwrt.go +++ b/internal/router/ddwrt/ddwrt.go @@ -54,7 +54,7 @@ func (d *Ddwrt) Uninstall(_ *service.Config) error { func (d *Ddwrt) PreRun() error { _ = d.Cleanup() - return ntp.Wait() + return ntp.WaitNvram() } func (d *Ddwrt) Setup() error { diff --git a/internal/router/merlin/merlin.go b/internal/router/merlin/merlin.go index 19d14b3..5abf8f1 100644 --- a/internal/router/merlin/merlin.go +++ b/internal/router/merlin/merlin.go @@ -45,7 +45,7 @@ func (m *Merlin) Uninstall(_ *service.Config) error { func (m *Merlin) PreRun() error { _ = m.Cleanup() - return ntp.Wait() + return ntp.WaitNvram() } func (m *Merlin) Setup() error { diff --git a/internal/router/ntp/ntp.go b/internal/router/ntp/ntp.go index 9854fcf..a9a9409 100644 --- a/internal/router/ntp/ntp.go +++ b/internal/router/ntp/ntp.go @@ -1,16 +1,20 @@ package ntp import ( + "bytes" "context" "errors" "fmt" + "os/exec" "time" - "github.com/Control-D-Inc/ctrld/internal/router/nvram" "tailscale.com/logtail/backoff" + + "github.com/Control-D-Inc/ctrld/internal/router/nvram" ) -func Wait() error { +// WaitNvram waits NTP synced by checking "ntp_ready" value using nvram. +func WaitNvram() error { // Wait until `ntp_ready=1` set. b := backoff.NewBackoff("ntp.Wait", func(format string, args ...any) {}, 10*time.Second) for { @@ -24,3 +28,19 @@ func Wait() error { b.BackOff(context.Background(), errors.New("ntp not ready")) } } + +// WaitUpstart waits NTP synced by checking upstart task "ntpsync" is in "stop/waiting" state. +func WaitUpstart() error { + // Wait until `initctl status ntpsync` returns stop state. + b := backoff.NewBackoff("ntp.WaitUpstart", func(format string, args ...any) {}, 10*time.Second) + for { + out, err := exec.Command("initctl", "status", "ntpsync").CombinedOutput() + if err != nil { + return fmt.Errorf("exec.Command: %w", err) + } + if bytes.Contains(out, []byte("stop/waiting")) { + return nil + } + b.BackOff(context.Background(), errors.New("ntp not ready")) + } +} diff --git a/internal/router/synology/synology.go b/internal/router/synology/synology.go index fb69d8e..7933943 100644 --- a/internal/router/synology/synology.go +++ b/internal/router/synology/synology.go @@ -1,14 +1,21 @@ package synology import ( + "bytes" + "context" + "errors" "fmt" "os" "os/exec" + "strings" + "time" - "github.com/Control-D-Inc/ctrld/internal/router/dnsmasq" + "github.com/kardianos/service" + "tailscale.com/logtail/backoff" "github.com/Control-D-Inc/ctrld" - "github.com/kardianos/service" + "github.com/Control-D-Inc/ctrld/internal/router/dnsmasq" + "github.com/Control-D-Inc/ctrld/internal/router/ntp" ) const ( @@ -19,16 +26,20 @@ const ( ) type Synology struct { - cfg *ctrld.Config + cfg *ctrld.Config + useUpstart bool } // New returns a router.Router for configuring/setup/run ctrld on Ubios routers. func New(cfg *ctrld.Config) *Synology { - return &Synology{cfg: cfg} + return &Synology{ + cfg: cfg, + useUpstart: service.Platform() == "linux-upstart", + } } func (s *Synology) ConfigureService(svc *service.Config) error { - svc.Option["UpstartScript"] = upstartScript + svc.Option["LogOutput"] = true return nil } @@ -41,6 +52,12 @@ func (s *Synology) Uninstall(_ *service.Config) error { } func (s *Synology) PreRun() error { + if s.useUpstart { + if err := ntp.WaitUpstart(); err != nil { + return err + } + return waitDhcpServer() + } return nil } @@ -88,49 +105,21 @@ func restartDNSMasq() error { return nil } -// Copied from https://github.com/kardianos/service/blob/6fe2824ee8248e776b0f8be39aaeff45a45a4f6c/service_upstart_linux.go#L232 -// With modification to wait for dhcpserver started before ctrld. - -// The upstart script should stop with an INT or the Go runtime will terminate -// the program before the Stop handler can run. -const upstartScript = `# {{.Description}} - -{{if .DisplayName}}description "{{.DisplayName}}"{{end}} - -{{if .HasKillStanza}}kill signal INT{{end}} -{{if .ChRoot}}chroot {{.ChRoot}}{{end}} -{{if .WorkingDirectory}}chdir {{.WorkingDirectory}}{{end}} -start on filesystem or runlevel [2345] -stop on runlevel [!2345] - -start on started dhcpserver -normal exit 0 TERM HUP - -{{if and .UserName .HasSetUIDStanza}}setuid {{.UserName}}{{end}} - -respawn -respawn limit 10 5 -umask 022 - -console none - -pre-start script - test -x {{.Path}} || { stop; exit 0; } -end script - -# Start -script - {{if .LogOutput}} - stdout_log="/var/log/{{.Name}}.out" - stderr_log="/var/log/{{.Name}}.err" - {{end}} - - if [ -f "/etc/sysconfig/{{.Name}}" ]; then - set -a - source /etc/sysconfig/{{.Name}} - set +a - fi - - exec {{if and .UserName (not .HasSetUIDStanza)}}sudo -E -u {{.UserName}} {{end}}{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}{{if .LogOutput}} >> $stdout_log 2>> $stderr_log{{end}} -end script -` +func waitDhcpServer() error { + // Wait until `initctl status dhcpserver` returns running state. + b := backoff.NewBackoff("waitDhcpServer", func(format string, args ...any) {}, 10*time.Second) + for { + out, err := exec.Command("initctl", "status", "dhcpserver").CombinedOutput() + if err != nil { + if strings.Contains(err.Error(), "Unknown job") { + // dhcpserver service does not exist. + return nil + } + return fmt.Errorf("exec.Command: %w", err) + } + if bytes.Contains(out, []byte("start/running")) { + return nil + } + b.BackOff(context.Background(), errors.New("ntp not ready")) + } +} diff --git a/internal/router/tomato/tomato.go b/internal/router/tomato/tomato.go index 40a70e5..cd8df70 100644 --- a/internal/router/tomato/tomato.go +++ b/internal/router/tomato/tomato.go @@ -49,7 +49,7 @@ func (f *FreshTomato) Uninstall(_ *service.Config) error { func (f *FreshTomato) PreRun() error { _ = f.Cleanup() - return ntp.Wait() + return ntp.WaitNvram() } func (f *FreshTomato) Setup() error {