github.com/simpleiot/simpleiot@v0.18.3/client/ntp.go (about) 1 package client 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "os" 8 "os/exec" 9 "strings" 10 11 "github.com/nats-io/nats.go" 12 "github.com/simpleiot/simpleiot/data" 13 "golang.org/x/exp/slices" 14 ) 15 16 // NTPConfigPath points to the systemd-timesyncd configuration file 17 const NTPConfigPath = "/etc/systemd/timesyncd.conf.d/simpleiot.conf" 18 19 // NTPClient is a SimpleIoT client that synchronizes NTP servers to local 20 // systemd-timesync configuration 21 type NTPClient struct { 22 log *log.Logger 23 nc *nats.Conn 24 config NTP 25 stopCh chan struct{} 26 pointsCh chan NewPoints 27 } 28 29 // NTP client configuration 30 type NTP struct { 31 ID string `node:"id"` 32 Parent string `node:"parent"` 33 Servers []string `point:"server"` 34 FallbackServers []string `point:"fallbackServer"` 35 } 36 37 // NewNTPClient returns a new NTPClient using its 38 // configuration read from the Client Manager 39 func NewNTPClient(nc *nats.Conn, config NTP) Client { 40 // TODO: Ensure only one NTP client exists 41 return &NTPClient{ 42 log: log.New(os.Stderr, "NTP: ", log.LstdFlags|log.Lmsgprefix), 43 nc: nc, 44 config: config, 45 stopCh: make(chan struct{}), 46 pointsCh: make(chan NewPoints), 47 } 48 } 49 50 // Run starts the NTP Client 51 func (c *NTPClient) Run() error { 52 c.log.Println("Starting NTP client") 53 err := c.UpdateConfig() 54 if err != nil { 55 c.log.Println("error updating systemd-timesyncd config:", err) 56 } 57 loop: 58 for { 59 select { 60 case <-c.stopCh: 61 break loop 62 case points := <-c.pointsCh: 63 // Update local configuration 64 err := data.MergePoints(points.ID, points.Points, &c.config) 65 if err != nil { 66 return fmt.Errorf("merging points: %w", err) 67 } 68 err = c.UpdateConfig() 69 if err != nil { 70 c.log.Println("error updating systemd-timesyncd config:", err) 71 break // select 72 } 73 } 74 } 75 return nil 76 } 77 78 // UpdateConfig writes the NTP configuration to NTPConfigPath and restarts 79 // system-timesyncd 80 func (c *NTPClient) UpdateConfig() error { 81 data := []byte(` 82 # This file is auto-generated by SimpleIoT 83 # DO NOT EDIT OR REMOVE! 84 [Time] 85 `) 86 if len(c.config.Servers) > 0 { 87 data = append( 88 data, 89 []byte(`NTP=`+strings.Join(c.config.Servers, " "))..., 90 ) 91 } 92 if len(c.config.FallbackServers) > 0 { 93 data = append( 94 data, 95 []byte(`FallbackNTP=`+strings.Join(c.config.FallbackServers, " "))..., 96 ) 97 } 98 f, err := os.OpenFile(NTPConfigPath, os.O_RDWR|os.O_CREATE, 0644) 99 if err != nil { 100 return err 101 } 102 currData, err := io.ReadAll(f) 103 if err != nil { 104 return err 105 } 106 if slices.Equal(currData, data) { 107 // No changes to file 108 return nil 109 } 110 n, err := f.WriteAt(data, 0) 111 if err != nil { 112 return err 113 } 114 err = f.Truncate(int64(n)) 115 if err != nil { 116 return err 117 } 118 // Restart NTP 119 return exec.Command("/usr/bin/systemctl", "restart", "systemd-timesyncd").Run() 120 } 121 122 // Stop stops the NTP Client 123 func (c *NTPClient) Stop(error) { 124 close(c.stopCh) 125 } 126 127 // Points is called when the client's node points are updated 128 func (c *NTPClient) Points(nodeID string, points []data.Point) { 129 c.pointsCh <- NewPoints{ 130 ID: nodeID, 131 Points: points, 132 } 133 } 134 135 // EdgePoints is called when the client's node edge points are updated 136 func (c *NTPClient) EdgePoints( 137 _ string, _ string, _ []data.Point, 138 ) { 139 // Do nothing 140 }