commit 3885c79b91a2c7974d6be1a4a802156b0dd0ac16 Author: Slawek Koszewski Date: Mon Jun 16 06:11:19 2025 +0200 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af96791 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +build diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8a37ebe --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module netbox-dns-updater diff --git a/netbox-dns-updater.go b/netbox-dns-updater.go new file mode 100644 index 0000000..6cede43 --- /dev/null +++ b/netbox-dns-updater.go @@ -0,0 +1,124 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "strings" +) + +type IPAddress struct { + Address string `json:"address"` + DNSName string `json:"dns_name"` +} + +type NetBoxResponse struct { + Results []IPAddress `json:"results"` +} + +func FetchNetboxIPAddresses(apiURL, token string) ([]IPAddress, error) { + req, err := http.NewRequest("GET", apiURL, nil) + if err != nil { + return nil, err + } + req.Header.Set("Accept", "application/json; indent=2") + req.Header.Set("Authorization", "Token "+token) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return nil, fmt.Errorf("unexpected status: %s, body: %s", resp.Status, string(body)) + } + + var nbResp NetBoxResponse + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&nbResp); err != nil { + return nil, err + } + + // Filter out entries with empty DNSName and strip CIDR from Address + var filtered []IPAddress + for _, ip := range nbResp.Results { + if ip.DNSName != "" { + ip.Address = strings.Split(ip.Address, "/")[0] + filtered = append(filtered, ip) + } + } + return filtered, nil +} + +func CreateDnsMasqConfig() { + token := os.Getenv("NETBOX_TOKEN") + if token == "" { + home := os.Getenv("HOME") + paths := []string{ + home + "/.netbox/token", + "/etc/netbox/token", + } + for _, path := range paths { + data, err := os.ReadFile(path) + if err == nil { + token = strings.TrimSpace(string(data)) + break + } + } + if token == "" { + log.Fatal("NETBOX_TOKEN not set and no token file found") + } + } + apiURL := "https://netbox.koszewscy.waw.pl/api/ipam/ip-addresses/" + ips, err := FetchNetboxIPAddresses(apiURL, token) + if err != nil { + log.Fatalf("Error fetching IP addresses: %v", err) + } + + dir := "/etc/dnsmasq.d" + if stat, err := os.Stat(dir); err == nil && stat.IsDir() { + file, err := os.Create(dir + "/netbox.conf") + if err != nil { + log.Fatalf("Failed to create netbox.conf: %v", err) + } + defer file.Close() + for _, ip := range ips { + fmt.Fprintf(file, "address=/%s/%s\n", ip.DNSName, ip.Address) + } + } else { + for _, ip := range ips { + fmt.Printf("address=/%s/%s\n", ip.DNSName, ip.Address) + } + } +} + +func main() { + listenAddr := flag.String("listen", ":8080", "address and port to listen on (e.g. :8080 or 127.0.0.1:8080)") + flag.Parse() + + http.HandleFunc("/update-dnsmasq", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("Method not allowed")) + return + } + CreateDnsMasqConfig() + cmd := exec.Command("systemctl", "restart", "dnsmasq.service") + if err := cmd.Run(); err != nil { + log.Printf("Failed to restart dnsmasq.service: %v", err) + w.Write([]byte("\nWarning: Failed to restart dnsmasq.service.\n")) + } + w.Write([]byte("DNSMasq config updated.")) + }) + log.Printf("Starting web service on %s...", *listenAddr) + if err := http.ListenAndServe(*listenAddr, nil); err != nil { + log.Fatalf("Failed to start server: %v", err) + } +} diff --git a/netbox-dns-updater.service b/netbox-dns-updater.service new file mode 100644 index 0000000..9ea0c32 --- /dev/null +++ b/netbox-dns-updater.service @@ -0,0 +1,13 @@ +[Unit] +Description=NetBox DNSMasq Updater Web Service +After=network.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/netbox-dns-updater -listen=:8080 +Restart=on-failure +User=nobody +Group=nogroup + +[Install] +WantedBy=multi-user.target diff --git a/query-netbox.sh b/query-netbox.sh new file mode 100644 index 0000000..903f81f --- /dev/null +++ b/query-netbox.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +. /root/.netbox-token + +# Build DNS data file from NetBox +curl -s \ + -H "Accept: application/json; indent=2" \ + -H "Authorization: Token $NETBOX_TOKEN" \ + "https://netbox.koszewscy.waw.pl/api/ipam/ip-addresses/" | \ + jq -r '.results[] | select(.dns_name!="") | [(.address | split("/"))[0], .dns_name] | @tsv' | \ + while read ip name + do + echo "address=/$name/$ip" + done > /etc/dnsmasq.d/netbox.conf + +# Restart the DNSMasq service +systemctl restart dnsmasq.service \ No newline at end of file