-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathpdns-sql-slave-prune
More file actions
executable file
·225 lines (204 loc) · 7.09 KB
/
Copy pathpdns-sql-slave-prune
File metadata and controls
executable file
·225 lines (204 loc) · 7.09 KB
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#!/bin/sh
# pdns-sql-slave-prune (part of ossobv/vcutil)
# // wdoekes/2017,2021,2023,2025-2026 // Public Domain
#
# Reads PowerDNS SQL backend SLAVE data for stale domains and prunes
# those.
#
# Usage:
#
# # pdns-sql-slave-prune
# Pruning SLAVE domain 'old-removed-domain.tld' (704)
# ...
#
# It works by checking your PowerDNS SQL database backend for domains
# with:
# - type set to SLAVE
# - that haven't been 'last_check' updated in the last 3 days
#
# An extra check is performed before removal to confirm that the AVG of
# the last_check updates is at least two days ago. If the average is
# older, it aborts because the slaving might be broken.
#
#
# Tested with PowerDNS 4.0, but should work with PowerDNS 3.4 as well.
# Silence regular output by discarding stdout.
#
set -eu
test -z "${DBNAME:-}" && \
DBNAME=$(find /etc/powerdns/ -name '*.conf' -type f |
xargs grep -hE '^g(mysql|pgsql)-dbname=' | sed -e 's/[^=]*=//')
test -z "${DBTYPE:-}" && \
DBTYPE=$(find /etc/powerdns/ -name '*.conf' -type f |
xargs sed -ne 's/^g\(mysql\|pgsql\)-dbname=.*/\1/p')
case "$DBTYPE" in
mysql)
query() {
mysql --defaults-file=/etc/mysql/debian.cnf "$DBNAME" -BNe "$1"
}
SQL_EPOCH='UNIX_TIMESTAMP()'
SQL_TRUE=1
;;
pgsql)
if test $(whoami) != postgres; then
cd /
exec sudo -Hupostgres env DBNAME=$DBNAME DBTYPE=$DBTYPE $0 "$@"
exit 2 # should not get here
fi
query() {
psql -tAF "$(printf '\t')" -c "$1" "$DBNAME"
}
SQL_EPOCH='EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)'
SQL_TRUE=t
;;
'')
DBTYPE=bind
SQL_EPOCH=irrelevant
;;
*)
echo "NotImplementedError: DBTYPE=$DBTYPE" >&2
exit 1
;;
esac
AVG_OLD_TIMESTAMP="($SQL_EPOCH - 2 * 86400)" # '-2 days'
OLD_TIMESTAMP="($SQL_EPOCH - 3 * 86400)" # '-3 days'
test_slave_running() {
#return # <-- DISABLE this check?
if test "$(query "
SELECT AVG(last_check) > $AVG_OLD_TIMESTAMP AS val
FROM domains WHERE type = 'SLAVE';")" != "$SQL_TRUE"; then
updated=$(query "
SELECT name FROM domains WHERE last_check >= $AVG_OLD_TIMESTAMP
AND type = 'SLAVE' ORDER BY name;")
not_updated=$(query "
SELECT name FROM domains WHERE last_check < $AVG_OLD_TIMESTAMP
AND type = 'SLAVE' ORDER BY name;")
if test -z "$updated"; then
cat >&2 <<EOF
Slave updates are probably broken!
No domain appears to have been updated recently.
Stopping the pruning to avoid accidents..
EOF
else
updated_len=$(echo "$updated" | wc -l)
not_updated_len=$(echo "$not_updated" | wc -l)
# NOTE: Using tail instead of head in the sample, because we don't
# want to see only PTR (in-addr.arpa.) ranges.
cat >&2 <<EOF
Slave updates _might_ be broken.
$updated_len domains are updated, $not_updated_len are not.
Sample of updated domains:
- ...
$(echo "$updated" | sed -e 's/^/- /' | tail -n10)
Sample of not updated domains:
- ...
$(echo "$not_updated" | sed -e 's/^/- /' | tail -n10)
Perhaps you deleted a lot of domains -- more than half? (The not-updated ones.)
If so, you should disable this check temporarily, so the pruning can commence.
See the to-be-pruned candidates for yourself:
SELECT name FROM domains WHERE last_check < $OLD_TIMESTAMP
AND type = 'SLAVE' ORDER BY name;
Stopping for now..
EOF
fi
exit 1
fi
}
old_slave_domains() {
query "
SELECT id, name FROM domains
WHERE type = 'SLAVE' AND last_check < $OLD_TIMESTAMP
ORDER BY name, id;"
}
prune_domain() {
local id="$1"
local domain="$2"
if ! test "$((id-1))" -lt "$id"; then
echo "Not an integer id for domain '$domain': $id" >&2
exit 1
fi
echo "Pruning SLAVE domain '$domain' ($id)"
query "
DELETE FROM comments WHERE domain_id = $id;
DELETE FROM cryptokeys WHERE domain_id = $id;
DELETE FROM domainmetadata WHERE domain_id = $id;
DELETE FROM records WHERE domain_id = $id;
DELETE FROM domains WHERE id = $id;"
}
if test "$DBTYPE" = bind; then
test_slave_running() {
local ip host rest
# DNS seems up:
cat /etc/powerdns/autoprimaries.list | while read ip host rest; do
if test "$(dig -t A "$host" "@$ip" +short)" != "$ip"; then
echo "DNS $host at $ip down?" >&2
exit 1
fi
done
# Files have been updated recently?
# > find /var/lib/powerdns -type f -mtime +7 # none contain DNSKEY
# > find /var/lib/powerdns -type f -mtime -7 # all contain DNSKEY
# # .. except some, which have been altered by the user.
# We'll use the PDNS automatic KSK Thursday rollover to conclude that
# slaving still works.
if ! find /var/lib/powerdns -type f -mtime -7 | grep -qF ''; then
echo 'No recent slave updates (slaving broken?)' >&2
exit 1
fi
}
old_slave_domains() {
local auto autos id domain
autos=$(sed -e 's/[[:blank:]].*//' /etc/powerdns/autoprimaries.list)
# Or: pdns_control list-zones
# Also use the mtime +7 here (see Thursday rollover at
# test_slave_running). This will keep old records for at least a week.
find /var/lib/powerdns/zones.slave.d/ -type f -mtime +7 |
LC_ALL=C sort |
sed -Ee 's@^(.*)/([^/]*)$@\1/\2\t\2@' | while read id domain
do
local res try
# Try DNS three times
for try in 1 2 3; do
for auto in $autos; do
res=$(dig -t SOA "$domain" "@$auto" +short)
test -z "$res" || break
done
test -z "$res" || break
# echo "retry $domain" >&2
done
# No results after three runs? It's gone.
if test -z "$res"; then
printf '%s\t%s\n' "$id" "$domain"
fi
done
}
prune_domain() {
local id domain safe_domain safe_domain_dot
id=$1
domain=$2
safe_domain=$(echo "$domain" | sed -e 's/[^A-Za-z0-9]/[&]/g')
safe_domain_dot="${safe_domain}[.]"
# Copy with permissions. Sed -i inline so we have good perms on the target.
cp -a /var/lib/powerdns/supermaster.conf /var/lib/powerdns/supermaster.conf.new
sed -i -e "
/^# Superslave zone '$safe_domain_dot'\\|^zone \"$safe_domain\"/,/^};\$/d
" /var/lib/powerdns/supermaster.conf.new
# Move the new config over the old one.
diff -pu /var/lib/powerdns/supermaster.conf \
/var/lib/powerdns/supermaster.conf.new || true
mv /var/lib/powerdns/supermaster.conf.new /var/lib/powerdns/supermaster.conf
rm -v "$id"
pdns_control rediscover
pdns_control reload # needed?
}
fi
# Only prune data if most of the slaving works.
test_slave_running || exit 1
# Do some pruning.
TAB=$(printf '\t')
old_slave_domains | while read line; do
id=${line%$TAB*}
domain=${line#*$TAB}
prune_domain $id $domain
done
# vim: set ts=8 sw=4 sts=4 et ai: