- No recognized USB Wi-Fi module is plugged into the machine! Such a module will be
- needed before this machine can connect to an external Wi-Fi network.
-
- diff --git a/internal/app/ipc/openuc2/com.openuc2.deviceadmin.openuc2.varlink b/internal/app/ipc/openuc2/com.openuc2.deviceadmin.openuc2.varlink
new file mode 100644
index 0000000..f51579b
--- /dev/null
+++ b/internal/app/ipc/openuc2/com.openuc2.deviceadmin.openuc2.varlink
@@ -0,0 +1,18 @@
+# com.openuc2.deviceadmin.openuc2 manages openUC2 OS-specific settings.
+interface com.openuc2.deviceadmin.openuc2
+
+# The service was unable to perform the requested operation for an unspecified reason.
+error Unknown (description: string)
+
+# NetworkManager
+
+# UpdatePSKDropInFile updates the specified connection profile (as specified via file-based name,
+# e.g. "wlan0-hotspot")'s Wi-Fi PSK drop-in snippet file (which is automatically determined)
+# with the specified cleartext password.
+# This operation does not try to regenerate the connection profile itself from the drop-in files.
+method UpdatePSKDropInFile(connProfile: string, newPw: string) -> ()
+
+# RegenerateConnProfile reassembles the file for the NetworkManager connection profile (specified by
+# its file-based name, e.g. "wlan0-hotspot") from its constituent drop-in snippet files.
+# This operation does not try to make NetworkManager reload the updated connection profile.
+method RegenerateDropInConnProfile(connProfile: string) -> ()
diff --git a/internal/app/ipc/openuc2/comopenuc2deviceadminopenuc2.go b/internal/app/ipc/openuc2/comopenuc2deviceadminopenuc2.go
new file mode 100644
index 0000000..5e2e90b
--- /dev/null
+++ b/internal/app/ipc/openuc2/comopenuc2deviceadminopenuc2.go
@@ -0,0 +1,277 @@
+// Code generated by github.com/varlink/go/cmd/varlink-go-interface-generator, DO NOT EDIT.
+
+// com.openuc2.deviceadmin.openuc2 manages openUC2 OS-specific settings.
+package comopenuc2deviceadminopenuc2
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "github.com/varlink/go/varlink"
+)
+
+// Generated type declarations
+
+// The service was unable to perform the requested operation for an unspecified reason.
+type Unknown struct {
+ Description string `json:"description"`
+}
+
+func (e Unknown) Error() string {
+ s := "com.openuc2.deviceadmin.openuc2.Unknown"
+ s += fmt.Sprintf("(Description: %v)", e.Description)
+ return s
+}
+
+func Dispatch_Error(err error) error {
+ if e, ok := err.(*varlink.Error); ok {
+ switch e.Name {
+ case "com.openuc2.deviceadmin.openuc2.Unknown":
+ errorRawParameters := e.Parameters.(*json.RawMessage)
+ if errorRawParameters == nil {
+ return e
+ }
+ var param Unknown
+ err := json.Unmarshal(*errorRawParameters, ¶m)
+ if err != nil {
+ return e
+ }
+ return ¶m
+ }
+ }
+ return err
+}
+
+// Generated client method calls
+
+// UpdatePSKDropInFile updates the specified connection profile (as specified via file-based name,
+// e.g. "wlan0-hotspot")'s Wi-Fi PSK drop-in snippet file (which is automatically determined)
+// with the specified cleartext password.
+// This operation does not try to regenerate the connection profile itself from the drop-in files.
+type UpdatePSKDropInFile_methods struct{}
+
+func UpdatePSKDropInFile() UpdatePSKDropInFile_methods { return UpdatePSKDropInFile_methods{} }
+
+func (m UpdatePSKDropInFile_methods) Call(ctx context.Context, c *varlink.Connection, connProfile_in_ string, newPw_in_ string) (err_ error) {
+ receive, err_ := m.Send(ctx, c, 0, connProfile_in_, newPw_in_)
+ if err_ != nil {
+ return
+ }
+ _, err_ = receive(ctx)
+ return
+}
+
+func (m UpdatePSKDropInFile_methods) Send(ctx context.Context, c *varlink.Connection, flags uint64, connProfile_in_ string, newPw_in_ string) (func(ctx context.Context) (uint64, error), error) {
+ var in struct {
+ ConnProfile string `json:"connProfile"`
+ NewPw string `json:"newPw"`
+ }
+ in.ConnProfile = connProfile_in_
+ in.NewPw = newPw_in_
+ receive, err := c.Send(ctx, "com.openuc2.deviceadmin.openuc2.UpdatePSKDropInFile", in, flags)
+ if err != nil {
+ return nil, err
+ }
+ return func(context.Context) (flags uint64, err error) {
+ flags, err = receive(ctx, nil)
+ if err != nil {
+ err = Dispatch_Error(err)
+ return
+ }
+ return
+ }, nil
+}
+
+func (m UpdatePSKDropInFile_methods) Upgrade(ctx context.Context, c *varlink.Connection, connProfile_in_ string, newPw_in_ string) (func(ctx context.Context) (flags uint64, conn varlink.ReadWriterContext, err_ error), error) {
+ var in struct {
+ ConnProfile string `json:"connProfile"`
+ NewPw string `json:"newPw"`
+ }
+ in.ConnProfile = connProfile_in_
+ in.NewPw = newPw_in_
+ receive, err := c.Upgrade(ctx, "com.openuc2.deviceadmin.openuc2.UpdatePSKDropInFile", in)
+ if err != nil {
+ return nil, err
+ }
+ return func(context.Context) (flags uint64, conn varlink.ReadWriterContext, err error) {
+ flags, conn, err = receive(ctx, nil)
+ if err != nil {
+ err = Dispatch_Error(err)
+ return
+ }
+ return
+ }, nil
+}
+
+// RegenerateConnProfile reassembles the file for the NetworkManager connection profile (specified by
+// its file-based name, e.g. "wlan0-hotspot") from its constituent drop-in snippet files.
+// This operation does not try to make NetworkManager reload the updated connection profile.
+type RegenerateDropInConnProfile_methods struct{}
+
+func RegenerateDropInConnProfile() RegenerateDropInConnProfile_methods {
+ return RegenerateDropInConnProfile_methods{}
+}
+
+func (m RegenerateDropInConnProfile_methods) Call(ctx context.Context, c *varlink.Connection, connProfile_in_ string) (err_ error) {
+ receive, err_ := m.Send(ctx, c, 0, connProfile_in_)
+ if err_ != nil {
+ return
+ }
+ _, err_ = receive(ctx)
+ return
+}
+
+func (m RegenerateDropInConnProfile_methods) Send(ctx context.Context, c *varlink.Connection, flags uint64, connProfile_in_ string) (func(ctx context.Context) (uint64, error), error) {
+ var in struct {
+ ConnProfile string `json:"connProfile"`
+ }
+ in.ConnProfile = connProfile_in_
+ receive, err := c.Send(ctx, "com.openuc2.deviceadmin.openuc2.RegenerateDropInConnProfile", in, flags)
+ if err != nil {
+ return nil, err
+ }
+ return func(context.Context) (flags uint64, err error) {
+ flags, err = receive(ctx, nil)
+ if err != nil {
+ err = Dispatch_Error(err)
+ return
+ }
+ return
+ }, nil
+}
+
+func (m RegenerateDropInConnProfile_methods) Upgrade(ctx context.Context, c *varlink.Connection, connProfile_in_ string) (func(ctx context.Context) (flags uint64, conn varlink.ReadWriterContext, err_ error), error) {
+ var in struct {
+ ConnProfile string `json:"connProfile"`
+ }
+ in.ConnProfile = connProfile_in_
+ receive, err := c.Upgrade(ctx, "com.openuc2.deviceadmin.openuc2.RegenerateDropInConnProfile", in)
+ if err != nil {
+ return nil, err
+ }
+ return func(context.Context) (flags uint64, conn varlink.ReadWriterContext, err error) {
+ flags, conn, err = receive(ctx, nil)
+ if err != nil {
+ err = Dispatch_Error(err)
+ return
+ }
+ return
+ }, nil
+}
+
+// Generated service interface with all methods
+
+type comopenuc2deviceadminopenuc2Interface interface {
+ UpdatePSKDropInFile(ctx context.Context, c VarlinkCall, connProfile_ string, newPw_ string) error
+ RegenerateDropInConnProfile(ctx context.Context, c VarlinkCall, connProfile_ string) error
+}
+
+// Generated service object with all methods
+
+type VarlinkCall struct{ varlink.Call }
+
+// Generated reply methods for all varlink errors
+
+// The service was unable to perform the requested operation for an unspecified reason.
+func (c *VarlinkCall) ReplyUnknown(ctx context.Context, description_ string) error {
+ var out Unknown
+ out.Description = description_
+ return c.ReplyError(ctx, "com.openuc2.deviceadmin.openuc2.Unknown", &out)
+}
+
+// Generated reply methods for all varlink methods
+
+func (c *VarlinkCall) ReplyUpdatePSKDropInFile(ctx context.Context) error {
+ return c.Reply(ctx, nil)
+}
+
+func (c *VarlinkCall) ReplyRegenerateDropInConnProfile(ctx context.Context) error {
+ return c.Reply(ctx, nil)
+}
+
+// Generated dummy implementations for all varlink methods
+
+// UpdatePSKDropInFile updates the specified connection profile (as specified via file-based name,
+// e.g. "wlan0-hotspot")'s Wi-Fi PSK drop-in snippet file (which is automatically determined)
+// with the specified cleartext password.
+// This operation does not try to regenerate the connection profile itself from the drop-in files.
+func (s *VarlinkInterface) UpdatePSKDropInFile(ctx context.Context, c VarlinkCall, connProfile_ string, newPw_ string) error {
+ return c.ReplyMethodNotImplemented(ctx, "com.openuc2.deviceadmin.openuc2.UpdatePSKDropInFile")
+}
+
+// RegenerateConnProfile reassembles the file for the NetworkManager connection profile (specified by
+// its file-based name, e.g. "wlan0-hotspot") from its constituent drop-in snippet files.
+// This operation does not try to make NetworkManager reload the updated connection profile.
+func (s *VarlinkInterface) RegenerateDropInConnProfile(ctx context.Context, c VarlinkCall, connProfile_ string) error {
+ return c.ReplyMethodNotImplemented(ctx, "com.openuc2.deviceadmin.openuc2.RegenerateDropInConnProfile")
+}
+
+// Generated method call dispatcher
+
+func (s *VarlinkInterface) VarlinkDispatch(ctx context.Context, call varlink.Call, methodname string) error {
+ switch methodname {
+ case "UpdatePSKDropInFile":
+ var in struct {
+ ConnProfile string `json:"connProfile"`
+ NewPw string `json:"newPw"`
+ }
+ err := call.GetParameters(&in)
+ if err != nil {
+ return call.ReplyInvalidParameter(ctx, "parameters")
+ }
+ return s.comopenuc2deviceadminopenuc2Interface.UpdatePSKDropInFile(ctx, VarlinkCall{call}, in.ConnProfile, in.NewPw)
+
+ case "RegenerateDropInConnProfile":
+ var in struct {
+ ConnProfile string `json:"connProfile"`
+ }
+ err := call.GetParameters(&in)
+ if err != nil {
+ return call.ReplyInvalidParameter(ctx, "parameters")
+ }
+ return s.comopenuc2deviceadminopenuc2Interface.RegenerateDropInConnProfile(ctx, VarlinkCall{call}, in.ConnProfile)
+
+ default:
+ return call.ReplyMethodNotFound(ctx, methodname)
+ }
+}
+
+// Generated varlink interface name
+
+func (s *VarlinkInterface) VarlinkGetName() string {
+ return `com.openuc2.deviceadmin.openuc2`
+}
+
+// Generated varlink interface description
+
+func (s *VarlinkInterface) VarlinkGetDescription() string {
+ return `# com.openuc2.deviceadmin.openuc2 manages openUC2 OS-specific settings.
+interface com.openuc2.deviceadmin.openuc2
+
+# The service was unable to perform the requested operation for an unspecified reason.
+error Unknown (description: string)
+
+# NetworkManager
+
+# UpdatePSKDropInFile updates the specified connection profile (as specified via file-based name,
+# e.g. "wlan0-hotspot")'s Wi-Fi PSK drop-in snippet file (which is automatically determined)
+# with the specified cleartext password.
+# This operation does not try to regenerate the connection profile itself from the drop-in files.
+method UpdatePSKDropInFile(connProfile: string, newPw: string) -> ()
+
+# RegenerateConnProfile reassembles the file for the NetworkManager connection profile (specified by
+# its file-based name, e.g. "wlan0-hotspot") from its constituent drop-in snippet files.
+# This operation does not try to make NetworkManager reload the updated connection profile.
+method RegenerateDropInConnProfile(connProfile: string) -> ()
+`
+}
+
+// Generated service interface
+
+type VarlinkInterface struct {
+ comopenuc2deviceadminopenuc2Interface
+}
+
+func VarlinkNew(m comopenuc2deviceadminopenuc2Interface) *VarlinkInterface {
+ return &VarlinkInterface{m}
+}
diff --git a/internal/app/ipc/openuc2/generate.go b/internal/app/ipc/openuc2/generate.go
new file mode 100644
index 0000000..ac4fb37
--- /dev/null
+++ b/internal/app/ipc/openuc2/generate.go
@@ -0,0 +1,3 @@
+package comopenuc2deviceadminopenuc2
+
+//go:generate go tool varlink-go-interface-generator com.openuc2.deviceadmin.openuc2.varlink
diff --git a/internal/app/server/routes/boot/routes.go b/internal/app/server/routes/boot/routes.go
index ece9999..b103e29 100644
--- a/internal/app/server/routes/boot/routes.go
+++ b/internal/app/server/routes/boot/routes.go
@@ -116,14 +116,8 @@ func shutdownViaSidecar(ctx context.Context, method string, scc *sc.Client, l go
if err != nil {
return errors.Wrap(err, "couldn't open connection to sidecar")
}
- defer func() {
- if conn == nil {
- return
- }
- if err := conn.Close(); err != nil {
- l.Error(errors.New("couldn't close connection to sidecar"))
- }
- }()
+ defer sc.CloseConn(conn, l)
+
switch method {
default:
return errors.Errorf("unknown sidecar method %s", method)
diff --git a/internal/app/server/routes/internet/conn-profiles.go b/internal/app/server/routes/internet/conn-profiles.go
index c139a75..9fc9e67 100644
--- a/internal/app/server/routes/internet/conn-profiles.go
+++ b/internal/app/server/routes/internet/conn-profiles.go
@@ -6,6 +6,7 @@ import (
"math"
"net/http"
"net/url"
+ "path"
"strconv"
"strings"
"time"
@@ -17,7 +18,8 @@ import (
"github.com/sargassum-world/godest/handling"
"github.com/sargassum-world/godest/turbostreams"
- ipc "github.com/openUC2/device-admin/internal/app/ipc/networkmanager"
+ nmipc "github.com/openUC2/device-admin/internal/app/ipc/networkmanager"
+ uc2ipc "github.com/openUC2/device-admin/internal/app/ipc/openuc2"
sh "github.com/openUC2/device-admin/internal/app/server/handling"
nm "github.com/openUC2/device-admin/internal/clients/networkmanager"
sc "github.com/openUC2/device-admin/internal/clients/sidecar"
@@ -51,15 +53,9 @@ func reloadConnProfilesViaSidecar(ctx context.Context, scc *sc.Client, l godest.
if err != nil {
return errors.Wrap(err, "couldn't open connection to sidecar")
}
- defer func() {
- if conn == nil {
- return
- }
- if err := conn.Close(); err != nil {
- l.Error(errors.New("couldn't close connection to sidecar"))
- }
- }()
- if err := ipc.ReloadConnProfiles().Call(ctx, conn); err != nil {
+ defer sc.CloseConn(conn, l)
+
+ if err := nmipc.ReloadConnProfiles().Call(ctx, conn); err != nil {
return errors.Wrap(err, "couldn't call sidecar's ReloadConnProfiles method")
}
return nil
@@ -176,7 +172,13 @@ func (h *Handlers) HandleConnProfilePostByUUID() echo.HandlerFunc {
if err != nil {
return errors.Wrap(err, "couldn't load form parameters")
}
- state := c.FormValue("state")
+ rawTrue := "true"
+ dropInUpdate := c.FormValue("state:drop-in-updated") == rawTrue
+ regenerate := c.FormValue("state:regenerated") == rawTrue
+ reload := c.FormValue("state:reloaded") == rawTrue
+ update := c.FormValue("state:updated") == rawTrue
+ updateType := c.FormValue("update-type")
+ activate := c.FormValue("state:activated") == rawTrue
redirectTarget := c.FormValue("redirect-target")
// Run queries
@@ -185,39 +187,79 @@ func (h *Handlers) HandleConnProfilePostByUUID() echo.HandlerFunc {
// network interface down before bringing it back up), the operation is not interrupted by
// context cancellation from the loss ofthe client-server connection:
ctx := context.Background()
- switch state {
- default:
- return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf(
- "invalid connection profiles state %s", state,
- ))
- case "reloaded":
- if err := reloadConnProfileViaSidecar(ctx, uid, h.scc, h.l); err != nil {
- return errors.Wrapf(err, "couldn't reload connection profile %s", rawUUID)
- }
- case "activated-transiently":
- if err := h.nmc.ActivateConnProfile(ctx, uid); err != nil {
- return errors.Wrapf(err, "couldn't activate connection profile %s", rawUUID)
+ if dropInUpdate {
+ if err := dropInUpdateConnProfileViaSidecar(
+ ctx, uid, c.FormValue("802-11-wireless-security.psk"), h.nmc, h.scc, h.l,
+ ); err != nil {
+ return errors.Wrapf(err, "couldn't regenerate connection profile %s", uid.String())
}
- case "simplified-updated", "simplified-updated-activated":
- if err := updateConnProfile(ctx, uid, "save and apply", formValues, h.nmc); err != nil {
- return errors.Wrapf(err, "couldn't update connection profile %s", rawUUID)
+ }
+ if regenerate {
+ if err := regenerateConnProfileViaSidecar(ctx, uid, h.nmc, h.scc, h.l); err != nil {
+ return errors.Wrapf(err, "couldn't regenerate connection profile %s", uid.String())
}
- if state == "simplified-updated-activated" {
- if err := h.nmc.ActivateConnProfile(ctx, uid); err != nil {
- return errors.Wrapf(err, "couldn't activate connection profile %s", rawUUID)
- }
+ }
+ if reload {
+ if err := reloadConnProfileViaSidecar(ctx, uid, h.scc, h.l); err != nil {
+ return errors.Wrapf(err, "couldn't reload connection profile %s", uid.String())
}
- case "updated":
- updateType := c.FormValue("update-type")
+ }
+ if update {
if err := updateConnProfile(ctx, uid, updateType, formValues, h.nmc); err != nil {
- return errors.Wrapf(err, "couldn't update connection profile %s", rawUUID)
+ return errors.Wrapf(err, "couldn't update connection profile %s", uid.String())
+ }
+ }
+ if activate {
+ if err := h.nmc.ActivateConnProfile(ctx, uid); err != nil {
+ return errors.Wrapf(err, "couldn't activate connection profile %s", uid.String())
}
}
+
// Redirect user
return c.Redirect(http.StatusSeeOther, redirectTarget)
}
}
+func dropInUpdateConnProfileViaSidecar(
+ ctx context.Context, uid uuid.UUID, newPw string, nmc *nm.Client, scc *sc.Client, l godest.Logger,
+) error {
+ conn, err := scc.Open(ctx)
+ if err != nil {
+ return errors.Wrap(err, "couldn't open connection to sidecar")
+ }
+ defer sc.CloseConn(conn, l)
+
+ filename, err := nmc.GetConnProfileFilename(ctx, uid)
+ if err != nil {
+ return err
+ }
+ filename = strings.TrimSuffix(path.Base(filename), ".nmconnection")
+ if err := uc2ipc.UpdatePSKDropInFile().Call(ctx, conn, filename, newPw); err != nil {
+ return errors.Wrap(err, "couldn't call sidecar's UpdatePSKDropInFile method")
+ }
+ return nil
+}
+
+func regenerateConnProfileViaSidecar(
+ ctx context.Context, uid uuid.UUID, nmc *nm.Client, scc *sc.Client, l godest.Logger,
+) error {
+ conn, err := scc.Open(ctx)
+ if err != nil {
+ return errors.Wrap(err, "couldn't open connection to sidecar")
+ }
+ defer sc.CloseConn(conn, l)
+
+ filename, err := nmc.GetConnProfileFilename(ctx, uid)
+ if err != nil {
+ return err
+ }
+ filename = strings.TrimSuffix(path.Base(filename), ".nmconnection")
+ if err := uc2ipc.RegenerateDropInConnProfile().Call(ctx, conn, filename); err != nil {
+ return errors.Wrap(err, "couldn't call sidecar's RegenerateConnProfile method")
+ }
+ return nil
+}
+
func reloadConnProfileViaSidecar(
ctx context.Context, uid uuid.UUID, scc *sc.Client, l godest.Logger,
) error {
@@ -225,16 +267,11 @@ func reloadConnProfileViaSidecar(
if err != nil {
return errors.Wrap(err, "couldn't open connection to sidecar")
}
- defer func() {
- if conn == nil {
- return
- }
- if err := conn.Close(); err != nil {
- l.Error(errors.New("couldn't close connection to sidecar"))
- }
- }()
- if err := ipc.ReloadConnProfile().Call(ctx, conn, uid.String()); err != nil {
- return errors.Wrap(err, "couldn't call sidecar's ReloadConnProfiles method")
+ defer sc.CloseConn(conn, l)
+
+ connProfileName := uid.String()
+ if err := nmipc.ReloadConnProfile().Call(ctx, conn, connProfileName); err != nil {
+ return errors.Wrap(err, "couldn't call sidecar's ReloadConnProfile method")
}
return nil
}
diff --git a/internal/app/server/routes/internet/routes.go b/internal/app/server/routes/internet/routes.go
index 68bcc54..aa92294 100644
--- a/internal/app/server/routes/internet/routes.go
+++ b/internal/app/server/routes/internet/routes.go
@@ -85,10 +85,13 @@ func (h *Handlers) HandleInternetGet() echo.HandlerFunc {
type InternetViewData struct {
NM nm.NetworkManager
- AvailableSSIDs []string
- Wlan0HotspotConnProfile nm.ConnProfile
+ Wlan0HotspotConnProfile nm.ConnProfile
+ Wlan0Device nm.Device
+ Wlan0HotspotPasswordInsecure bool
+
Wlan1InternetConnProfile nm.ConnProfile
Wlan1Device nm.Device
+ AvailableSSIDs []string
WifiDevices []nm.Device
EthernetDevices []nm.Device
@@ -119,16 +122,30 @@ func getInternetViewData(ctx context.Context, nmc *nm.Client) (vd InternetViewDa
}
slices.Sort(vd.AvailableSSIDs)
+ if err := collectDevices(ctx, nmc, &vd); err != nil {
+ return vd, err
+ }
+ if err := collectConnProfiles(ctx, nmc, &vd); err != nil {
+ return vd, err
+ }
+
+ return vd, nil
+}
+
+func collectDevices(ctx context.Context, nmc *nm.Client, vd *InternetViewData) error {
allDevices, err := nmc.GetDevices(ctx)
if err != nil {
- return vd, errors.Wrap(err, "couldn't list network devices")
+ return errors.Wrap(err, "couldn't list network devices")
}
for _, device := range allDevices {
switch device.Type.Info().Short {
default:
vd.OtherDevices = append(vd.OtherDevices, device)
case "wifi":
- if cmp.Or(device.IpInterface, device.ControlInterface) == "wlan1" {
+ switch cmp.Or(device.IpInterface, device.ControlInterface) {
+ case "wlan0":
+ vd.Wlan0Device = device
+ case "wlan1":
vd.Wlan1Device = device
}
vd.WifiDevices = append(vd.WifiDevices, device)
@@ -136,10 +153,13 @@ func getInternetViewData(ctx context.Context, nmc *nm.Client) (vd InternetViewDa
vd.EthernetDevices = append(vd.EthernetDevices, device)
}
}
+ return nil
+}
+func collectConnProfiles(ctx context.Context, nmc *nm.Client, vd *InternetViewData) error {
connProfiles, err := nmc.ListConnProfiles(ctx)
if err != nil {
- return vd, errors.Wrap(err, "couldn't list connection profiles")
+ return errors.Wrap(err, "couldn't list connection profiles")
}
for _, connProfile := range connProfiles {
switch connProfile.Settings.Conn.Type.Info().Short {
@@ -157,8 +177,7 @@ func getInternetViewData(ctx context.Context, nmc *nm.Client) (vd InternetViewDa
vd.OtherConnProfiles = append(vd.OtherConnProfiles, connProfile.Settings.Conn)
}
}
-
- return vd, nil
+ return nil
}
func (h *Handlers) HandleInternetPub() turbostreams.HandlerFunc {
diff --git a/internal/app/sidecar/handling/varlink.go b/internal/app/sidecar/handling/varlink.go
new file mode 100644
index 0000000..cf75796
--- /dev/null
+++ b/internal/app/sidecar/handling/varlink.go
@@ -0,0 +1,35 @@
+// Package handling provides utilities for handlers
+package handling
+
+import (
+ "context"
+ "encoding/json"
+
+ "github.com/pkg/errors"
+ "github.com/sargassum-world/godest"
+)
+
+func LogMethod(request *[]byte, l godest.Logger) {
+ if request != nil {
+ var req struct {
+ Method string `json:"method"`
+ }
+ if err := json.Unmarshal(*request, &req); err == nil {
+ l.Info(req.Method)
+ }
+ }
+}
+
+type UnknownErrorReplier interface {
+ ReplyUnknown(ctx context.Context, description string) error
+}
+
+func ReportUnknownError(
+ ctx context.Context, errReplier UnknownErrorReplier, err error, l godest.Logger,
+) error {
+ l.Error(err)
+ if replyErr := errReplier.ReplyUnknown(ctx, err.Error()); replyErr != nil {
+ return errors.Wrapf(replyErr, "couldn't report error (%s)", err.Error())
+ }
+ return nil
+}
diff --git a/internal/app/sidecar/routes/boot/routes.go b/internal/app/sidecar/routes/boot/routes.go
index 1d4a3ea..c8c8388 100644
--- a/internal/app/sidecar/routes/boot/routes.go
+++ b/internal/app/sidecar/routes/boot/routes.go
@@ -2,13 +2,12 @@ package boot
import (
"context"
- "encoding/json"
- "github.com/pkg/errors"
"github.com/sargassum-world/godest"
"github.com/varlink/go/varlink"
ipc "github.com/openUC2/device-admin/internal/app/ipc/boot"
+ "github.com/openUC2/device-admin/internal/app/sidecar/handling"
sd "github.com/openUC2/device-admin/internal/clients/systemd"
)
@@ -32,64 +31,28 @@ func (h *Handlers) Register(service *varlink.Service) error {
}
func (h *Handlers) Poweroff(ctx context.Context, call ipc.VarlinkCall) error {
- if call.Request != nil {
- var req struct {
- Method string `json:"method"`
- }
- if err := json.Unmarshal(*call.Request, &req); err == nil {
- h.l.Info(req.Method)
- }
- }
+ handling.LogMethod(call.Request, h.l)
if err := h.sdc.Poweroff(ctx); err != nil {
- if replyErr := call.ReplyError(
- ctx, "com.openuc2.deviceadmin.boot.Unknown", ipc.Unknown{Description: err.Error()},
- ); replyErr != nil {
- h.l.Error(err)
- return errors.Wrapf(replyErr, "couldn't report error (%s) in method call reply", err.Error())
- }
+ return handling.ReportUnknownError(ctx, &call, err, h.l)
}
return call.ReplyPoweroff(ctx)
}
func (h *Handlers) Reboot(ctx context.Context, call ipc.VarlinkCall) error {
- if call.Request != nil {
- var req struct {
- Method string `json:"method"`
- }
- if err := json.Unmarshal(*call.Request, &req); err == nil {
- h.l.Info(req.Method)
- }
- }
+ handling.LogMethod(call.Request, h.l)
if err := h.sdc.Reboot(ctx); err != nil {
- if replyErr := call.ReplyError(
- ctx, "com.openuc2.deviceadmin.boot.Unknown", ipc.Unknown{Description: err.Error()},
- ); replyErr != nil {
- h.l.Error(err)
- return errors.Wrapf(replyErr, "couldn't report error (%s) in method call reply", err.Error())
- }
+ return handling.ReportUnknownError(ctx, &call, err, h.l)
}
return call.ReplyReboot(ctx)
}
func (h *Handlers) SoftReboot(ctx context.Context, call ipc.VarlinkCall) error {
- if call.Request != nil {
- var req struct {
- Method string `json:"method"`
- }
- if err := json.Unmarshal(*call.Request, &req); err == nil {
- h.l.Info(req.Method)
- }
- }
+ handling.LogMethod(call.Request, h.l)
if err := h.sdc.SoftReboot(ctx); err != nil {
- if replyErr := call.ReplyError(
- ctx, "com.openuc2.deviceadmin.boot.Unknown", ipc.Unknown{Description: err.Error()},
- ); replyErr != nil {
- h.l.Error(err)
- return errors.Wrapf(replyErr, "couldn't report error (%s) in method call reply", err.Error())
- }
+ return handling.ReportUnknownError(ctx, &call, err, h.l)
}
return call.ReplySoftReboot(ctx)
}
diff --git a/internal/app/sidecar/routes/networkmanager/routes.go b/internal/app/sidecar/routes/networkmanager/routes.go
index aa92272..2b12fbe 100644
--- a/internal/app/sidecar/routes/networkmanager/routes.go
+++ b/internal/app/sidecar/routes/networkmanager/routes.go
@@ -2,7 +2,6 @@ package networkmanager
import (
"context"
- "encoding/json"
"github.com/google/uuid"
"github.com/pkg/errors"
@@ -10,6 +9,7 @@ import (
"github.com/varlink/go/varlink"
ipc "github.com/openUC2/device-admin/internal/app/ipc/networkmanager"
+ "github.com/openUC2/device-admin/internal/app/sidecar/handling"
nm "github.com/openUC2/device-admin/internal/clients/networkmanager"
)
@@ -33,23 +33,10 @@ func (h *Handlers) Register(service *varlink.Service) error {
}
func (h *Handlers) ReloadConnProfiles(ctx context.Context, call ipc.VarlinkCall) error {
- if call.Request != nil {
- var req struct {
- Method string `json:"method"`
- }
- if err := json.Unmarshal(*call.Request, &req); err == nil {
- h.l.Info(req.Method)
- }
- }
+ handling.LogMethod(call.Request, h.l)
if err := h.nmc.ReloadConnProfiles(ctx); err != nil {
- if replyErr := call.ReplyError(
- ctx, "com.openuc2.deviceadmin.networkmanager.Unknown", ipc.Unknown{Description: err.Error()},
- ); replyErr != nil {
- h.l.Error(err)
- return errors.Wrapf(replyErr, "couldn't report error (%s) in method call reply", err.Error())
- }
- return err
+ return handling.ReportUnknownError(ctx, &call, err, h.l)
}
return call.ReplyReloadConnProfiles(ctx)
}
@@ -57,35 +44,16 @@ func (h *Handlers) ReloadConnProfiles(ctx context.Context, call ipc.VarlinkCall)
func (h *Handlers) ReloadConnProfile(
ctx context.Context, call ipc.VarlinkCall, rawUUID string,
) error {
- if call.Request != nil {
- var req struct {
- Method string `json:"method"`
- }
- if err := json.Unmarshal(*call.Request, &req); err == nil {
- h.l.Info(req.Method)
- }
- }
+ handling.LogMethod(call.Request, h.l)
uid, err := uuid.Parse(rawUUID)
if err != nil {
- err = errors.Wrapf(err, "couldn't parse uuid %s", rawUUID)
- if replyErr := call.ReplyError(
- ctx, "com.openuc2.deviceadmin.networkmanager.InvalidUUID",
- ipc.InvalidUUID{Description: err.Error()},
- ); replyErr != nil {
- h.l.Error(err)
- return errors.Wrapf(replyErr, "couldn't report error (%s) in method call reply", err.Error())
- }
- return err
+ return handling.ReportUnknownError(ctx, &call, errors.Wrapf(
+ err, "couldn't parse uuid %s", rawUUID,
+ ), h.l)
}
if err := h.nmc.ReloadConnProfile(ctx, uid); err != nil {
- if replyErr := call.ReplyError(
- ctx, "com.openuc2.deviceadmin.networkmanager.Unknown", ipc.Unknown{Description: err.Error()},
- ); replyErr != nil {
- h.l.Error(err)
- return errors.Wrapf(replyErr, "couldn't report error (%s) in method call reply", err.Error())
- }
- return err
+ return handling.ReportUnknownError(ctx, &call, err, h.l)
}
return call.ReplyReloadConnProfiles(ctx)
}
diff --git a/internal/app/sidecar/routes/openuc2/routes.go b/internal/app/sidecar/routes/openuc2/routes.go
new file mode 100644
index 0000000..515c4b3
--- /dev/null
+++ b/internal/app/sidecar/routes/openuc2/routes.go
@@ -0,0 +1,152 @@
+package openuc2
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path"
+ "regexp"
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/sargassum-world/godest"
+ "github.com/varlink/go/varlink"
+
+ ipc "github.com/openUC2/device-admin/internal/app/ipc/openuc2"
+ "github.com/openUC2/device-admin/internal/app/sidecar/handling"
+ sd "github.com/openUC2/device-admin/internal/clients/systemd"
+)
+
+type Handlers struct {
+ ipc.VarlinkInterface
+
+ sdc *sd.Client
+
+ l godest.Logger
+}
+
+func New(sdc *sd.Client, l godest.Logger) *Handlers {
+ return &Handlers{
+ sdc: sdc,
+ l: l,
+ }
+}
+
+func (h *Handlers) Register(service *varlink.Service) error {
+ return service.RegisterInterface(ipc.VarlinkNew(h))
+}
+
+func (h *Handlers) UpdatePSKDropInFile(
+ ctx context.Context, call ipc.VarlinkCall, connProfile string, newPw string,
+) error {
+ handling.LogMethod(call.Request, h.l)
+
+ dropInDir := path.Join("/etc/NetworkManager/system-connections.d", connProfile)
+ fsys, err := os.OpenRoot(dropInDir)
+ if err != nil {
+ return errors.Wrapf(err, "couldn't open drop-in directory %s", dropInDir)
+ }
+ const dropInFile = "51-wifi-security-password.nmconnection"
+ lines, err := readLines(fsys, dropInFile)
+ if err != nil {
+ return handling.ReportUnknownError(ctx, &call, errors.Wrapf(
+ err, "couldn't read PSK drop-in file %s", path.Join(dropInDir, dropInFile),
+ ), h.l)
+ }
+
+ if lines, err = setKey(lines, "psk", newPw); err != nil {
+ return handling.ReportUnknownError(ctx, &call, errors.Wrapf(
+ err, "couldn't update PSK for drop-in file %s", path.Join(dropInDir, dropInFile),
+ ), h.l)
+ }
+
+ const mode = 0o600 // -rw-------
+ if err = writeAtomically(fsys, dropInFile, lines, mode); err != nil {
+ return handling.ReportUnknownError(ctx, &call, errors.Wrapf(
+ err, "couldn't atomically write updated drop-in file %s", path.Join(dropInDir, dropInFile),
+ ), h.l)
+ }
+
+ return call.ReplyUpdatePSKDropInFile(ctx)
+}
+
+func readLines(fsys *os.Root, filePath string) ([]string, error) {
+ contents, err := fsys.ReadFile(filePath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "couldn't read file %s", filePath)
+ }
+
+ return strings.Split(string(contents), "\n"), nil
+}
+
+func setKey(lines []string, key string, newValue string) ([]string, error) {
+ pattern := fmt.Sprintf("^%s[ ]*=", key)
+ re, err := regexp.Compile(pattern)
+ if err != nil {
+ return nil, errors.Wrapf(err, "couldn't compile regexp for key %s", key)
+ }
+ setLine := fmt.Sprintf("%s=%s", key, newValue)
+
+ hasKey := false
+ for i, line := range lines {
+ if !re.MatchString(line) {
+ continue
+ }
+ hasKey = true
+ lines[i] = setLine
+ }
+ if !hasKey {
+ lines = append(lines, setLine)
+ }
+ return lines, nil
+}
+
+func writeAtomically(fsys *os.Root, filePath string, lines []string, perm os.FileMode) error {
+ data := []byte(strings.Join(lines, "\n"))
+ swapFilePath := filePath + ".swp"
+ if err := fsys.WriteFile(swapFilePath, data, perm.Perm()); err != nil {
+ return errors.Wrapf(
+ err, "couldn't write drop-in file %s to swap file %s", filePath, swapFilePath,
+ )
+ }
+ if err := fsys.Rename(swapFilePath, filePath); err != nil {
+ return errors.Wrapf(
+ err, "couldn't move temporary drop-in file %s to %s", swapFilePath, filePath,
+ )
+ }
+ return nil
+}
+
+func (h *Handlers) RegenerateDropInConnProfile(
+ ctx context.Context, call ipc.VarlinkCall, connProfile string,
+) error {
+ handling.LogMethod(call.Request, h.l)
+
+ templatedAssembleUnit := fmt.Sprintf(
+ "assemble-networkmanager-connection-templated@%s.service", connProfile,
+ )
+ hasTemplatedAssemble, err := h.sdc.UnitExists(ctx, templatedAssembleUnit)
+ if err != nil {
+ return handling.ReportUnknownError(ctx, &call, errors.Wrapf(
+ err, "couldn't check whether templated drop-in assembly service %s exists for %s",
+ templatedAssembleUnit, connProfile,
+ ), h.l)
+ }
+ if hasTemplatedAssemble {
+ if err := h.sdc.RestartUnit(ctx, templatedAssembleUnit); err != nil {
+ return handling.ReportUnknownError(ctx, &call, errors.Wrapf(
+ err, "couldn't restart templated drop-in assembly service %s for %s",
+ templatedAssembleUnit, connProfile,
+ ), h.l)
+ }
+ }
+
+ assembleUnit := fmt.Sprintf("assemble-networkmanager-connection@%s.service", connProfile)
+ if err := h.sdc.RestartUnit(ctx, assembleUnit); err != nil {
+ return handling.ReportUnknownError(ctx, &call, errors.Wrapf(
+ err, "couldn't restart drop-in assembly service %s for %s", assembleUnit, connProfile,
+ ), h.l)
+ }
+
+ return call.ReplyRegenerateDropInConnProfile(ctx)
+}
diff --git a/internal/app/sidecar/routes/routes.go b/internal/app/sidecar/routes/routes.go
index dc3ceea..6d86356 100644
--- a/internal/app/sidecar/routes/routes.go
+++ b/internal/app/sidecar/routes/routes.go
@@ -7,6 +7,7 @@ import (
"github.com/openUC2/device-admin/internal/app/sidecar/client"
"github.com/openUC2/device-admin/internal/app/sidecar/routes/boot"
"github.com/openUC2/device-admin/internal/app/sidecar/routes/networkmanager"
+ "github.com/openUC2/device-admin/internal/app/sidecar/routes/openuc2"
)
type Handlers struct {
@@ -27,5 +28,8 @@ func (s *Handlers) Register(service *varlink.Service) error {
if err := networkmanager.New(s.globals.NetworkManager, l).Register(service); err != nil {
return errors.Wrap(err, "couldn't register networkmanager handlers")
}
+ if err := openuc2.New(s.globals.Systemd, l).Register(service); err != nil {
+ return errors.Wrap(err, "couldn't register openUC2 OS handlers")
+ }
return nil
}
diff --git a/internal/clients/networkmanager/conn-profiles.go b/internal/clients/networkmanager/conn-profiles.go
index 7f56b97..1cb739a 100644
--- a/internal/clients/networkmanager/conn-profiles.go
+++ b/internal/clients/networkmanager/conn-profiles.go
@@ -140,18 +140,9 @@ func (c *Client) ReloadConnProfiles(ctx context.Context) error {
}
func (c *Client) ReloadConnProfile(ctx context.Context, uid uuid.UUID) error {
- conno, err := c.findConnProfileByUUID(ctx, uid)
+ filename, err := c.GetConnProfileFilename(ctx, uid)
if err != nil {
- return errors.Wrapf(err, "couldn't find connection profile with uuid %s", uid)
- }
-
- var filename string
- const connName = nmName + ".Settings.Connection"
- if err = conno.StoreProperty(connName+".Filename", &filename); err != nil {
- return errors.Wrap(err, "couldn't query for filename")
- }
- if filename == "" {
- return errors.Wrapf(err, "connection with uuid %s is not backed by a file!", uid)
+ return errors.Wrapf(err, "couldn't determine filename of connection with uuid %s", uid)
}
nm := c.getNetworkManagerSettings()
@@ -169,6 +160,24 @@ func (c *Client) ReloadConnProfile(ctx context.Context, uid uuid.UUID) error {
return nil
}
+func (c *Client) GetConnProfileFilename(
+ ctx context.Context, uid uuid.UUID,
+) (filename string, err error) {
+ conno, err := c.findConnProfileByUUID(ctx, uid)
+ if err != nil {
+ return "", errors.Wrapf(err, "couldn't find connection profile with uuid %s", uid)
+ }
+
+ const connName = nmName + ".Settings.Connection"
+ if err = conno.StoreProperty(connName+".Filename", &filename); err != nil {
+ return "", errors.Wrap(err, "couldn't query for filename")
+ }
+ if filename == "" {
+ return "", errors.Wrapf(err, "connection with uuid %s is not backed by a file!", uid)
+ }
+ return filename, nil
+}
+
func (c *Client) ActivateConnProfile(ctx context.Context, uid uuid.UUID) error {
nm := c.getNetworkManager()
conno, err := c.findConnProfileByUUID(ctx, uid)
diff --git a/internal/clients/sidecar/client.go b/internal/clients/sidecar/client.go
index 41d27c0..f56fb42 100644
--- a/internal/clients/sidecar/client.go
+++ b/internal/clients/sidecar/client.go
@@ -5,6 +5,7 @@ import (
"context"
"github.com/pkg/errors"
+ "github.com/sargassum-world/godest"
"github.com/varlink/go/varlink"
)
@@ -26,3 +27,12 @@ func (c *Client) Open(ctx context.Context) (conn *varlink.Connection, err error)
}
return conn, nil
}
+
+func CloseConn(conn *varlink.Connection, l godest.Logger) {
+ if conn == nil {
+ return
+ }
+ if err := conn.Close(); err != nil {
+ l.Error(errors.New("couldn't close connection to sidecar"))
+ }
+}
diff --git a/internal/clients/systemd/client.go b/internal/clients/systemd/client.go
index 916352e..f1c3956 100644
--- a/internal/clients/systemd/client.go
+++ b/internal/clients/systemd/client.go
@@ -33,7 +33,10 @@ func (c *Client) Open(ctx context.Context) (err error) {
return nil
}
-const sdName = "org.freedesktop.systemd1"
+const (
+ sdName = "org.freedesktop.systemd1"
+ sdManagerName = "org.freedesktop.systemd1.Manager"
+)
func (c *Client) getSystemdManager() dbus.BusObject {
return c.bus.Object(sdName, "/org/freedesktop/systemd1")
@@ -43,7 +46,7 @@ func (c *Client) getSystemdManager() dbus.BusObject {
func (c *Client) Poweroff(ctx context.Context) error {
sdm := c.getSystemdManager()
- if err := sdm.CallWithContext(ctx, sdName+".Manager.PowerOff", 0).Store(); err != nil {
+ if err := sdm.CallWithContext(ctx, sdManagerName+".PowerOff", 0).Store(); err != nil {
return errors.Wrap(err, "couldn't power-off")
}
return nil
@@ -51,7 +54,7 @@ func (c *Client) Poweroff(ctx context.Context) error {
func (c *Client) Reboot(ctx context.Context) error {
sdm := c.getSystemdManager()
- if err := sdm.CallWithContext(ctx, sdName+".Manager.Reboot", 0).Store(); err != nil {
+ if err := sdm.CallWithContext(ctx, sdManagerName+".Reboot", 0).Store(); err != nil {
return errors.Wrap(err, "couldn't reboot")
}
return nil
@@ -59,8 +62,33 @@ func (c *Client) Reboot(ctx context.Context) error {
func (c *Client) SoftReboot(ctx context.Context) error {
sd := c.getSystemdManager()
- if err := sd.CallWithContext(ctx, sdName+".Manager.SoftReboot", 0, "").Store(); err != nil {
+ if err := sd.CallWithContext(ctx, sdManagerName+".SoftReboot", 0, "").Store(); err != nil {
return errors.Wrap(err, "couldn't soft-reboot")
}
return nil
}
+
+// Units
+
+func (c *Client) UnitExists(ctx context.Context, name string) (bool, error) {
+ sd := c.getSystemdManager()
+ var unitPath dbus.ObjectPath
+ err := sd.CallWithContext(
+ ctx, sdManagerName+".GetUnit", 0, name,
+ ).Store(&unitPath)
+ if err != nil {
+ return false, errors.Wrapf(err, "couldn't find %s", name)
+ }
+ return true, nil
+}
+
+func (c *Client) RestartUnit(ctx context.Context, name string) error {
+ sd := c.getSystemdManager()
+ var jobPath dbus.ObjectPath
+ if err := sd.CallWithContext(
+ ctx, sdManagerName+".RestartUnit", 0, name, "replace",
+ ).Store(&jobPath); err != nil {
+ return errors.Wrapf(err, "couldn't restart %s", name)
+ }
+ return nil
+}
diff --git a/web/app/package.json b/web/app/package.json
index 65526c8..5d7e47b 100644
--- a/web/app/package.json
+++ b/web/app/package.json
@@ -8,7 +8,7 @@
"lint": "prettier --check --write ./src/**/*.{js,html} --no-error-on-unmatched-pattern && eslint src/**/*.{js} --no-error-on-unmatched-pattern",
"lint:fix": "prettier --write ./src/**/*.{js,html} --no-error-on-unmatched-pattern && eslint --fix src/**/*.{js} --no-error-on-unmatched-pattern"
},
- "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be",
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
"devDependencies": {
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^10.0.1",
diff --git a/web/app/pnpm-workspace.yaml b/web/app/pnpm-workspace.yaml
new file mode 100644
index 0000000..620a7bb
--- /dev/null
+++ b/web/app/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+allowBuilds:
+ '@parcel/watcher': true
diff --git a/web/templates/internet/conn-profiles/index.page.tmpl b/web/templates/internet/conn-profiles/index.page.tmpl
index 741ab5d..c379c46 100644
--- a/web/templates/internet/conn-profiles/index.page.tmpl
+++ b/web/templates/internet/conn-profiles/index.page.tmpl
@@ -107,7 +107,7 @@
data-form-submission-target="submitter"
class="mt-4 mb-3"
>
-
+
-
+
-
+
+
+