mirror of
https://github.com/0x1d/rcond.git
synced 2025-12-14 10:16:50 +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
|
||||
- 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
|
||||
- System state: Restart and shutdown the system
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -85,6 +86,8 @@ All endpoints except `/health` require authentication via an API token passed in
|
||||
| POST | `/hostname` | Set the hostname |
|
||||
| POST | `/users/{user}/keys` | Add 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
|
||||
|
||||
|
||||
@@ -494,3 +494,46 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$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"
|
||||
|
||||
network "github.com/0x1d/rcond/pkg/network"
|
||||
"github.com/0x1d/rcond/pkg/system"
|
||||
"github.com/0x1d/rcond/pkg/user"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -256,3 +257,33 @@ func HandleRemoveAuthorizedKey(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
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("/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("/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) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/0x1d/rcond/pkg/system"
|
||||
"github.com/godbus/dbus/v5"
|
||||
"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.
|
||||
// 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.
|
||||
@@ -259,6 +245,7 @@ func AddConnectionWithConfig(conn *dbus.Conn, cfg *ConnectionConfig) (dbus.Objec
|
||||
var wirelessMap map[string]dbus.Variant
|
||||
if cfg.Mode == "ap" {
|
||||
wirelessMap = map[string]dbus.Variant{
|
||||
"ssid": dbus.MakeVariant([]byte(cfg.SSID)),
|
||||
"mode": dbus.MakeVariant(cfg.Mode),
|
||||
"band": dbus.MakeVariant(cfg.Band),
|
||||
"channel": dbus.MakeVariant(cfg.Channel),
|
||||
@@ -266,6 +253,7 @@ func AddConnectionWithConfig(conn *dbus.Conn, cfg *ConnectionConfig) (dbus.Objec
|
||||
} else {
|
||||
wirelessMap = map[string]dbus.Variant{
|
||||
"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.
|
||||
// newHost is your desired hostname, interactive=false skips any prompt.
|
||||
func SetHostname(newHost string) error {
|
||||
return withDbus(func(conn *dbus.Conn) error {
|
||||
return system.WithDbus(func(conn *dbus.Conn) error {
|
||||
obj := conn.Object(
|
||||
"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) {
|
||||
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)
|
||||
if err != nil {
|
||||
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) {
|
||||
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)
|
||||
if err != nil {
|
||||
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.
|
||||
// Returns an error if any operation fails.
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -429,7 +417,7 @@ func Up(iface string, uuid string) error {
|
||||
// It takes the interface name as an argument.
|
||||
// Returns an error if the device cannot be found or disconnected.
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -447,7 +435,7 @@ func Down(iface string) error {
|
||||
// If no connection with the UUID exists, it returns nil.
|
||||
// Returns an error if the connection exists but cannot be deleted.
|
||||
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)
|
||||
if err != nil {
|
||||
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