feat(network): VLAN/bridge/bond interface provisioner + IP-address apply
Fügt zwei neue systemd-oneshot-Services hinzu: - edgeguard-interfaces.service: erstellt VLAN/bridge/bond-Interfaces (ip link add/del) wenn der Operator sie über die GUI anlegt/entfernt. Ethernet + WireGuard bleiben OS-managed. - edgeguard-ipaddresses.service: bindet/entfernt IP-Adressen (ip addr add/del) nach jeder GUI-Mutation; läuft jetzt After=edgeguard-interfaces damit Interfaces immer vor den Adressen existieren. Beide Services triggern per applyAsync() in den zugehörigen Handlern (networks.go → Interfaces, ipaddresses.go → Adressen). Diff-Ansatz über *-applied.conf verhindert dass manuell gebundene Adressen/ Interfaces angefasst werden. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -13,13 +15,27 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type IPAddressesHandler struct {
|
type IPAddressesHandler struct {
|
||||||
Repo *ipaddresses.Repo
|
Repo *ipaddresses.Repo
|
||||||
Audit *audit.Repo
|
Generator *ipaddresses.Generator
|
||||||
NodeID string
|
Audit *audit.Repo
|
||||||
|
NodeID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIPAddressesHandler(repo *ipaddresses.Repo, a *audit.Repo, nodeID string) *IPAddressesHandler {
|
func NewIPAddressesHandler(repo *ipaddresses.Repo, a *audit.Repo, nodeID string) *IPAddressesHandler {
|
||||||
return &IPAddressesHandler{Repo: repo, Audit: a, NodeID: nodeID}
|
return &IPAddressesHandler{
|
||||||
|
Repo: repo,
|
||||||
|
Generator: ipaddresses.NewGenerator(repo),
|
||||||
|
Audit: a,
|
||||||
|
NodeID: nodeID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *IPAddressesHandler) applyAsync(ctx context.Context) {
|
||||||
|
go func() {
|
||||||
|
if err := h.Generator.Render(ctx); err != nil {
|
||||||
|
slog.Warn("ip-addresses: apply failed", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *IPAddressesHandler) Register(rg *gin.RouterGroup) {
|
func (h *IPAddressesHandler) Register(rg *gin.RouterGroup) {
|
||||||
@@ -70,6 +86,7 @@ func (h *IPAddressesHandler) Create(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "ip_address.create",
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "ip_address.create",
|
||||||
req.Address, out, h.NodeID)
|
req.Address, out, h.NodeID)
|
||||||
|
h.applyAsync(c.Request.Context())
|
||||||
response.Created(c, out)
|
response.Created(c, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +111,7 @@ func (h *IPAddressesHandler) Update(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "ip_address.update",
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "ip_address.update",
|
||||||
out.Address, out, h.NodeID)
|
out.Address, out, h.NodeID)
|
||||||
|
h.applyAsync(c.Request.Context())
|
||||||
response.OK(c, out)
|
response.OK(c, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,5 +130,6 @@ func (h *IPAddressesHandler) Delete(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "ip_address.delete",
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "ip_address.delete",
|
||||||
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
||||||
|
h.applyAsync(c.Request.Context())
|
||||||
response.NoContent(c)
|
response.NoContent(c)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -15,18 +17,34 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type NetworksHandler struct {
|
type NetworksHandler struct {
|
||||||
Repo *networkifs.Repo
|
Repo *networkifs.Repo
|
||||||
IPs *ipaddresses.Repo
|
Generator *networkifs.Generator
|
||||||
Zones *firewall.ZonesRepo
|
IPs *ipaddresses.Repo
|
||||||
Audit *audit.Repo
|
Zones *firewall.ZonesRepo
|
||||||
NodeID string
|
Audit *audit.Repo
|
||||||
|
NodeID string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNetworksHandler(
|
func NewNetworksHandler(
|
||||||
repo *networkifs.Repo, ips *ipaddresses.Repo,
|
repo *networkifs.Repo, ips *ipaddresses.Repo,
|
||||||
zones *firewall.ZonesRepo, a *audit.Repo, nodeID string,
|
zones *firewall.ZonesRepo, a *audit.Repo, nodeID string,
|
||||||
) *NetworksHandler {
|
) *NetworksHandler {
|
||||||
return &NetworksHandler{Repo: repo, IPs: ips, Zones: zones, Audit: a, NodeID: nodeID}
|
return &NetworksHandler{
|
||||||
|
Repo: repo,
|
||||||
|
Generator: networkifs.NewGenerator(repo),
|
||||||
|
IPs: ips,
|
||||||
|
Zones: zones,
|
||||||
|
Audit: a,
|
||||||
|
NodeID: nodeID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *NetworksHandler) applyAsync(ctx context.Context) {
|
||||||
|
go func() {
|
||||||
|
if err := h.Generator.Render(ctx); err != nil {
|
||||||
|
slog.Warn("network-interfaces: apply failed", "error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *NetworksHandler) Register(rg *gin.RouterGroup) {
|
func (h *NetworksHandler) Register(rg *gin.RouterGroup) {
|
||||||
@@ -88,6 +106,7 @@ func (h *NetworksHandler) Create(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "network_interface.create", req.Name, out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "network_interface.create", req.Name, out, h.NodeID)
|
||||||
|
h.applyAsync(c.Request.Context())
|
||||||
response.Created(c, out)
|
response.Created(c, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +141,7 @@ func (h *NetworksHandler) Update(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "network_interface.update", out.Name, out, h.NodeID)
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "network_interface.update", out.Name, out, h.NodeID)
|
||||||
|
h.applyAsync(c.Request.Context())
|
||||||
response.OK(c, out)
|
response.OK(c, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +160,7 @@ func (h *NetworksHandler) Delete(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "network_interface.delete",
|
_ = h.Audit.Log(c.Request.Context(), actorOf(c), "network_interface.delete",
|
||||||
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
strconv.FormatInt(id, 10), gin.H{"id": id}, h.NodeID)
|
||||||
|
h.applyAsync(c.Request.Context())
|
||||||
response.NoContent(c)
|
response.NoContent(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
87
internal/services/ipaddresses/apply.go
Normal file
87
internal/services/ipaddresses/apply.go
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package ipaddresses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/configgen"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfPath wird von edgeguard-apply-ipaddresses gelesen.
|
||||||
|
const ConfPath = "/etc/edgeguard/ip-addresses.conf"
|
||||||
|
|
||||||
|
type Generator struct {
|
||||||
|
Repo *Repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGenerator(repo *Repo) *Generator { return &Generator{Repo: repo} }
|
||||||
|
|
||||||
|
// Render schreibt /etc/edgeguard/ip-addresses.conf (Format: dev|addr/prefix)
|
||||||
|
// und triggert das apply-Skript via sudo.
|
||||||
|
func (g *Generator) Render(ctx context.Context) error {
|
||||||
|
type addrRow struct {
|
||||||
|
dev string
|
||||||
|
addr string
|
||||||
|
prefix int
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := g.Repo.Pool.Query(ctx, `
|
||||||
|
SELECT ni.name, ia.address, ia.prefix
|
||||||
|
FROM ip_addresses ia
|
||||||
|
JOIN network_interfaces ni ON ni.id = ia.interface_id
|
||||||
|
WHERE ia.active = true
|
||||||
|
ORDER BY ni.name, ia.address`,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("query: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var entries []addrRow
|
||||||
|
for rows.Next() {
|
||||||
|
var r addrRow
|
||||||
|
if err := rows.Scan(&r.dev, &r.addr, &r.prefix); err != nil {
|
||||||
|
return fmt.Errorf("scan: %w", err)
|
||||||
|
}
|
||||||
|
entries = append(entries, r)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("# Generated by edgeguard-api — DO NOT EDIT.\n")
|
||||||
|
buf.WriteString("# Read by edgeguard-apply-ipaddresses. Format: dev|address/prefix\n")
|
||||||
|
for _, e := range entries {
|
||||||
|
fmt.Fprintf(&buf, "%s|%s/%d\n",
|
||||||
|
sanitize(e.dev), sanitize(e.addr), e.prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := configgen.AtomicWrite(ConfPath, buf.Bytes(), 0o644); err != nil {
|
||||||
|
return fmt.Errorf("write %s: %w", ConfPath, err)
|
||||||
|
}
|
||||||
|
if err := applyIPAddresses(); err != nil {
|
||||||
|
return fmt.Errorf("apply: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyIPAddresses() error {
|
||||||
|
cmd := exec.Command("sudo", "-n", "/usr/bin/systemctl",
|
||||||
|
"restart", "edgeguard-ipaddresses.service")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("systemctl restart edgeguard-ipaddresses.service: %s: %w",
|
||||||
|
strings.TrimSpace(string(out)), err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitize(s string) string {
|
||||||
|
s = strings.ReplaceAll(s, "|", "")
|
||||||
|
s = strings.ReplaceAll(s, "\n", "")
|
||||||
|
return strings.TrimSpace(s)
|
||||||
|
}
|
||||||
103
internal/services/networkifs/apply.go
Normal file
103
internal/services/networkifs/apply.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package networkifs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.netcell-it.de/projekte/edgeguard-native/internal/configgen"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfPath is read by edgeguard-apply-interfaces.
|
||||||
|
const ConfPath = "/etc/edgeguard/interfaces.conf"
|
||||||
|
|
||||||
|
type Generator struct {
|
||||||
|
Repo *Repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGenerator(repo *Repo) *Generator { return &Generator{Repo: repo} }
|
||||||
|
|
||||||
|
// Render writes /etc/edgeguard/interfaces.conf (format: type|name|parent|vlan_id|mtu|members)
|
||||||
|
// for VLAN/bridge/bond interfaces and triggers edgeguard-interfaces.service.
|
||||||
|
// Ethernet and WireGuard interfaces are managed by the OS / wg-quick and are excluded.
|
||||||
|
func (g *Generator) Render(ctx context.Context) error {
|
||||||
|
rows, err := g.Repo.Pool.Query(ctx, `
|
||||||
|
SELECT type, name,
|
||||||
|
COALESCE(parent, ''),
|
||||||
|
COALESCE(vlan_id::text, ''),
|
||||||
|
COALESCE(mtu::text, ''),
|
||||||
|
members
|
||||||
|
FROM network_interfaces
|
||||||
|
WHERE active = true
|
||||||
|
AND type IN ('vlan', 'bridge', 'bond')
|
||||||
|
AND (type = 'vlan' OR jsonb_array_length(members) > 0)
|
||||||
|
ORDER BY type, name`,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("query: %w", err)
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
type ifRow struct {
|
||||||
|
typ string
|
||||||
|
name string
|
||||||
|
parent string
|
||||||
|
vlanID string
|
||||||
|
mtu string
|
||||||
|
members []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries []ifRow
|
||||||
|
for rows.Next() {
|
||||||
|
var r ifRow
|
||||||
|
var membersRaw []byte
|
||||||
|
if err := rows.Scan(&r.typ, &r.name, &r.parent, &r.vlanID, &r.mtu, &membersRaw); err != nil {
|
||||||
|
return fmt.Errorf("scan: %w", err)
|
||||||
|
}
|
||||||
|
if len(membersRaw) > 0 {
|
||||||
|
_ = json.Unmarshal(membersRaw, &r.members)
|
||||||
|
}
|
||||||
|
entries = append(entries, r)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString("# Generated by edgeguard-api — DO NOT EDIT.\n")
|
||||||
|
buf.WriteString("# Read by edgeguard-apply-interfaces. Format: type|name|parent|vlan_id|mtu|members\n")
|
||||||
|
for _, e := range entries {
|
||||||
|
fmt.Fprintf(&buf, "%s|%s|%s|%s|%s|%s\n",
|
||||||
|
sanitizeIf(e.typ), sanitizeIf(e.name), sanitizeIf(e.parent),
|
||||||
|
sanitizeIf(e.vlanID), sanitizeIf(e.mtu),
|
||||||
|
sanitizeIf(strings.Join(e.members, ",")))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := configgen.AtomicWrite(ConfPath, buf.Bytes(), 0o644); err != nil {
|
||||||
|
return fmt.Errorf("write %s: %w", ConfPath, err)
|
||||||
|
}
|
||||||
|
if err := applyInterfaces(); err != nil {
|
||||||
|
return fmt.Errorf("apply: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyInterfaces() error {
|
||||||
|
cmd := exec.Command("sudo", "-n", "/usr/bin/systemctl",
|
||||||
|
"restart", "edgeguard-interfaces.service")
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("systemctl restart edgeguard-interfaces.service: %s: %w",
|
||||||
|
strings.TrimSpace(string(out)), err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeIf(s string) string {
|
||||||
|
s = strings.ReplaceAll(s, "|", "")
|
||||||
|
s = strings.ReplaceAll(s, "\n", "")
|
||||||
|
return strings.TrimSpace(s)
|
||||||
|
}
|
||||||
@@ -121,6 +121,12 @@ edgeguard ALL=(postgres) NOPASSWD: /usr/bin/pg_dump --clean --if-exists --no-own
|
|||||||
# Static-Routes: API ruft `sudo systemctl restart edgeguard-routes.service`
|
# Static-Routes: API ruft `sudo systemctl restart edgeguard-routes.service`
|
||||||
# nach jedem Mutate, damit das apply-Skript die neue routes.conf anwendet.
|
# nach jedem Mutate, damit das apply-Skript die neue routes.conf anwendet.
|
||||||
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart edgeguard-routes.service
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart edgeguard-routes.service
|
||||||
|
# Network-Interfaces: API ruft `sudo systemctl restart edgeguard-interfaces.service`
|
||||||
|
# nach jedem Mutate (Create/Update/Delete) von VLAN/bridge/bond-Interfaces.
|
||||||
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart edgeguard-interfaces.service
|
||||||
|
# IP-Addresses: API ruft `sudo systemctl restart edgeguard-ipaddresses.service`
|
||||||
|
# nach jedem Mutate (Create/Update/Delete).
|
||||||
|
edgeguard ALL=(root) NOPASSWD: /usr/bin/systemctl restart edgeguard-ipaddresses.service
|
||||||
# Self-Upgrade-Pfad (handlers/system.go → /system/upgrade). Whitelist
|
# Self-Upgrade-Pfad (handlers/system.go → /system/upgrade). Whitelist
|
||||||
# nur die exakte Unit-Form, damit edgeguard NICHT beliebige systemd-
|
# nur die exakte Unit-Form, damit edgeguard NICHT beliebige systemd-
|
||||||
# Units anlegen darf.
|
# Units anlegen darf.
|
||||||
@@ -400,6 +406,167 @@ ROUTESUNIT
|
|||||||
chown "$EG_USER":"$EG_USER" /etc/edgeguard/routes.conf
|
chown "$EG_USER":"$EG_USER" /etc/edgeguard/routes.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── Network-Interfaces apply-script + systemd unit ───────────
|
||||||
|
# Verwaltet aus /etc/edgeguard/interfaces.conf. VLAN/bridge/bond-
|
||||||
|
# Interfaces die der Operator anlegt werden sofort via
|
||||||
|
# `systemctl restart edgeguard-interfaces.service` erstellt.
|
||||||
|
# Ethernet + WireGuard werden vom OS bzw. wg-quick verwaltet,
|
||||||
|
# nicht von diesem Skript. Diff-Ansatz analog ip-addresses.
|
||||||
|
cat > /usr/sbin/edgeguard-apply-interfaces <<'APPLYIFACES'
|
||||||
|
#!/bin/bash
|
||||||
|
# Managed by edgeguard — DO NOT EDIT.
|
||||||
|
CONF=/etc/edgeguard/interfaces.conf
|
||||||
|
APPLIED=/var/lib/edgeguard/interfaces-applied.conf
|
||||||
|
|
||||||
|
create_iface() {
|
||||||
|
local typ="$1" name="$2" parent="$3" vlan_id="$4" mtu="$5" members="$6"
|
||||||
|
case "$typ" in
|
||||||
|
vlan)
|
||||||
|
ip link add link "$parent" name "$name" type vlan id "$vlan_id" || return 1
|
||||||
|
;;
|
||||||
|
bridge)
|
||||||
|
ip link add "$name" type bridge || return 1
|
||||||
|
if [ -n "$members" ]; then
|
||||||
|
echo "$members" | tr ',' '\n' | while read -r m; do
|
||||||
|
[ -n "$m" ] && ip link set "$m" master "$name" 2>/dev/null || true
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
bond)
|
||||||
|
ip link add "$name" type bond || return 1
|
||||||
|
if [ -n "$members" ]; then
|
||||||
|
echo "$members" | tr ',' '\n' | while read -r m; do
|
||||||
|
[ -n "$m" ] && { ip link set "$m" down 2>/dev/null; ip link set "$m" master "$name" 2>/dev/null || true; }
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) return 0 ;;
|
||||||
|
esac
|
||||||
|
[ -n "$mtu" ] && ip link set "$name" mtu "$mtu" 2>/dev/null || true
|
||||||
|
ip link set "$name" up 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Entferne Interfaces die im letzten Apply waren, aber jetzt nicht mehr
|
||||||
|
if [ -f "$APPLIED" ]; then
|
||||||
|
while IFS='|' read -r typ name rest; do
|
||||||
|
[ -z "$typ" ] && continue
|
||||||
|
case "$typ" in '#'*) continue;; esac
|
||||||
|
if ! awk -F'|' -v n="$name" 'NR>2 && $2==n {found=1} END{exit !found}' "$CONF" 2>/dev/null; then
|
||||||
|
ip link set "$name" down 2>/dev/null || true
|
||||||
|
ip link del "$name" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done < "$APPLIED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -f "$CONF" ] || { : > "$APPLIED"; exit 0; }
|
||||||
|
|
||||||
|
# Erstelle/aktualisiere Interfaces aus Config
|
||||||
|
while IFS='|' read -r typ name parent vlan_id mtu members; do
|
||||||
|
[ -z "$typ" ] && continue
|
||||||
|
case "$typ" in '#'*) continue;; esac
|
||||||
|
if ! ip link show "$name" >/dev/null 2>&1; then
|
||||||
|
if ! create_iface "$typ" "$name" "$parent" "$vlan_id" "$mtu" "$members"; then
|
||||||
|
echo "edgeguard-interfaces: failed to create $typ $name" >&2
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
[ -n "$mtu" ] && ip link set "$name" mtu "$mtu" 2>/dev/null || true
|
||||||
|
ip link set "$name" up 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done < "$CONF"
|
||||||
|
|
||||||
|
cp "$CONF" "$APPLIED"
|
||||||
|
APPLYIFACES
|
||||||
|
chmod 0755 /usr/sbin/edgeguard-apply-interfaces
|
||||||
|
chown root:root /usr/sbin/edgeguard-apply-interfaces
|
||||||
|
|
||||||
|
cat > /etc/systemd/system/edgeguard-interfaces.service <<'IFACESUNIT'
|
||||||
|
[Unit]
|
||||||
|
Description=EdgeGuard managed network interfaces apply
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/sbin/edgeguard-apply-interfaces
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
IFACESUNIT
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable edgeguard-interfaces.service >/dev/null 2>&1 || true
|
||||||
|
# Initialer Apply — leere /etc/edgeguard/interfaces.conf ist ok
|
||||||
|
systemctl start edgeguard-interfaces.service 2>/dev/null || true
|
||||||
|
|
||||||
|
if [ ! -f /etc/edgeguard/interfaces.conf ]; then
|
||||||
|
: > /etc/edgeguard/interfaces.conf
|
||||||
|
chown "$EG_USER":"$EG_USER" /etc/edgeguard/interfaces.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── IP-Addresses apply-script + systemd unit ─────────────────
|
||||||
|
# Verwaltet aus /etc/edgeguard/ip-addresses.conf. Adressen die
|
||||||
|
# der Operator über die GUI hinzufügt/entfernt werden sofort via
|
||||||
|
# `systemctl restart edgeguard-ipaddresses.service` angewendet.
|
||||||
|
# Diff-Ansatz: /var/lib/edgeguard/ip-addresses-applied.conf hält
|
||||||
|
# den zuletzt angewendeten Stand; alte Adressen werden entfernt,
|
||||||
|
# neue hinzugefügt — manuell gebundene Adressen bleiben intakt.
|
||||||
|
cat > /usr/sbin/edgeguard-apply-ipaddresses <<'APPLYIP'
|
||||||
|
#!/bin/bash
|
||||||
|
# Managed by edgeguard — DO NOT EDIT.
|
||||||
|
set -e
|
||||||
|
CONF=/etc/edgeguard/ip-addresses.conf
|
||||||
|
APPLIED=/var/lib/edgeguard/ip-addresses-applied.conf
|
||||||
|
|
||||||
|
# Entferne Adressen die im letzten Apply-Lauf waren, aber jetzt nicht mehr
|
||||||
|
if [ -f "$APPLIED" ]; then
|
||||||
|
while IFS='|' read -r dev addrpfx; do
|
||||||
|
[ -z "$dev" ] && continue
|
||||||
|
case "$dev" in '#'*) continue;; esac
|
||||||
|
# Nur entfernen wenn nicht mehr in der neuen Config
|
||||||
|
if ! grep -qxF "${dev}|${addrpfx}" "$CONF" 2>/dev/null; then
|
||||||
|
ip addr del "$addrpfx" dev "$dev" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
done < "$APPLIED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
[ -f "$CONF" ] || { : > "$APPLIED"; exit 0; }
|
||||||
|
|
||||||
|
# Füge neue Adressen hinzu
|
||||||
|
while IFS='|' read -r dev addrpfx; do
|
||||||
|
[ -z "$dev" ] && continue
|
||||||
|
case "$dev" in '#'*) continue;; esac
|
||||||
|
ip addr add "$addrpfx" dev "$dev" 2>/dev/null || true
|
||||||
|
done < "$CONF"
|
||||||
|
|
||||||
|
# Aktuelle Config als "applied" speichern
|
||||||
|
cp "$CONF" "$APPLIED"
|
||||||
|
APPLYIP
|
||||||
|
chmod 0755 /usr/sbin/edgeguard-apply-ipaddresses
|
||||||
|
chown root:root /usr/sbin/edgeguard-apply-ipaddresses
|
||||||
|
|
||||||
|
cat > /etc/systemd/system/edgeguard-ipaddresses.service <<'IPADDRUNIT'
|
||||||
|
[Unit]
|
||||||
|
Description=EdgeGuard managed IP addresses apply
|
||||||
|
After=network-online.target edgeguard-interfaces.service
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/sbin/edgeguard-apply-ipaddresses
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
IPADDRUNIT
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable edgeguard-ipaddresses.service >/dev/null 2>&1 || true
|
||||||
|
systemctl start edgeguard-ipaddresses.service 2>/dev/null || true
|
||||||
|
|
||||||
|
if [ ! -f /etc/edgeguard/ip-addresses.conf ]; then
|
||||||
|
: > /etc/edgeguard/ip-addresses.conf
|
||||||
|
chown "$EG_USER":"$EG_USER" /etc/edgeguard/ip-addresses.conf
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Keepalived notify-scripts installieren ───────────────────
|
# ── Keepalived notify-scripts installieren ───────────────────
|
||||||
# Die Skripte liegen im Package unter /usr/lib/edgeguard/ und
|
# Die Skripte liegen im Package unter /usr/lib/edgeguard/ und
|
||||||
# werden von Keepalived als notify_master / notify_backup / check
|
# werden von Keepalived als notify_master / notify_backup / check
|
||||||
|
|||||||
Reference in New Issue
Block a user