mirror of
https://github.com/0x1d/rcond.git
synced 2025-12-14 18:25:21 +01:00
feat: add reboot and shutdown
This commit is contained in:
@@ -6,6 +6,7 @@ A simple daemon and REST API designed to simplify the management of various syst
|
|||||||
- Network connections: Utilizing NetworkManager's D-Bus interface to dynamically configure network connections
|
- Network connections: Utilizing NetworkManager's D-Bus interface to dynamically configure network connections
|
||||||
- System hostname: Dynamically update the system's hostname
|
- System hostname: Dynamically update the system's hostname
|
||||||
- Authorized SSH keys: Directly managing the user's authorized_keys file to securely add, remove, or modify authorized SSH keys
|
- Authorized SSH keys: Directly managing the user's authorized_keys file to securely add, remove, or modify authorized SSH keys
|
||||||
|
- System state: Restart and shutdown the system
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
@@ -85,6 +86,8 @@ All endpoints except `/health` require authentication via an API token passed in
|
|||||||
| POST | `/hostname` | Set the hostname |
|
| POST | `/hostname` | Set the hostname |
|
||||||
| POST | `/users/{user}/keys` | Add an authorized SSH key |
|
| POST | `/users/{user}/keys` | Add an authorized SSH key |
|
||||||
| DELETE | `/users/{user}/keys/{fingerprint}` | Remove an authorized SSH key |
|
| DELETE | `/users/{user}/keys/{fingerprint}` | Remove an authorized SSH key |
|
||||||
|
| POST | `/system/restart` | Restart the system |
|
||||||
|
| POST | `/system/shutdown` | Shutdown the system |
|
||||||
|
|
||||||
### Response Codes
|
### Response Codes
|
||||||
|
|
||||||
|
|||||||
@@ -494,3 +494,46 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/Error'
|
$ref: '#/components/schemas/Error'
|
||||||
|
/system/restart:
|
||||||
|
post:
|
||||||
|
summary: Restart system
|
||||||
|
description: Restarts the system
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: System restarted successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
example: "success"
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
/system/shutdown:
|
||||||
|
post:
|
||||||
|
summary: Shutdown system
|
||||||
|
description: Shuts down the system
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: System shut down successfully
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
example: "success"
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
network "github.com/0x1d/rcond/pkg/network"
|
network "github.com/0x1d/rcond/pkg/network"
|
||||||
|
"github.com/0x1d/rcond/pkg/system"
|
||||||
"github.com/0x1d/rcond/pkg/user"
|
"github.com/0x1d/rcond/pkg/user"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
@@ -256,3 +257,33 @@ func HandleRemoveAuthorizedKey(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HandleReboot(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := system.Restart(); err != nil {
|
||||||
|
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleShutdown(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := system.Shutdown(); err != nil {
|
||||||
|
writeError(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
json.NewEncoder(w).Encode(map[string]string{"status": "success"})
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ func (s *Server) RegisterRoutes() {
|
|||||||
s.router.HandleFunc("/hostname", s.verifyToken(HandleSetHostname)).Methods(http.MethodPost)
|
s.router.HandleFunc("/hostname", s.verifyToken(HandleSetHostname)).Methods(http.MethodPost)
|
||||||
s.router.HandleFunc("/users/{user}/keys", s.verifyToken(HandleAddAuthorizedKey)).Methods(http.MethodPost)
|
s.router.HandleFunc("/users/{user}/keys", s.verifyToken(HandleAddAuthorizedKey)).Methods(http.MethodPost)
|
||||||
s.router.HandleFunc("/users/{user}/keys/{fingerprint}", s.verifyToken(HandleRemoveAuthorizedKey)).Methods(http.MethodDelete)
|
s.router.HandleFunc("/users/{user}/keys/{fingerprint}", s.verifyToken(HandleRemoveAuthorizedKey)).Methods(http.MethodDelete)
|
||||||
|
s.router.HandleFunc("/system/restart", s.verifyToken(HandleReboot)).Methods(http.MethodPost)
|
||||||
|
s.router.HandleFunc("/system/shutdown", s.verifyToken(HandleShutdown)).Methods(http.MethodPost)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/0x1d/rcond/pkg/system"
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@@ -64,21 +65,6 @@ func DefaultAPConfig(uuid uuid.UUID, ssid string, password string, autoconnect b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// withDbus executes the given function with a D-Bus system connection
|
|
||||||
// and handles any connection errors
|
|
||||||
func withDbus(fn func(*dbus.Conn) error) error {
|
|
||||||
conn, err := dbus.SystemBus()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to connect to system bus: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := fn(conn); err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActivateConnection activates a NetworkManager connection profile.
|
// ActivateConnection activates a NetworkManager connection profile.
|
||||||
// It takes a D-Bus connection, connection profile path, and device path as arguments.
|
// It takes a D-Bus connection, connection profile path, and device path as arguments.
|
||||||
// The function waits up to 10 seconds for the connection to become active.
|
// The function waits up to 10 seconds for the connection to become active.
|
||||||
@@ -259,6 +245,7 @@ func AddConnectionWithConfig(conn *dbus.Conn, cfg *ConnectionConfig) (dbus.Objec
|
|||||||
var wirelessMap map[string]dbus.Variant
|
var wirelessMap map[string]dbus.Variant
|
||||||
if cfg.Mode == "ap" {
|
if cfg.Mode == "ap" {
|
||||||
wirelessMap = map[string]dbus.Variant{
|
wirelessMap = map[string]dbus.Variant{
|
||||||
|
"ssid": dbus.MakeVariant([]byte(cfg.SSID)),
|
||||||
"mode": dbus.MakeVariant(cfg.Mode),
|
"mode": dbus.MakeVariant(cfg.Mode),
|
||||||
"band": dbus.MakeVariant(cfg.Band),
|
"band": dbus.MakeVariant(cfg.Band),
|
||||||
"channel": dbus.MakeVariant(cfg.Channel),
|
"channel": dbus.MakeVariant(cfg.Channel),
|
||||||
@@ -266,6 +253,7 @@ func AddConnectionWithConfig(conn *dbus.Conn, cfg *ConnectionConfig) (dbus.Objec
|
|||||||
} else {
|
} else {
|
||||||
wirelessMap = map[string]dbus.Variant{
|
wirelessMap = map[string]dbus.Variant{
|
||||||
"ssid": dbus.MakeVariant([]byte(cfg.SSID)),
|
"ssid": dbus.MakeVariant([]byte(cfg.SSID)),
|
||||||
|
"mode": dbus.MakeVariant(cfg.Mode),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +323,7 @@ func GetHostname() (string, error) {
|
|||||||
// SetHostname changes the static hostname via the system bus.
|
// SetHostname changes the static hostname via the system bus.
|
||||||
// newHost is your desired hostname, interactive=false skips any prompt.
|
// newHost is your desired hostname, interactive=false skips any prompt.
|
||||||
func SetHostname(newHost string) error {
|
func SetHostname(newHost string) error {
|
||||||
return withDbus(func(conn *dbus.Conn) error {
|
return system.WithDbus(func(conn *dbus.Conn) error {
|
||||||
obj := conn.Object(
|
obj := conn.Object(
|
||||||
"org.freedesktop.hostname1",
|
"org.freedesktop.hostname1",
|
||||||
dbus.ObjectPath("/org/freedesktop/hostname1"),
|
dbus.ObjectPath("/org/freedesktop/hostname1"),
|
||||||
@@ -356,7 +344,7 @@ func SetHostname(newHost string) error {
|
|||||||
func ConfigureSTA(iface string, ssid string, password string, autoconnect bool) (string, error) {
|
func ConfigureSTA(iface string, ssid string, password string, autoconnect bool) (string, error) {
|
||||||
uuid := uuid.New()
|
uuid := uuid.New()
|
||||||
|
|
||||||
err := withDbus(func(conn *dbus.Conn) error {
|
err := system.WithDbus(func(conn *dbus.Conn) error {
|
||||||
_, err := AddStationConnection(conn, uuid, ssid, password, autoconnect)
|
_, err := AddStationConnection(conn, uuid, ssid, password, autoconnect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create station connection: %v", err)
|
return fmt.Errorf("failed to create station connection: %v", err)
|
||||||
@@ -378,7 +366,7 @@ func ConfigureSTA(iface string, ssid string, password string, autoconnect bool)
|
|||||||
func ConfigureAP(iface string, ssid string, password string, autoconnect bool) (string, error) {
|
func ConfigureAP(iface string, ssid string, password string, autoconnect bool) (string, error) {
|
||||||
uuid := uuid.New()
|
uuid := uuid.New()
|
||||||
|
|
||||||
err := withDbus(func(conn *dbus.Conn) error {
|
err := system.WithDbus(func(conn *dbus.Conn) error {
|
||||||
_, err := AddAccessPointConnection(conn, uuid, ssid, password, autoconnect)
|
_, err := AddAccessPointConnection(conn, uuid, ssid, password, autoconnect)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create access point connection: %v", err)
|
return fmt.Errorf("failed to create access point connection: %v", err)
|
||||||
@@ -399,7 +387,7 @@ func ConfigureAP(iface string, ssid string, password string, autoconnect bool) (
|
|||||||
// The connection will be activated on the specified interface.
|
// The connection will be activated on the specified interface.
|
||||||
// Returns an error if any operation fails.
|
// Returns an error if any operation fails.
|
||||||
func Up(iface string, uuid string) error {
|
func Up(iface string, uuid string) error {
|
||||||
return withDbus(func(conn *dbus.Conn) error {
|
return system.WithDbus(func(conn *dbus.Conn) error {
|
||||||
connPath, err := GetConnectionPath(conn, uuid)
|
connPath, err := GetConnectionPath(conn, uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -429,7 +417,7 @@ func Up(iface string, uuid string) error {
|
|||||||
// It takes the interface name as an argument.
|
// It takes the interface name as an argument.
|
||||||
// Returns an error if the device cannot be found or disconnected.
|
// Returns an error if the device cannot be found or disconnected.
|
||||||
func Down(iface string) error {
|
func Down(iface string) error {
|
||||||
return withDbus(func(conn *dbus.Conn) error {
|
return system.WithDbus(func(conn *dbus.Conn) error {
|
||||||
devPath, err := GetDeviceByIpIface(conn, iface)
|
devPath, err := GetDeviceByIpIface(conn, iface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -447,7 +435,7 @@ func Down(iface string) error {
|
|||||||
// If no connection with the UUID exists, it returns nil.
|
// If no connection with the UUID exists, it returns nil.
|
||||||
// Returns an error if the connection exists but cannot be deleted.
|
// Returns an error if the connection exists but cannot be deleted.
|
||||||
func Remove(uuid string) error {
|
func Remove(uuid string) error {
|
||||||
return withDbus(func(conn *dbus.Conn) error {
|
return system.WithDbus(func(conn *dbus.Conn) error {
|
||||||
connPath, err := GetConnectionPath(conn, uuid)
|
connPath, err := GetConnectionPath(conn, uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
23
pkg/system/dbus.go
Normal file
23
pkg/system/dbus.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithDbus executes the given function with a D-Bus system connection
|
||||||
|
// and handles any connection errors
|
||||||
|
func WithDbus(fn func(*dbus.Conn) error) error {
|
||||||
|
conn, err := dbus.SystemBus()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to connect to system bus: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := fn(conn); err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
33
pkg/system/state.go
Normal file
33
pkg/system/state.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Restart restarts the system.
|
||||||
|
func Restart() error {
|
||||||
|
return WithDbus(func(conn *dbus.Conn) error {
|
||||||
|
obj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
|
||||||
|
log.Println("Rebooting system...")
|
||||||
|
call := obj.Call("org.freedesktop.systemd1.Manager.Reboot", 0)
|
||||||
|
if call.Err != nil {
|
||||||
|
log.Fatal(call.Err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the system.
|
||||||
|
func Shutdown() error {
|
||||||
|
return WithDbus(func(conn *dbus.Conn) error {
|
||||||
|
obj := conn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1")
|
||||||
|
log.Println("Shutting down system...")
|
||||||
|
call := obj.Call("org.freedesktop.systemd1.Manager.PowerOff", 0)
|
||||||
|
if call.Err != nil {
|
||||||
|
log.Fatal(call.Err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user