os-agent/system/system.go

163 lines
4.2 KiB
Go

package system
import (
"context"
"fmt"
"os"
"strings"
"github.com/godbus/dbus/v5"
"github.com/godbus/dbus/v5/introspect"
"github.com/godbus/dbus/v5/prop"
"github.com/home-assistant/os-agent/udisks2"
logging "github.com/home-assistant/os-agent/utils/log"
)
const (
objectPath = "/io/hass/os/System"
ifaceName = "io.hass.os.System"
labelDataFileSystem = "hassos-data"
labelOverlayFileSystem = "hassos-overlay"
kernelCommandLine = "/mnt/boot/cmdline.txt"
tmpKernelCommandLine = "/mnt/boot/.tmp.cmdline.txt"
sshAuthKeyFileName = "/root/.ssh/authorized_keys"
)
type system struct {
conn *dbus.Conn
}
func getAndCheckBusObjectFromLabel(udisks2helper udisks2.UDisks2Helper, label string) (dbus.BusObject, error) {
dataBusObject, err := udisks2helper.GetBusObjectFromLabel(label)
if err != nil {
return nil, dbus.MakeFailedError(err)
}
dataFilesystem := udisks2.NewFilesystem(dataBusObject)
dataMountPoints, err := dataFilesystem.GetMountPointsString(context.Background())
if err != nil {
return nil, dbus.MakeFailedError(err)
}
if len(dataMountPoints) > 0 {
return nil, dbus.MakeFailedError(fmt.Errorf("Device with label \"%s\" is mounted at %s, aborting.", label, dataMountPoints))
}
return dataBusObject, nil
}
func (d system) WipeDevice() (bool, *dbus.Error) {
logging.Info.Printf("Wipe device data.")
udisks2helper := udisks2.NewUDisks2(d.conn)
dataBusObject, err := getAndCheckBusObjectFromLabel(udisks2helper, labelDataFileSystem)
if err != nil {
return false, dbus.MakeFailedError(err)
}
overlayBusObject, err := getAndCheckBusObjectFromLabel(udisks2helper, labelOverlayFileSystem)
if err != nil {
return false, dbus.MakeFailedError(err)
}
err = udisks2helper.FormatPartition(dataBusObject, "ext4", labelDataFileSystem)
if err != nil {
return false, dbus.MakeFailedError(err)
}
err = udisks2helper.FormatPartition(overlayBusObject, "ext4", labelOverlayFileSystem)
if err != nil {
return false, dbus.MakeFailedError(err)
}
logging.Info.Printf("Successfully wiped device data.")
return true, nil
}
func (d system) ScheduleWipeDevice() (bool, *dbus.Error) {
data, err := os.ReadFile(kernelCommandLine)
if err != nil {
fmt.Println(err)
return false, dbus.MakeFailedError(err)
}
datastr := strings.TrimSpace(string(data))
datastr += " haos.wipe=1"
err = os.WriteFile(tmpKernelCommandLine, []byte(datastr), 0644) //nolint:gosec
if err != nil {
fmt.Println(err)
return false, dbus.MakeFailedError(err)
}
// Boot is mounted sync on Home Assistant OS, so just rename should be fine.
err = os.Rename(tmpKernelCommandLine, kernelCommandLine)
if err != nil {
fmt.Println(err)
return false, dbus.MakeFailedError(err)
}
logging.Info.Printf("Device will get wiped on next reboot!")
return true, nil
}
func (d system) AddSSHAuthKey(newKey string) *dbus.Error {
file, err := os.OpenFile(sshAuthKeyFileName, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
logging.Error.Printf("Failed to open SSH authentication file %s: %s", sshAuthKeyFileName, err)
return dbus.MakeFailedError(err)
}
defer file.Close()
if _, err := file.WriteString(newKey + "\n"); err != nil {
logging.Error.Printf("Failed to write SSH authentication file: %s.", err)
return dbus.MakeFailedError(err)
}
logging.Info.Printf("New SSH authentication key added for user root.")
return nil
}
func (d system) ClearSSHAuthKeys() *dbus.Error {
if err := os.Remove(sshAuthKeyFileName); err != nil && os.IsNotExist(err) {
logging.Error.Printf("Failed to delete SSH authentication file %s: %s", sshAuthKeyFileName, err)
return dbus.MakeFailedError(err)
}
return nil
}
func InitializeDBus(conn *dbus.Conn) {
d := system{
conn: conn,
}
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),
},
},
}
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)
}