feat(docs): update speaker installation instructions and logging details
quality / commitlint (push) Skipped
quality / changes (push) Successful in 8s
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 26s
quality / web (push) Successful in 1m27s
quality / go (push) Successful in 1m18s
quality / bird2 (push) Successful in 16s
CD / quality (push) Successful in 3m43s
CD / publish (push) Successful in 3m11s
quality / commitlint (push) Skipped
quality / changes (push) Successful in 8s
quality / docker-check (push) Skipped
quality / openapi (push) Successful in 26s
quality / web (push) Successful in 1m27s
quality / go (push) Successful in 1m18s
quality / bird2 (push) Successful in 16s
CD / quality (push) Successful in 3m43s
CD / publish (push) Successful in 3m11s
- Enhanced the speaker installation documentation to clarify the use of TCP port 179 and the logging commands for monitoring BIRD and evobgp-agent. - Updated the speaker form dialog to include additional information about MikroTik connections and logging commands. - Modified the BIRD configuration to include logging to stderr for better visibility during operations. - Adjusted the Docker Compose configuration to ensure proper network settings and sysctl configurations for BGP functionality.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -117,6 +118,9 @@ func (s *Server) handleSync(w http.ResponseWriter, r *http.Request) {
|
||||
if timeout <= 0 {
|
||||
timeout = 45 * time.Second
|
||||
}
|
||||
revID := strings.TrimSpace(req.RevisionID)
|
||||
log.Printf("agentserver: sync start speaker_id=%s revision_id=%q control_plane=%s",
|
||||
strings.TrimSpace(s.cfg.SpeakerID), revID, controlPlaneHost(s.cfg.ControlPlaneURL))
|
||||
ctx, cancel := context.WithTimeout(r.Context(), timeout)
|
||||
defer cancel()
|
||||
|
||||
@@ -124,7 +128,7 @@ func (s *Server) handleSync(w http.ResponseWriter, r *http.Request) {
|
||||
BaseURL: s.cfg.ControlPlaneURL,
|
||||
Token: s.cfg.NodeToken,
|
||||
SpeakerID: s.cfg.SpeakerID,
|
||||
RevisionID: strings.TrimSpace(req.RevisionID),
|
||||
RevisionID: revID,
|
||||
PubKeyB64: s.cfg.PubKeyB64,
|
||||
PubKeyHex: s.cfg.PubKeyHex,
|
||||
ExtractDir: s.cfg.ExtractDir,
|
||||
@@ -134,13 +138,16 @@ func (s *Server) handleSync(w http.ResponseWriter, r *http.Request) {
|
||||
Timeout: timeout,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("agentserver: sync: %v", err)
|
||||
log.Printf("agentserver: sync failed speaker_id=%s revision_id=%q err=%v",
|
||||
strings.TrimSpace(s.cfg.SpeakerID), revID, err)
|
||||
writeProblem(w, http.StatusBadGateway, upstreamErrorDetail)
|
||||
return
|
||||
}
|
||||
if s.cfg.OnSyncSuccess != nil {
|
||||
s.cfg.OnSyncSuccess(res.RevisionID)
|
||||
}
|
||||
log.Printf("agentserver: sync ok speaker_id=%s applied_revision_id=%s",
|
||||
strings.TrimSpace(s.cfg.SpeakerID), res.RevisionID)
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"ok": true,
|
||||
"applied_revision_id": res.RevisionID,
|
||||
@@ -148,6 +155,18 @@ func (s *Server) handleSync(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func controlPlaneHost(raw string) string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil || strings.TrimSpace(u.Host) == "" {
|
||||
return raw
|
||||
}
|
||||
return u.Host
|
||||
}
|
||||
|
||||
func (s *Server) authorize(r *http.Request) bool {
|
||||
secret := strings.TrimSpace(s.cfg.Secret)
|
||||
if secret == "" {
|
||||
|
||||
@@ -72,7 +72,8 @@ func RenderMainBirdConf(opts MainBirdConfOptions) (string, error) {
|
||||
}
|
||||
b.WriteString("router id ")
|
||||
b.WriteString(strings.TrimSpace(opts.RouterID))
|
||||
b.WriteString(";\n\n")
|
||||
b.WriteString(";\n")
|
||||
b.WriteString("log stderr all;\n\n")
|
||||
for _, inc := range opts.Includes {
|
||||
inc = strings.TrimSpace(inc)
|
||||
if inc == "" {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
router id 192.0.2.1;
|
||||
log stderr all;
|
||||
|
||||
include "bird.d/evobgp_filters_v4.conf";
|
||||
include "bird.d/evobgp_filters_v6.conf";
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Standard EvoBGP layout: main skeleton + bird.d fragments (matches StandardIncludeFragments).
|
||||
|
||||
router id 192.0.2.1;
|
||||
log stderr all;
|
||||
|
||||
include "bird.d/evobgp_filters_v4.conf";
|
||||
include "bird.d/evobgp_filters_v6.conf";
|
||||
|
||||
@@ -999,9 +999,12 @@ func (s *Server) handleNodeBundle(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
frags := rev.PreviewFragments
|
||||
if overlaid, err := pipeline.OverlayFragmentsForSpeaker(s.store, a.TenantID, sid, rid, frags); err == nil {
|
||||
frags = overlaid
|
||||
overlaid, err := pipeline.OverlayFragmentsForSpeaker(s.store, a.TenantID, sid, rid, frags)
|
||||
if err != nil {
|
||||
writeInternalError(w, "bundle overlay", err)
|
||||
return
|
||||
}
|
||||
frags = overlaid
|
||||
tgz, err := bundle.BuildGzippedTar(rid, sid, frags, s.bundlePriv)
|
||||
if err != nil {
|
||||
writeInternalError(w, "internal", err)
|
||||
|
||||
@@ -149,6 +149,7 @@ func TestPostSpeaker_installCommandsAndMetaObject(t *testing.T) {
|
||||
secret,
|
||||
token,
|
||||
"https://cp.example.com",
|
||||
`"179:179/tcp"`,
|
||||
} {
|
||||
if !strings.Contains(cmd, want) {
|
||||
t.Errorf("docker_commands missing %q", want)
|
||||
|
||||
@@ -19,9 +19,13 @@ func BirdLocalsForSpeaker(st store.Backend, tenantID, speakerID string) birdLoca
|
||||
return loc
|
||||
}
|
||||
meta := store.ParseSpeakerMeta(sp.MetaJSON)
|
||||
if s := strings.TrimSpace(meta.BirdBgpSourceIPv4); s != "" {
|
||||
loc.routerID = s
|
||||
loc.localV4 = s
|
||||
src := strings.TrimSpace(meta.BirdBgpSourceIPv4)
|
||||
if src == "" {
|
||||
src = strings.TrimSpace(meta.NodeIPv4)
|
||||
}
|
||||
if src != "" {
|
||||
loc.routerID = src
|
||||
loc.localV4 = src
|
||||
}
|
||||
if s := strings.TrimSpace(meta.BirdBgpSourceIPv6); s != "" {
|
||||
loc.localV6 = s
|
||||
@@ -32,7 +36,7 @@ func BirdLocalsForSpeaker(st store.Backend, tenantID, speakerID string) birdLoca
|
||||
// OverlayFragmentsForSpeaker re-renders bird.conf and peers fragment with speaker-specific BIRD locals.
|
||||
func OverlayFragmentsForSpeaker(st store.Backend, tenantID, speakerID, revisionID string, frags map[string]string) (map[string]string, error) {
|
||||
if frags == nil {
|
||||
return nil, fmt.Errorf("pipeline: overlay: nil fragments")
|
||||
frags = map[string]string{}
|
||||
}
|
||||
locals := BirdLocalsForSpeaker(st, tenantID, speakerID)
|
||||
out := make(map[string]string, len(frags))
|
||||
|
||||
@@ -40,3 +40,48 @@ func TestOverlayFragmentsForSpeaker_differentRouterID(t *testing.T) {
|
||||
t.Fatalf("sp2 router: %s", out2["bird.conf"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverlayFragmentsForSpeaker_nodeIPv4Fallback(t *testing.T) {
|
||||
m := store.NewMemory()
|
||||
m.SeedDemo()
|
||||
tenant, _, _, _, _ := m.DemoIDs()
|
||||
sp, err := m.CreateSpeaker(tenant, &store.Speaker{
|
||||
Role: "replica",
|
||||
Endpoint: "https://node.example.com",
|
||||
MetaJSON: `{"node_ipv4":"198.51.100.9"}`,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, err := pipeline.OverlayFragmentsForSpeaker(m, tenant, sp.ID, "rev1", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(out["bird.conf"], "198.51.100.9") {
|
||||
t.Fatalf("expected node_ipv4 as router id, got: %s", out["bird.conf"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverlayFragmentsForSpeaker_sourceOverridesNodeIPv4(t *testing.T) {
|
||||
m := store.NewMemory()
|
||||
m.SeedDemo()
|
||||
tenant, _, _, _, _ := m.DemoIDs()
|
||||
sp, err := m.CreateSpeaker(tenant, &store.Speaker{
|
||||
Role: "replica",
|
||||
Endpoint: "https://node.example.com",
|
||||
MetaJSON: `{"node_ipv4":"198.51.100.9","bird_bgp_source_ipv4":"203.0.113.40"}`,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, err := pipeline.OverlayFragmentsForSpeaker(m, tenant, sp.ID, "rev1", map[string]string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(out["bird.conf"], "203.0.113.40") {
|
||||
t.Fatalf("source should win: %s", out["bird.conf"])
|
||||
}
|
||||
if strings.Contains(out["bird.conf"], "198.51.100.9") {
|
||||
t.Fatalf("node_ipv4 should not win over source: %s", out["bird.conf"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ type renderData struct {
|
||||
|
||||
const composeTemplate = `# EvoBGP replica: bird2 + evobgp-agent + Traefik (Let's Encrypt DNS-01 / Cloudflare).
|
||||
# Generated by control plane. Do not commit secrets. ACME state: volume evobgp_speaker_traefik_letsencrypt.
|
||||
# BGP: ports 179:179 like control plane (overlay sets router id). Logs: docker compose logs -f bird2 evobgp-agent
|
||||
|
||||
name: evobgp-remote-speaker
|
||||
|
||||
@@ -70,12 +71,18 @@ services:
|
||||
bird2:
|
||||
image: {{.BirdImage}}
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
sysctls:
|
||||
net.ipv4.ip_forward: "1"
|
||||
net.ipv6.conf.all.forwarding: "1"
|
||||
ports:
|
||||
- "179:179/tcp"
|
||||
volumes:
|
||||
- bird_etc:/etc/bird
|
||||
- bird_run:/run/bird
|
||||
networks:
|
||||
- speaker-net
|
||||
logging: *default-logging
|
||||
|
||||
evobgp-agent:
|
||||
@@ -200,6 +207,8 @@ func dockerCommands(composeYAML string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString(`# EvoBGP replica: bird2 + agent + Traefik (Let's Encrypt DNS-01 / Cloudflare)
|
||||
# docker login git.shx.one # if images are private
|
||||
# BGP TCP/179 published like the control plane. Cloud security group must allow 179.
|
||||
# Logs: cd /opt/evobgp-speaker && docker compose logs -f bird2 evobgp-agent
|
||||
set -euo pipefail
|
||||
sysctl -w net.ipv4.ip_forward=1
|
||||
sysctl -w net.ipv6.conf.all.forwarding=1
|
||||
|
||||
@@ -34,11 +34,16 @@ func TestBuild_includesTraefikDNS01(t *testing.T) {
|
||||
"docker compose up -d",
|
||||
"sysctl -w net.ipv4.ip_forward=1",
|
||||
"evobgp_speaker_traefik_letsencrypt",
|
||||
`"179:179/tcp"`,
|
||||
"net.ipv4.ip_forward: \"1\"",
|
||||
} {
|
||||
if !strings.Contains(cmd, want) {
|
||||
t.Errorf("docker_commands missing %q", want)
|
||||
}
|
||||
}
|
||||
if strings.Contains(cmd, "network_mode: host") {
|
||||
t.Error("replica bird2 must not use network_mode: host")
|
||||
}
|
||||
if strings.Contains(cmd, "?set ") {
|
||||
t.Error("compose must bake values, not ${VAR:?set VAR}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user