-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add D-Bus API for configuring NTP servers of systemd-timesyncd
Add new `time` package that currently provides methods that configure NTP and FallbackNTP options of systemd-timesyncd service. The service is restarted when the values are changed. Example usage through gdbus: gdbus call --system --dest io.hass.os \ --object-path /io/hass/os/Time/Timesyncd \ --method org.freedesktop.DBus.Properties.Set \ io.hass.os.Time.Timesyncd NTPServer \ "<['pool.ntp.org', 'time.google.com']>" A `lineinfile` helper has been implemented for adjusting Systemd unit files, the inspiration comes from Ansible's module of the same name, although the behavior is slightly different (hopefully still quite intuitive). Unit tests for the core methods handling the file content are included. In the future, the `time` package could also handle system timezone and other time-related tasks.
- Loading branch information
Showing
4 changed files
with
591 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package time | ||
|
||
import ( | ||
"fmt" | ||
"github.com/godbus/dbus/v5" | ||
"github.com/godbus/dbus/v5/introspect" | ||
"github.com/godbus/dbus/v5/prop" | ||
"github.com/home-assistant/os-agent/utils/lineinfile" | ||
"regexp" | ||
"strings" | ||
|
||
logging "github.com/home-assistant/os-agent/utils/log" | ||
) | ||
|
||
const ( | ||
objectPath = "/io/hass/os/Time/Timesyncd" | ||
ifaceName = "io.hass.os.Time.Timesyncd" | ||
timesyncdConf = "/etc/systemd/timesyncd.conf" | ||
) | ||
|
||
var ( | ||
optNTPServer []string | ||
optFallbackNTPServer []string | ||
configFile = lineinfile.LineInFile{FilePath: timesyncdConf} | ||
) | ||
|
||
type timesyncd struct { | ||
conn *dbus.Conn | ||
props *prop.Properties | ||
} | ||
|
||
func getNTPServers() []string { | ||
return getTimesyncdConfigProperty("NTP") | ||
} | ||
|
||
func getFallbackNTPServers() []string { | ||
return getTimesyncdConfigProperty("FallbackNTP") | ||
} | ||
|
||
func setNTPServer(c *prop.Change) *dbus.Error { | ||
servers, ok := c.Value.([]string) | ||
if !ok { | ||
return dbus.MakeFailedError(fmt.Errorf("invalid type for NTPServer")) | ||
} | ||
|
||
value := strings.Join(servers, " ") | ||
|
||
if err := setTimesyncdConfigProperty("NTP", value); err != nil { | ||
return dbus.MakeFailedError(err) | ||
} | ||
|
||
optNTPServer = servers | ||
return nil | ||
} | ||
|
||
func setFallbackNTPServer(c *prop.Change) *dbus.Error { | ||
servers, ok := c.Value.([]string) | ||
if !ok { | ||
return dbus.MakeFailedError(fmt.Errorf("invalid type for FallbackNTPServer")) | ||
} | ||
|
||
value := strings.Join(servers, " ") | ||
|
||
if err := setTimesyncdConfigProperty("FallbackNTP", value); err != nil { | ||
return dbus.MakeFailedError(err) | ||
} | ||
|
||
optFallbackNTPServer = servers | ||
return nil | ||
} | ||
|
||
func getTimesyncdConfigProperty(property string) []string { | ||
value, err := configFile.Find(`^\s*(`+property+`=).*$`, `\[Time\]`, true) | ||
|
||
var servers []string | ||
|
||
if err != nil || value == nil { | ||
return servers | ||
} | ||
|
||
matches := regexp.MustCompile(property + `=([^\s#]+(?:\s+[^\s#]+)*)`).FindStringSubmatch(*value) | ||
if len(matches) > 1 { | ||
servers = strings.Split(matches[1], " ") | ||
} | ||
|
||
return servers | ||
} | ||
|
||
func setTimesyncdConfigProperty(property string, value string) error { | ||
var params = lineinfile.NewPresentParams("NTP=" + value) | ||
params.Regexp, _ = regexp.Compile(`^\s*#?\s*(` + property + `=).*$`) | ||
// Keep it simple, timesyncd.conf only has the [Time] section | ||
params.After = `\[Time\]` | ||
if err := configFile.Present(params); err != nil { | ||
return fmt.Errorf("failed to set %s: %s", property, err) | ||
} | ||
|
||
if err := restartTimesyncd(); err != nil { | ||
return fmt.Errorf("failed to restart timesyncd: %s", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func restartTimesyncd() error { | ||
conn, err := dbus.SystemBus() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
obj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") | ||
call := obj.Call("org.freedesktop.systemd1.Manager.RestartUnit", 0, "systemd-timesyncd.service", "replace") | ||
if call.Err != nil { | ||
return call.Err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func InitializeDBus(conn *dbus.Conn) { | ||
d := timesyncd{ | ||
conn: conn, | ||
} | ||
|
||
optNTPServer = getNTPServers() | ||
optFallbackNTPServer = getFallbackNTPServers() | ||
|
||
propsSpec := map[string]map[string]*prop.Prop{ | ||
ifaceName: { | ||
"NTPServer": { | ||
Value: optNTPServer, | ||
Writable: true, | ||
Emit: prop.EmitTrue, | ||
Callback: setNTPServer, | ||
}, | ||
"FallbackNTPServer": { | ||
Value: optFallbackNTPServer, | ||
Writable: true, | ||
Emit: prop.EmitTrue, | ||
Callback: setFallbackNTPServer, | ||
}, | ||
}, | ||
} | ||
|
||
props, err := prop.Export(conn, objectPath, propsSpec) | ||
if err != nil { | ||
logging.Critical.Panic(err) | ||
} | ||
d.props = props | ||
|
||
err = conn.Export(d, objectPath, ifaceName) | ||
if err != nil { | ||
logging.Critical.Panic(err) | ||
} | ||
|
||
node := &introspect.Node{ | ||
Name: objectPath, | ||
Interfaces: []introspect.Interface{ | ||
introspect.IntrospectData, | ||
prop.IntrospectData, | ||
{ | ||
Name: ifaceName, | ||
Methods: introspect.Methods(d), | ||
Properties: props.Introspection(ifaceName), | ||
}, | ||
}, | ||
} | ||
|
||
err = conn.Export(introspect.NewIntrospectable(node), objectPath, "org.freedesktop.DBus.Introspectable") | ||
if err != nil { | ||
logging.Critical.Panic(err) | ||
} | ||
|
||
logging.Info.Printf("Exposing object %s with interface %s ...", objectPath, ifaceName) | ||
} |
Oops, something went wrong.