summaryrefslogtreecommitdiff
path: root/frontends/scripts/dns-failover.ksh
blob: 4042ee36e952a74af1df714744152794e8166bec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#!/bin/ksh

ZONES_DIR=/var/nsd/zones/master/
DEFAULT_MASTER=fishfinger.buetow.org
DEFAULT_STANDBY=blowfish.buetow.org

determine_master_and_standby () {
    local master=$DEFAULT_MASTER
    local standby=$DEFAULT_STANDBY

    # Based on the week of the year, we swap the master/standby roles.
    # This is so that we always have up-to-date Let's Encrypt TLS certificates
    # renewed on either server.
    local -i week_of_the_year=$(date +%U)
    if [ $(( week_of_the_year % 2 )) -ne 0 ]; then
        local tmp=$master
        master=$standby
        standby=$tmp
    fi

    echo "Master is $master, standby is $standby"

    MASTER_A=$(host $master | awk '/has address/ { print $(NF) }')
    MASTER_AAAA=$(host $master | awk '/has IPv6 address/ { print $(NF) }')
    STANDBY_A=$(host $standby | awk '/has address/ { print $(NF) }')
    STANDBY_AAAA=$(host $standby | awk '/has IPv6 address/ { print $(NF) }')
}

transform () {
    sed -E '
        /IN A .*; Enable failover/ {
            /^mirror/! {
                s/^(.*) 300 IN A (.*) ; (.*)/\1 300 IN A '$MASTER_A' ; \3/;
            }
            /^mirror/ {
                s/^(.*) 300 IN A (.*) ; (.*)/\1 300 IN A '$STANDBY_A' ; \3/;
            }
        }
        /IN AAAA .*; Enable failover/ {
            /^mirror/! {
                s/^(.*) 300 IN AAAA (.*) ; (.*)/\1 300 IN AAAA '$MASTER_AAAA' ; \3/;
            }
            /^mirror/ {
                s/^(.*) 300 IN AAAA (.*) ; (.*)/\1 300 IN AAAA '$STANDBY_AAAA' ; \3/;
            }
        }
        / ; serial/ {
            s/^( +) ([0-9]+) .*; (.*)/\1 '"$(date +%s)"' ; \3/;
        }
    '
}

zone_is_ok () {
    local zone=$1
    local domain=${zone%.zone}

    echo "Testing zone $zone (if no NS output, then doesn't work)"
    dig $domain @localhost | grep "$domain.*IN.*NS"
}

failover_zone () {
    local zone_file=$1
    local zone=$(basename $zone_file)

    cat $zone_file | transform > $zone_file.new.tmp 

    grep -v ' ; serial' $zone_file.new.tmp > $zone_file.new.noserial.tmp
    grep -v ' ; serial' $zone_file > $zone_file.old.noserial.tmp

    if diff $zone_file.new.noserial.tmp $zone_file.old.noserial.tmp; then
        echo "zone $zone_file hasn't changed"
        rm $zone_file.*.tmp
        return
    fi

    cp $zone_file $zone_file.bak
    mv $zone_file.new.tmp $zone_file
    rm $zone_file.*.tmp
    nsd-control reload

    if zone_is_ok $zone; then
        if [ -f $zone_file.invalid ]; then
            rm $zone_file.invalid
        fi
        echo "Failover of zone $zone completed"
        return
    fi

    echo "Rolling back $zone_file changes"
    cp $zone_file $zone_file.invalid
    mv $zone_file.bak $zone_file
    nsd-control reload
    zone_is_ok $zone
}

main () {
    determine_master_and_standby
    for zone_file in $ZONES_DIR/*.zone; do
        failover_zone $zone_file
    done
}

main