main.go (2922B)
1 package main 2 3 import ( 4 "context" 5 "encoding/json" 6 "os" 7 8 "code.dwrz.net/src/pkg/digip" 9 "code.dwrz.net/src/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/src/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.Debug.Printf( 124 "updated %s route 53 records:\n%s", domain, outcome, 125 ) 126 } 127 128 // Cancel the main context. 129 cancel() 130 }