338 lines
7.6 KiB
Go
338 lines
7.6 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.readonly.ch/bouzoure/gestion-dns/dnsconfig"
|
|
"git.readonly.ch/bouzoure/gestion-dns/helpers"
|
|
"git.readonly.ch/bouzoure/gestion-dns/hetzner"
|
|
)
|
|
|
|
func main() {
|
|
log := helpers.GetLogger()
|
|
|
|
config, err := helpers.GetConfig()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if config.DryRun {
|
|
log.Warn("Dry run is enabled, changes will not be applied")
|
|
} else {
|
|
log.Warn("Dry run is disabled, changes will be applied")
|
|
}
|
|
|
|
// Fetch zones from Hetzner API
|
|
log.Info("Fetching existing zones from Hetzner API")
|
|
hZonesTmp, err := hetzner.GetZones()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Fetch records from Hetzner API
|
|
log.Info("Fetching existing records from Hetzner API")
|
|
var hZones []hetzner.Zone
|
|
for _, zone := range hZonesTmp {
|
|
err = hetzner.GetRecords(&zone)
|
|
if err != nil {
|
|
log.Error(err)
|
|
continue
|
|
}
|
|
|
|
hZones = append(hZones, zone)
|
|
}
|
|
|
|
// Fetch zones from config files
|
|
log.Info("Fetching zones from DNS config files")
|
|
zones, err := dnsconfig.GetZones()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Create missing config files from existing zones
|
|
log.Info("Checking if we need to create DNS config files from existing zones")
|
|
for _, hZone := range hZones {
|
|
found := false
|
|
for _, zone := range zones {
|
|
if strings.EqualFold(hZone.Name, zone.Domain) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if found {
|
|
continue
|
|
}
|
|
|
|
log.Info("Creating DNS config file for existing zone", "zone", hZone.Name)
|
|
|
|
zone := dnsconfig.Zone{
|
|
Domain: strings.ToLower(hZone.Name),
|
|
DefaultTTL: 3600,
|
|
}
|
|
|
|
for _, record := range hZone.Records {
|
|
zone.Records = append(zone.Records, dnsconfig.Record{
|
|
Name: record.Name,
|
|
Type: record.Type,
|
|
Value: record.Value,
|
|
TTL: record.TTL,
|
|
Flat: false,
|
|
})
|
|
}
|
|
|
|
err = dnsconfig.CreateZone(zone)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// Fetch zones from config files again
|
|
// This is because the previous step might have created new config files
|
|
log.Info("Fetching zones from DNS config files")
|
|
zones, err = dnsconfig.GetZones()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
// Keep operations in these slices
|
|
// Sync is only performed after diff calculation is done
|
|
var recordsToCreate []hetzner.Record
|
|
var recordsToUpdate []hetzner.Record
|
|
var recordsToDelete []hetzner.Record
|
|
|
|
// PushOver message, will only be sent if PushOver enabled and message not empty
|
|
var pushoverMessages []string
|
|
|
|
log.Info("Calculating sync diff (step 1: create/update)")
|
|
var keepTheseIds []string
|
|
for _, zone := range zones {
|
|
if zone.DefaultTTL <= 0 {
|
|
zone.DefaultTTL = 3600
|
|
}
|
|
|
|
for _, hZone := range hZones {
|
|
if strings.EqualFold(zone.Domain, hZone.Name) {
|
|
log.Info("Calculating sync diff for zone", "name", zone.Domain)
|
|
|
|
var alreadyFoundIds []string
|
|
for _, record := range zone.Records {
|
|
if record.Flat {
|
|
record.Value = helpers.ResolveRecord(
|
|
record.Value, record.Type,
|
|
)
|
|
|
|
if len(record.Value) == 0 {
|
|
log.Error("Could not flatten record, skipping", "record", record)
|
|
continue
|
|
}
|
|
}
|
|
|
|
if record.TTL <= 0 {
|
|
record.TTL = zone.DefaultTTL
|
|
}
|
|
|
|
var id string
|
|
for _, hRecord := range hZone.Records {
|
|
if slices.Contains(alreadyFoundIds, hRecord.ID) {
|
|
continue
|
|
}
|
|
|
|
if !strings.EqualFold(record.Name, hRecord.Name) {
|
|
continue
|
|
}
|
|
|
|
if !strings.EqualFold(record.Type, hRecord.Type) {
|
|
continue
|
|
}
|
|
|
|
id = hRecord.ID
|
|
alreadyFoundIds = append(alreadyFoundIds, id)
|
|
break
|
|
}
|
|
|
|
if len(id) > 0 {
|
|
keepTheseIds = append(keepTheseIds, id)
|
|
updateNeeded := false
|
|
for _, hRecord := range hZone.Records {
|
|
if hRecord.ID == id {
|
|
if record.TTL != hRecord.TTL {
|
|
updateNeeded = true
|
|
}
|
|
|
|
if record.Value != hRecord.Value {
|
|
updateNeeded = true
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
if updateNeeded {
|
|
log.Info(
|
|
"Marking record for update",
|
|
"id", id,
|
|
"name", record.Name,
|
|
"type", record.Type,
|
|
"value", record.Value,
|
|
"ttl", record.TTL,
|
|
)
|
|
|
|
recordsToUpdate = append(recordsToUpdate, hetzner.Record{
|
|
ID: id,
|
|
Type: record.Type,
|
|
Name: record.Name,
|
|
Value: record.Value,
|
|
TTL: record.TTL,
|
|
ZoneID: hZone.ID,
|
|
})
|
|
|
|
pushoverMessages = append(pushoverMessages, fmt.Sprintf(
|
|
"<b>Action:</b> update\n<b>Zone:</b> %s\n<b>Record:</b> %s\n<b>Type:</b> %s\n<b>Value:</b> %s\n<b>TTL:</b> %d",
|
|
zone.Domain,
|
|
record.Name,
|
|
record.Type,
|
|
record.Value,
|
|
record.TTL,
|
|
))
|
|
}
|
|
} else {
|
|
log.Info(
|
|
"Marking record for creation",
|
|
"zone_id", hZone.ID,
|
|
"name", record.Name,
|
|
"type", record.Type,
|
|
"value", record.Value,
|
|
"ttl", record.TTL,
|
|
)
|
|
|
|
recordsToCreate = append(recordsToCreate, hetzner.Record{
|
|
Type: record.Type,
|
|
Name: record.Name,
|
|
Value: record.Value,
|
|
TTL: record.TTL,
|
|
ZoneID: hZone.ID,
|
|
})
|
|
|
|
pushoverMessages = append(pushoverMessages, fmt.Sprintf(
|
|
"<b>Action:</b> create\n<b>Zone:</b> %s\n<b>Record:</b> %s\n<b>Type:</b> %s\n<b>Value:</b> %s\n<b>TTL:</b> %d",
|
|
zone.Domain,
|
|
record.Name,
|
|
record.Type,
|
|
record.Value,
|
|
record.TTL,
|
|
))
|
|
}
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Info("Calculating sync diff (step 2: delete)")
|
|
for _, zone := range zones {
|
|
for _, hZone := range hZones {
|
|
if strings.EqualFold(zone.Domain, hZone.Name) {
|
|
log.Info("Calculating sync diff for zone", "name", zone.Domain)
|
|
|
|
for _, hRecord := range hZone.Records {
|
|
if !slices.Contains(keepTheseIds, hRecord.ID) {
|
|
log.Info(
|
|
"Marking record for deletion",
|
|
"id", hRecord.ID,
|
|
"name", hRecord.Name,
|
|
"type", hRecord.Type,
|
|
"value", hRecord.Value,
|
|
"ttl", hRecord.TTL,
|
|
)
|
|
|
|
recordsToDelete = append(recordsToDelete, hetzner.Record{
|
|
ID: hRecord.ID,
|
|
})
|
|
|
|
pushoverMessages = append(pushoverMessages, fmt.Sprintf(
|
|
"<b>Action:</b> delete\n<b>Zone:</b> %s\n<b>Record:</b> %s\n<b>Type:</b> %s\n<b>Value:</b> %s\n<b>TTL:</b> %d",
|
|
hZone.Name,
|
|
hRecord.Name,
|
|
hRecord.Type,
|
|
hRecord.Value,
|
|
hRecord.TTL,
|
|
))
|
|
}
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Info("Starting sync (step 1: delete)", "dry_run", config.DryRun)
|
|
for _, record := range recordsToDelete {
|
|
if !config.DryRun {
|
|
hetzner.DeleteRecord(&record)
|
|
if err != nil {
|
|
log.Error(err)
|
|
continue
|
|
}
|
|
|
|
log.Warn(
|
|
"Record deleted",
|
|
"ID", record.ID,
|
|
)
|
|
}
|
|
}
|
|
|
|
log.Info("Starting sync (step 2: create)", "dry_run", config.DryRun)
|
|
for _, record := range recordsToCreate {
|
|
if !config.DryRun {
|
|
newRecord, err := hetzner.CreateRecord(&record)
|
|
if err != nil {
|
|
log.Error(err)
|
|
continue
|
|
}
|
|
|
|
log.Warn(
|
|
"Record created",
|
|
"ID", newRecord.ID,
|
|
)
|
|
}
|
|
}
|
|
|
|
log.Info("Starting sync (step 3: update)", "dry_run", config.DryRun)
|
|
for _, record := range recordsToUpdate {
|
|
if !config.DryRun {
|
|
newRecord, err := hetzner.UpdateRecord(&record)
|
|
if err != nil {
|
|
log.Error(err)
|
|
continue
|
|
}
|
|
|
|
log.Warn(
|
|
"Record updated",
|
|
"ID", newRecord.ID,
|
|
)
|
|
}
|
|
}
|
|
|
|
log.Info("Sync is finished, all done!")
|
|
|
|
if !config.DryRun && config.PushOver.Enable && len(pushoverMessages) > 0 {
|
|
log.Info("Changes made, sending PushOver notifications")
|
|
|
|
for _, message := range pushoverMessages {
|
|
err = helpers.PushoverSendMessage(helpers.PushoverMessage{
|
|
Message: message,
|
|
Title: "Changes made to DNS record",
|
|
AppKey: config.PushOver.AppKey,
|
|
UserKey: config.PushOver.UserKey,
|
|
})
|
|
|
|
if err != nil {
|
|
log.Error(err)
|
|
}
|
|
}
|
|
}
|
|
}
|