code.dwrz.net

Go monorepo.
Log | Files | Refs

r53.go (2909B)


      1 package main
      2 
      3 import (
      4 	"context"
      5 	"encoding/json"
      6 	"os"
      7 
      8 	"code.dwrz.net/pkg/digip"
      9 	"code.dwrz.net/pkg/log"
     10 
     11 	"github.com/aws/aws-sdk-go-v2/aws"
     12 	"github.com/aws/aws-sdk-go-v2/service/route53"
     13 	"github.com/aws/aws-sdk-go-v2/service/route53/types"
     14 
     15 	"code.dwrz.net/cmd/r53/config"
     16 )
     17 
     18 func main() {
     19 	// Setup the logger.
     20 	l := log.New(os.Stderr)
     21 
     22 	// Setup the main context.
     23 	ctx, cancel := context.WithCancel(context.Background())
     24 
     25 	// Get the service configuration.
     26 	cfg, err := config.New(ctx)
     27 	if err != nil {
     28 		l.Error.Fatalf("failed to get config: %v", err)
     29 	}
     30 
     31 	// Create the Route53 client.
     32 	var svc = route53.NewFromConfig(*cfg.AWS)
     33 
     34 	// Get the IP address.
     35 	var ip4, ip6 string
     36 
     37 	// Dig with OpenDNS.
     38 	if ip, err := digip.OpenDNS(ctx, digip.A); err != nil {
     39 		l.Error.Printf("failed to get opendns ipv4 ip: %v", err)
     40 	} else {
     41 		ip4 = ip.String()
     42 	}
     43 	if ip, err := digip.OpenDNS(ctx, digip.AAAA); err != nil {
     44 		l.Error.Printf("failed to get opendns ipv6 ip: %v", err)
     45 	} else {
     46 		ip6 = ip.String()
     47 	}
     48 
     49 	// Fallback on Google.
     50 	if ip4 == "" {
     51 		if ip, err := digip.Google(ctx, digip.V4); err != nil {
     52 			l.Error.Printf("failed to get google ipv4 ip: %v", err)
     53 		} else {
     54 			ip4 = ip.String()
     55 		}
     56 	}
     57 	if ip6 == "" {
     58 		if ip, err := digip.Google(ctx, digip.V6); err != nil {
     59 			l.Error.Printf("failed to get google ipv6 ip: %v", err)
     60 		} else {
     61 			ip6 = ip.String()
     62 		}
     63 	}
     64 
     65 	// Abort if we don't have any IP address.
     66 	if ip4 == "" && ip6 == "" {
     67 		l.Error.Fatalf("failed to retrieve public ip")
     68 	}
     69 
     70 	// Update the domains.
     71 	for _, domain := range os.Args[1:] {
     72 		// Assemble the change(s).
     73 		var changes []types.Change
     74 		if ip4 != "" {
     75 			change := types.Change{
     76 				Action: types.ChangeActionUpsert,
     77 				ResourceRecordSet: &types.ResourceRecordSet{
     78 					Name: &domain,
     79 					ResourceRecords: []types.ResourceRecord{
     80 						{Value: &ip4},
     81 					},
     82 					TTL:  &cfg.TTL,
     83 					Type: types.RRTypeA,
     84 				},
     85 			}
     86 
     87 			changes = append(changes, change)
     88 		}
     89 		if ip6 != "" {
     90 			change := types.Change{
     91 				Action: types.ChangeActionUpsert,
     92 				ResourceRecordSet: &types.ResourceRecordSet{
     93 					Name: &domain,
     94 					ResourceRecords: []types.ResourceRecord{
     95 						{Value: &ip6}},
     96 					TTL:  &cfg.TTL,
     97 					Type: types.RRTypeAaaa,
     98 				},
     99 			}
    100 			changes = append(changes, change)
    101 		}
    102 
    103 		input := &route53.ChangeResourceRecordSetsInput{
    104 			ChangeBatch: &types.ChangeBatch{
    105 				Changes: changes,
    106 				Comment: aws.String("DDNS"),
    107 			},
    108 			HostedZoneId: &cfg.HostedZoneId,
    109 		}
    110 
    111 		result, err := svc.ChangeResourceRecordSets(ctx, input)
    112 		if err != nil {
    113 			l.Error.Fatalf("failed to update route 53: %v", err)
    114 		}
    115 
    116 		outcome, err := json.MarshalIndent(result, "", "  ")
    117 		if err != nil {
    118 			l.Error.Printf("failed to unmarshal response: %v", err)
    119 
    120 			outcome = []byte(result.ChangeInfo.Status)
    121 		}
    122 
    123 		l.Info.Printf(
    124 			"updated %s route 53 records:\n%s", domain, outcome,
    125 		)
    126 	}
    127 
    128 	// Cancel the main context.
    129 	cancel()
    130 }