- Agent: wg_add_peer, wg_delete_peer, wg_list_peers Commands - Keypair-Generierung auf der Firewall (wg genkey/pubkey) - Automatische IP-Vergabe aus dem Tunnel-Subnetz - WAN-IP Auto-Detection via Default-Route Interface - Endpoint als Parameter uebergebbar (fuer DynDNS etc.) - Config-Aenderungen direkt in config.xml (Go-basiert, kein PHP) - configctl wireguard configure fuer Live-Reload - Client-Config in Response (ready for import) - Server-Name default: ALWAYSON_VPN - Backend: 3 neue API-Endpoints (POST/GET/DELETE) - README mit vollstaendiger Dokumentation
605 lines
17 KiB
Go
605 lines
17 KiB
Go
package ws
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// --- OPNsense config.xml Structs ---
|
|
|
|
type OPNsenseConfig struct {
|
|
XMLName xml.Name `xml:"opnsense"`
|
|
WireGuard *WGConfig `xml:"OPNsense>wireguard"`
|
|
Rest []byte `xml:",innerxml"`
|
|
}
|
|
|
|
type WGConfig struct {
|
|
General WGGeneral `xml:"general"`
|
|
Server WGServer `xml:"server"`
|
|
Client WGClient `xml:"client"`
|
|
}
|
|
|
|
type WGGeneral struct {
|
|
XMLName xml.Name `xml:"general"`
|
|
Version string `xml:"version,attr"`
|
|
PersistedAt string `xml:"persisted_at,attr"`
|
|
Description string `xml:"description,attr"`
|
|
Enabled string `xml:"enabled"`
|
|
}
|
|
|
|
type WGServer struct {
|
|
XMLName xml.Name `xml:"server"`
|
|
Version string `xml:"version,attr"`
|
|
PersistedAt string `xml:"persisted_at,attr"`
|
|
Description string `xml:"description,attr"`
|
|
Servers []WGServerInst `xml:"servers>server"`
|
|
}
|
|
|
|
type WGServerInst struct {
|
|
UUID string `xml:"uuid,attr"`
|
|
Enabled string `xml:"enabled"`
|
|
Name string `xml:"name"`
|
|
Instance string `xml:"instance"`
|
|
PubKey string `xml:"pubkey"`
|
|
PrivKey string `xml:"privkey"`
|
|
Port string `xml:"port"`
|
|
MTU string `xml:"mtu"`
|
|
DNS string `xml:"dns"`
|
|
TunnelAddress string `xml:"tunneladdress"`
|
|
DisableRoutes string `xml:"disableroutes"`
|
|
Gateway string `xml:"gateway"`
|
|
CarpDependOn string `xml:"carp_depend_on"`
|
|
Peers string `xml:"peers"`
|
|
Debug string `xml:"debug"`
|
|
Endpoint string `xml:"endpoint"`
|
|
PeerDNS string `xml:"peer_dns"`
|
|
}
|
|
|
|
type WGClient struct {
|
|
XMLName xml.Name `xml:"client"`
|
|
Version string `xml:"version,attr"`
|
|
PersistedAt string `xml:"persisted_at,attr"`
|
|
Description string `xml:"description,attr"`
|
|
Clients []WGClientInst `xml:"clients>client"`
|
|
}
|
|
|
|
type WGClientInst struct {
|
|
UUID string `xml:"uuid,attr"`
|
|
Enabled string `xml:"enabled"`
|
|
Name string `xml:"name"`
|
|
PubKey string `xml:"pubkey"`
|
|
PSK string `xml:"psk"`
|
|
TunnelAddress string `xml:"tunneladdress"`
|
|
ServerAddress string `xml:"serveraddress"`
|
|
ServerPort string `xml:"serverport"`
|
|
KeepAlive string `xml:"keepalive"`
|
|
}
|
|
|
|
// handleWGAddPeer erzeugt einen neuen WireGuard-Peer
|
|
func (h *Handler) handleWGAddPeer(msg Message) Message {
|
|
response := Message{Type: "response", ID: msg.ID}
|
|
|
|
name, _ := msg.Params["name"].(string)
|
|
allowedIPs, _ := msg.Params["allowed_ips"].(string)
|
|
if name == "" {
|
|
response.Status = "error"
|
|
response.Error = "Parameter 'name' erforderlich"
|
|
return response
|
|
}
|
|
if allowedIPs == "" {
|
|
allowedIPs = "0.0.0.0/0"
|
|
}
|
|
|
|
// Optional: DNS fuer Client-Config
|
|
dns, _ := msg.Params["dns"].(string)
|
|
|
|
// Server-Name (default: ALWAYSON_VPN)
|
|
serverName := "ALWAYSON_VPN"
|
|
if sn, ok := msg.Params["server_name"].(string); ok && sn != "" {
|
|
serverName = sn
|
|
}
|
|
|
|
log.Printf("WG Add Peer: name=%s, server=%s, allowed_ips=%s", name, serverName, allowedIPs)
|
|
|
|
// 1. config.xml lesen
|
|
configData, err := os.ReadFile("/conf/config.xml")
|
|
if err != nil {
|
|
response.Status = "error"
|
|
response.Error = fmt.Sprintf("config.xml lesen: %v", err)
|
|
return response
|
|
}
|
|
|
|
// 2. Server-Instanz finden (direkt im XML-Text arbeiten statt Unmarshal,
|
|
// um die gesamte config.xml nicht zu zerstoeren)
|
|
// Wir parsen nur den WireGuard-Teil, den Rest lassen wir in Ruhe.
|
|
|
|
// Server-Instanz per configctl/PHP auslesen und bearbeiten
|
|
// Sicherer Weg: OPNsense PHP-API nutzen
|
|
serverUUID, serverPubKey, serverPort, tunnelSubnet, err := h.findWGServer(string(configData), serverName)
|
|
if err != nil {
|
|
response.Status = "error"
|
|
response.Error = err.Error()
|
|
return response
|
|
}
|
|
|
|
// 3. Naechste freie IP im Subnetz bestimmen
|
|
usedIPs := h.getUsedPeerIPs(string(configData), serverUUID, tunnelSubnet)
|
|
nextIP, err := h.nextFreeIP(tunnelSubnet, usedIPs)
|
|
if err != nil {
|
|
response.Status = "error"
|
|
response.Error = fmt.Sprintf("Keine freie IP: %v", err)
|
|
return response
|
|
}
|
|
|
|
// 4. Keypair generieren
|
|
privKeyOutput, err := h.runCommand("/usr/bin/wg genkey", 5)
|
|
if err != nil {
|
|
response.Status = "error"
|
|
response.Error = fmt.Sprintf("wg genkey fehlgeschlagen: %v", err)
|
|
return response
|
|
}
|
|
clientPrivKey := strings.TrimSpace(privKeyOutput)
|
|
|
|
pubKeyOutput, err := h.runCommand(fmt.Sprintf("echo '%s' | /usr/bin/wg pubkey", clientPrivKey), 5)
|
|
if err != nil {
|
|
response.Status = "error"
|
|
response.Error = fmt.Sprintf("wg pubkey fehlgeschlagen: %v", err)
|
|
return response
|
|
}
|
|
clientPubKey := strings.TrimSpace(pubKeyOutput)
|
|
|
|
// 5. UUID fuer neuen Peer generieren
|
|
uuidOutput, err := h.runCommand("/bin/uuidgen", 5)
|
|
if err != nil {
|
|
response.Status = "error"
|
|
response.Error = fmt.Sprintf("uuidgen fehlgeschlagen: %v", err)
|
|
return response
|
|
}
|
|
peerUUID := strings.ToLower(strings.TrimSpace(uuidOutput))
|
|
|
|
// 6. Peer in config.xml eintragen (direkt per sed — zuverlaessiger als PHP parse_config)
|
|
err = h.addPeerViaSed(peerUUID, name, clientPubKey, nextIP, serverUUID)
|
|
if err != nil {
|
|
response.Status = "error"
|
|
response.Error = fmt.Sprintf("Peer eintragen fehlgeschlagen: %v", err)
|
|
return response
|
|
}
|
|
|
|
// 7. WireGuard neu laden
|
|
reloadOutput, reloadErr := h.runCommand("/usr/local/sbin/configctl wireguard configure", 15)
|
|
if reloadErr != nil {
|
|
log.Printf("WireGuard reload Warnung: %v (%s)", reloadErr, reloadOutput)
|
|
}
|
|
|
|
// 8. Endpoint ermitteln
|
|
// Prioritaet: 1. Parameter "endpoint" 2. Default-Gateway-Interface IP 3. hostname
|
|
port := serverPort
|
|
if port == "" {
|
|
port = "51820"
|
|
}
|
|
|
|
endpoint := ""
|
|
if ep, ok := msg.Params["endpoint"].(string); ok && ep != "" {
|
|
// Wenn nur Host/IP ohne Port angegeben
|
|
if !strings.Contains(ep, ":") {
|
|
endpoint = fmt.Sprintf("%s:%s", ep, port)
|
|
} else {
|
|
endpoint = ep
|
|
}
|
|
} else {
|
|
// WAN-IP ueber Default-Route Interface ermitteln
|
|
routeOutput, _ := h.runCommand("/sbin/route -n get default 2>/dev/null | /usr/bin/grep interface | /usr/bin/awk '{print $2}'", 5)
|
|
wanIface := strings.TrimSpace(routeOutput)
|
|
if wanIface != "" {
|
|
ipOutput, _ := h.runCommand(fmt.Sprintf("/sbin/ifconfig %s 2>/dev/null | /usr/bin/grep 'inet ' | /usr/bin/awk '{print $2}' | /usr/bin/head -1", wanIface), 5)
|
|
wanIP := strings.TrimSpace(ipOutput)
|
|
if wanIP != "" {
|
|
endpoint = fmt.Sprintf("%s:%s", wanIP, port)
|
|
}
|
|
}
|
|
if endpoint == "" {
|
|
hostnameOutput, _ := h.runCommand("/bin/hostname", 5)
|
|
endpoint = fmt.Sprintf("%s:%s", strings.TrimSpace(hostnameOutput), port)
|
|
}
|
|
}
|
|
|
|
// 9. Client-Config zusammenbauen
|
|
clientConfig := fmt.Sprintf(`[Interface]
|
|
PrivateKey = %s
|
|
Address = %s/32
|
|
`, clientPrivKey, nextIP)
|
|
|
|
if dns != "" {
|
|
clientConfig += fmt.Sprintf("DNS = %s\n", dns)
|
|
}
|
|
|
|
clientConfig += fmt.Sprintf(`
|
|
[Peer]
|
|
PublicKey = %s
|
|
AllowedIPs = %s
|
|
Endpoint = %s
|
|
PersistentKeepalive = 25
|
|
`, serverPubKey, allowedIPs, endpoint)
|
|
|
|
response.Status = "ok"
|
|
response.Data = map[string]interface{}{
|
|
"peer_uuid": peerUUID,
|
|
"name": name,
|
|
"client_ip": nextIP,
|
|
"public_key": clientPubKey,
|
|
"server_pubkey": serverPubKey,
|
|
"endpoint": endpoint,
|
|
"allowed_ips": allowedIPs,
|
|
"client_config": clientConfig,
|
|
"server_name": serverName,
|
|
"server_uuid": serverUUID,
|
|
}
|
|
|
|
log.Printf("WG Peer erstellt: %s (%s) -> %s", name, nextIP, serverName)
|
|
return response
|
|
}
|
|
|
|
// handleWGDeletePeer entfernt einen WireGuard-Peer
|
|
func (h *Handler) handleWGDeletePeer(msg Message) Message {
|
|
response := Message{Type: "response", ID: msg.ID}
|
|
|
|
peerUUID, _ := msg.Params["peer_uuid"].(string)
|
|
if peerUUID == "" {
|
|
response.Status = "error"
|
|
response.Error = "Parameter 'peer_uuid' erforderlich"
|
|
return response
|
|
}
|
|
|
|
log.Printf("WG Delete Peer: uuid=%s", peerUUID)
|
|
|
|
// Peer per sed entfernen
|
|
if err := h.removePeerViaSed(peerUUID); err != nil {
|
|
response.Status = "error"
|
|
response.Error = fmt.Sprintf("Peer loeschen fehlgeschlagen: %v", err)
|
|
return response
|
|
}
|
|
|
|
// WireGuard reload
|
|
h.runCommand("/usr/local/sbin/configctl wireguard configure", 15)
|
|
|
|
response.Status = "ok"
|
|
response.Data = map[string]interface{}{
|
|
"peer_uuid": peerUUID,
|
|
"message": "Peer entfernt",
|
|
}
|
|
|
|
log.Printf("WG Peer geloescht: %s", peerUUID)
|
|
return response
|
|
}
|
|
|
|
// handleWGListPeers listet alle Peers einer WG-Instanz
|
|
func (h *Handler) handleWGListPeers(msg Message) Message {
|
|
response := Message{Type: "response", ID: msg.ID}
|
|
|
|
serverName := "ALWAYSON_VPN"
|
|
if sn, ok := msg.Params["server_name"].(string); ok && sn != "" {
|
|
serverName = sn
|
|
}
|
|
|
|
configData, err := os.ReadFile("/conf/config.xml")
|
|
if err != nil {
|
|
response.Status = "error"
|
|
response.Error = fmt.Sprintf("config.xml lesen: %v", err)
|
|
return response
|
|
}
|
|
|
|
configStr := string(configData)
|
|
serverUUID, _, _, tunnelSubnet, err := h.findWGServer(configStr, serverName)
|
|
if err != nil {
|
|
response.Status = "error"
|
|
response.Error = err.Error()
|
|
return response
|
|
}
|
|
|
|
// Peer-UUIDs aus Server lesen
|
|
peerUUIDs := h.getServerPeerUUIDs(configStr, serverUUID)
|
|
|
|
// Peer-Details sammeln
|
|
var peers []map[string]interface{}
|
|
for _, uuid := range peerUUIDs {
|
|
peer := h.getPeerByUUID(configStr, uuid)
|
|
if peer != nil {
|
|
peers = append(peers, peer)
|
|
}
|
|
}
|
|
|
|
if peers == nil {
|
|
peers = []map[string]interface{}{}
|
|
}
|
|
|
|
// Live WG-Status (Handshake, Transfer)
|
|
wgStatus, _ := h.runCommand("/usr/bin/wg show all", 5)
|
|
|
|
response.Status = "ok"
|
|
response.Data = map[string]interface{}{
|
|
"server_name": serverName,
|
|
"server_uuid": serverUUID,
|
|
"subnet": tunnelSubnet,
|
|
"peer_count": len(peers),
|
|
"peers": peers,
|
|
"wg_status": strings.TrimSpace(wgStatus),
|
|
}
|
|
|
|
return response
|
|
}
|
|
|
|
// --- Helper Functions ---
|
|
|
|
// findWGServer sucht eine WG-Server-Instanz nach Name in der config.xml
|
|
func (h *Handler) findWGServer(configXML, serverName string) (uuid, pubkey, port, tunnelAddr string, err error) {
|
|
type serverEntry struct {
|
|
UUID string `xml:"uuid,attr"`
|
|
Name string `xml:"name"`
|
|
PubKey string `xml:"pubkey"`
|
|
Port string `xml:"port"`
|
|
TunnelAddress string `xml:"tunneladdress"`
|
|
Peers string `xml:"peers"`
|
|
}
|
|
|
|
// Einfaches XML-Parsing nur fuer den WG-Teil
|
|
type opnsenseWG struct {
|
|
Servers []serverEntry `xml:"OPNsense>wireguard>server>servers>server"`
|
|
}
|
|
|
|
var cfg opnsenseWG
|
|
if xmlErr := xml.Unmarshal([]byte(configXML), &cfg); xmlErr != nil {
|
|
return "", "", "", "", fmt.Errorf("XML parsen: %v", xmlErr)
|
|
}
|
|
|
|
for _, srv := range cfg.Servers {
|
|
if srv.Name == serverName {
|
|
return srv.UUID, srv.PubKey, srv.Port, srv.TunnelAddress, nil
|
|
}
|
|
}
|
|
|
|
return "", "", "", "", fmt.Errorf("WireGuard-Server '%s' nicht gefunden", serverName)
|
|
}
|
|
|
|
// getUsedPeerIPs sammelt alle vergebenen IPs im Server-Subnetz
|
|
func (h *Handler) getUsedPeerIPs(configXML, serverUUID, tunnelAddr string) []string {
|
|
type clientEntry struct {
|
|
UUID string `xml:"uuid,attr"`
|
|
TunnelAddress string `xml:"tunneladdress"`
|
|
}
|
|
type opnsenseClients struct {
|
|
Clients []clientEntry `xml:"OPNsense>wireguard>client>clients>client"`
|
|
}
|
|
|
|
var cfg opnsenseClients
|
|
xml.Unmarshal([]byte(configXML), &cfg)
|
|
|
|
peerUUIDs := h.getServerPeerUUIDs(configXML, serverUUID)
|
|
peerSet := make(map[string]bool)
|
|
for _, u := range peerUUIDs {
|
|
peerSet[u] = true
|
|
}
|
|
|
|
var ips []string
|
|
for _, c := range cfg.Clients {
|
|
if peerSet[c.UUID] {
|
|
// tunneladdress kann "10.33.144.2/32" sein
|
|
addr := strings.Split(c.TunnelAddress, "/")[0]
|
|
if addr != "" {
|
|
ips = append(ips, addr)
|
|
}
|
|
}
|
|
}
|
|
|
|
return ips
|
|
}
|
|
|
|
// getServerPeerUUIDs liest die Peer-UUIDs aus dem Server-Eintrag
|
|
func (h *Handler) getServerPeerUUIDs(configXML, serverUUID string) []string {
|
|
type serverEntry struct {
|
|
UUID string `xml:"uuid,attr"`
|
|
Peers string `xml:"peers"`
|
|
}
|
|
type opnsenseServers struct {
|
|
Servers []serverEntry `xml:"OPNsense>wireguard>server>servers>server"`
|
|
}
|
|
|
|
var cfg opnsenseServers
|
|
xml.Unmarshal([]byte(configXML), &cfg)
|
|
|
|
for _, srv := range cfg.Servers {
|
|
if srv.UUID == serverUUID {
|
|
var uuids []string
|
|
for _, p := range strings.Split(srv.Peers, ",") {
|
|
p = strings.TrimSpace(p)
|
|
if p != "" {
|
|
uuids = append(uuids, p)
|
|
}
|
|
}
|
|
return uuids
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// getPeerByUUID liest Peer-Details aus config.xml
|
|
func (h *Handler) getPeerByUUID(configXML, uuid string) map[string]interface{} {
|
|
type clientEntry struct {
|
|
UUID string `xml:"uuid,attr"`
|
|
Enabled string `xml:"enabled"`
|
|
Name string `xml:"name"`
|
|
PubKey string `xml:"pubkey"`
|
|
TunnelAddress string `xml:"tunneladdress"`
|
|
}
|
|
type opnsenseClients struct {
|
|
Clients []clientEntry `xml:"OPNsense>wireguard>client>clients>client"`
|
|
}
|
|
|
|
var cfg opnsenseClients
|
|
xml.Unmarshal([]byte(configXML), &cfg)
|
|
|
|
for _, c := range cfg.Clients {
|
|
if c.UUID == uuid {
|
|
return map[string]interface{}{
|
|
"uuid": c.UUID,
|
|
"enabled": c.Enabled,
|
|
"name": c.Name,
|
|
"public_key": c.PubKey,
|
|
"tunnel_address": c.TunnelAddress,
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// nextFreeIP berechnet die naechste freie IP in einem Subnetz
|
|
func (h *Handler) nextFreeIP(tunnelAddr string, usedIPs []string) (string, error) {
|
|
// tunnelAddr: "10.33.144.1/24"
|
|
_, ipNet, err := net.ParseCIDR(tunnelAddr)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Subnetz parsen: %v", err)
|
|
}
|
|
|
|
usedSet := make(map[string]bool)
|
|
// Server-IP selbst ist auch belegt
|
|
serverIP := strings.Split(tunnelAddr, "/")[0]
|
|
usedSet[serverIP] = true
|
|
for _, ip := range usedIPs {
|
|
usedSet[ip] = true
|
|
}
|
|
|
|
// Ab .2 durchiterieren
|
|
ip := make(net.IP, len(ipNet.IP))
|
|
copy(ip, ipNet.IP)
|
|
|
|
for i := 2; i < 255; i++ {
|
|
candidate := make(net.IP, len(ip))
|
|
copy(candidate, ip)
|
|
candidate[len(candidate)-1] = byte(i)
|
|
|
|
if !ipNet.Contains(candidate) {
|
|
break
|
|
}
|
|
if !usedSet[candidate.String()] {
|
|
return candidate.String(), nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("kein freier IP-Platz im Subnetz %s", tunnelAddr)
|
|
}
|
|
|
|
// addPeerViaSed fuegt Peer per sed-Kommandos in config.xml ein
|
|
// Zuverlaessiger als PHP parse_config, behandelt <peers/> und <peers>...</peers> korrekt
|
|
func (h *Handler) addPeerViaSed(peerUUID, name, pubKey, clientIP, serverUUID string) error {
|
|
configPath := "/conf/config.xml"
|
|
|
|
// 1. Client-Block vor </clients> einfuegen (direkt in Go)
|
|
configData, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf("config.xml lesen: %v", err)
|
|
}
|
|
|
|
clientBlock := fmt.Sprintf(
|
|
"\n <client uuid=\"%s\">\n <enabled>1</enabled>\n <name>%s</name>\n <pubkey>%s</pubkey>\n <psk/>\n <tunneladdress>%s/32</tunneladdress>\n <serveraddress/>\n <serverport/>\n <keepalive/>\n </client>\n ",
|
|
peerUUID, name, pubKey, clientIP)
|
|
|
|
configStr := string(configData)
|
|
// Vor dem letzten </clients> einfuegen
|
|
lastIdx := strings.LastIndex(configStr, "</clients>")
|
|
if lastIdx < 0 {
|
|
return fmt.Errorf("</clients> nicht in config.xml gefunden")
|
|
}
|
|
configStr = configStr[:lastIdx] + clientBlock + configStr[lastIdx:]
|
|
|
|
if err := os.WriteFile(configPath, []byte(configStr), 0644); err != nil {
|
|
return fmt.Errorf("config.xml schreiben: %v", err)
|
|
}
|
|
|
|
// 2. Peer-UUID zum Server hinzufuegen
|
|
// Config nochmal lesen (wurde gerade geschrieben)
|
|
configData2, err2 := os.ReadFile(configPath)
|
|
if err2 != nil {
|
|
return fmt.Errorf("config.xml lesen: %v", err2)
|
|
}
|
|
configStr = string(configData2)
|
|
|
|
serverTag := fmt.Sprintf(`uuid="%s"`, serverUUID)
|
|
idx := strings.Index(configStr, serverTag)
|
|
if idx < 0 {
|
|
return fmt.Errorf("Server %s nicht gefunden", serverUUID)
|
|
}
|
|
|
|
// Suche im Server-Block (max 2000 Zeichen nach dem UUID-Tag)
|
|
searchEnd := idx + 2000
|
|
if searchEnd > len(configStr) {
|
|
searchEnd = len(configStr)
|
|
}
|
|
searchFrom := configStr[idx:searchEnd]
|
|
|
|
// Fall 1: <peers/> (self-closing, leer)
|
|
selfClosingIdx := strings.Index(searchFrom, "<peers/>")
|
|
if selfClosingIdx >= 0 {
|
|
absIdx := idx + selfClosingIdx
|
|
newPeersTag := fmt.Sprintf("<peers>%s</peers>", peerUUID)
|
|
configStr = configStr[:absIdx] + newPeersTag + configStr[absIdx+8:]
|
|
} else {
|
|
// Fall 2: <peers>...</peers>
|
|
peersStart := strings.Index(searchFrom, "<peers>")
|
|
peersEnd := strings.Index(searchFrom, "</peers>")
|
|
if peersStart >= 0 && peersEnd >= 0 {
|
|
absStart := idx + peersStart + 7
|
|
absEnd := idx + peersEnd
|
|
currentPeers := strings.TrimSpace(configStr[absStart:absEnd])
|
|
var newPeers string
|
|
if currentPeers == "" {
|
|
newPeers = peerUUID
|
|
} else {
|
|
newPeers = currentPeers + "," + peerUUID
|
|
}
|
|
configStr = configStr[:absStart] + newPeers + configStr[absEnd:]
|
|
} else {
|
|
return fmt.Errorf("peers Tag nicht im Server-Block gefunden")
|
|
}
|
|
}
|
|
|
|
return os.WriteFile(configPath, []byte(configStr), 0644)
|
|
}
|
|
|
|
// removePeerViaSed entfernt einen Peer per sed aus config.xml
|
|
func (h *Handler) removePeerViaSed(peerUUID string) error {
|
|
configPath := "/conf/config.xml"
|
|
|
|
// 1. Client-Block entfernen (alles von <client uuid="..."> bis </client>)
|
|
sedRemove := fmt.Sprintf(
|
|
`/usr/bin/sed -i '' '/<client uuid="%s">/,/<\/client>/d' %s`,
|
|
peerUUID, configPath)
|
|
if _, err := h.runCommand(sedRemove, 5); err != nil {
|
|
return fmt.Errorf("Client entfernen: %v", err)
|
|
}
|
|
|
|
// 2. UUID aus allen <peers>-Tags entfernen
|
|
configData, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
configStr := string(configData)
|
|
|
|
// Alle Vorkommen der UUID in <peers>-Tags bereinigen
|
|
// Varianten: "uuid", "uuid,other", "other,uuid", "other,uuid,more"
|
|
replacements := []struct{ old, new string }{
|
|
{fmt.Sprintf("<peers>%s,", peerUUID), "<peers>"},
|
|
{fmt.Sprintf(",%s</peers>", peerUUID), "</peers>"},
|
|
{fmt.Sprintf("<peers>%s</peers>", peerUUID), "<peers/>"},
|
|
{fmt.Sprintf(",%s,", peerUUID), ","},
|
|
}
|
|
|
|
for _, r := range replacements {
|
|
configStr = strings.ReplaceAll(configStr, r.old, r.new)
|
|
}
|
|
|
|
return os.WriteFile(configPath, []byte(configStr), 0644)
|
|
}
|