-
Notifications
You must be signed in to change notification settings - Fork 9
/
report-status.sh
executable file
·165 lines (147 loc) · 6.61 KB
/
report-status.sh
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
#!/usr/bin/env bash
# Generate the status report as ANSI and HTML and text files.
#
# The ANSI form is also shown on stdout. The HTML form is stored on a volume,
# and is server by a separately running web server (in another container).
#
set -euo pipefail
shopt -s nocasematch
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
# IPStack is used to detect the country name from an IP address.
# Open https://ipstack.com/ and sign-up for a free plan: 10'000 requests/month.
# The script caches the results for 5-10 mins, making ~8640-4320 reqs/month.
: ${IPSTACK_API_KEY:?"Sign-up for IPStack.com's free plan and get an API key."}
# For how long (seconds) to cache the resolved country of the IP addresses.
# The information itself changes rarely, but it can be resolved with mistakes.
# 5-10 mins are good enough to match IPStack's free plan even if running 24/7.
: ${COUNTRY_CACHE_TIME:=600}
# Which IP to ping/traceroute for checking the connection.
# Just google for "pingable ip".
: ${STATUS_IP:="139.130.4.5"}
# The DNS resolver that is un-blocked in the firewall.
: ${NS:="8.8.4.4"}
# Where the status files are stored before showing. Note: they are cached.
: ${STATUS_DIR:="/tmp"}
temp_file="${STATUS_DIR}/index.temp"
ansi_file="${STATUS_DIR}/index.ansi"
html_file="${STATUS_DIR}/index.html"
text_file="${STATUS_DIR}/index.txt"
# ANSI code: can be either color/mode names, or purpose names (e.g. "title").
# Multiple codes can be combined, e.g. `echo hello | ansi red blink invert`.
declare -A ANSI=(
[brightwhite]='0;97'
[midgray]='0;37'
[gray]='1;30'
[red]='0;31'
[green]='0;32'
[yellow]='0;33'
[bold]='1'
[blink]='5'
[invert]='7'
)
# Wrap every line of the input into ANSI code. Unlike the whole-block wrapping,
# per-line wrapping is needed to fit into docker-compose, which adds its own
# colorful prefixes for each container, and they break the block's ANSI codes.
# For example: both the container name and the country name are ANSI-coded,
# but the country name is multiline here, each line must be ANSI-coded the same:
# status_1 | ┌─────────────────────┐
# status_1 | │┏━╸┏━╸┏━┓┏┳┓┏━┓┏┓╻╻ ╻│
# status_1 | │┃╺┓┣╸ ┣┳┛┃┃┃┣━┫┃┗┫┗┳┛│
# status_1 | │┗━┛┗━╸╹┗╸╹ ╹╹ ╹╹ ╹ ╹ │
# status_1 | └─────────────────────┘
function ansi() {
local esc=$(printf '\033')
local codes=""
for code_or_name in "$@"; do
codes="${codes};${ANSI[$code_or_name]:-$code_or_name}"
done
sed -e "s/^/${esc}[${codes}m/g" -e "s/\$/${esc}[0m/g"
}
# Stylise the text on the input with big letters and maybe a border.
function show() {
toilet --width 100 -f future --filter crop "$@"
}
# A unified date-time format.
function now() {
date +'%Y-%m-%d %H:%M:%S'
}
# Generate the ANSI output and write it to a file.
started=$(now)
{
echo "started $started // updated $(now)" | ansi midgray
echo
# TODO: can we also prevent the OpenDNS resolver from being blocked by the firewall?
# Detect the IP address of the traffic, i.e. how the internet sees us.
# myip=$( dig TXT +short o-o.myaddr.l.google.com @ns1.google.com | sed 's/"//g' )
resolver=$(dig +short resolver1.opendns.com @"${NS}" +time=1 +tries=1 | sed -e 's/;;.*//')
resolver=${resolver:-resolver1.opendns.com}
myip=$(dig +short myip.opendns.com "@${resolver}" +time=1 +tries=1 | sed -e 's/;;.*//')
if [[ -z "$myip" ]]; then
echo "Current IP address cannot be detected:" | ansi gray
echo "?.?.?.?" | show | ansi yellow
else
echo "Current IP address (for information):" | ansi gray
echo "${myip}" | show | ansi brightwhite
fi
# Detect the country of our current IP address, i.e. the VPN's outgoing gate.
# Cache it to reduce the load on the APIs, and to fit into their limits.
country_cache="/tmp/country-of-${myip}.txt"
if [[ -e "${country_cache}" && $(find "${country_cache}" -mmin "+${COUNTRY_CACHE_TIME}") ]]; then
country=$(cat "${country_cache}")
else
country=$(curl -s https://ipvigilante.com/ --connect-timeout 1 | jq -r .data.country_name)
if [[ -z "${country}" ]]; then
country=$(curl -s "http://api.ipstack.com/${myip}?access_key=${IPSTACK_API_KEY}" --connect-timeout 1 | jq -r .country_name)
fi
if [[ -n "${country}" || "$country" != null ]]; then
mkdir -p $(dirname "$country_cache")
echo "${country}" >"$country_cache"
fi
fi
if [[ -z "$country" || "$country" == null ]]; then
echo "Country cannot be detected:" | ansi gray
echo "-=-=-=-" | show --filter border | ansi yellow
elif [[ "$country" == Germany ]]; then
echo "Country must NOT be Germany" | ansi red
echo "${country}" | show --filter border | ansi red blink invert
else
echo "Country is as expected:" | ansi gray
echo "${country}" | show --filter border | ansi green
fi
# Detect the next-hop IP address with the default routing: is it VPN or a local network?
# 10.*.*.* is a VPN (AirVPN). Everything else is considered to be a local network.
nexthop=$(traceroute -n -m1 -q1 "${STATUS_IP}" 2>/dev/null | tail -n+2 | awk '{print $2}' || true)
if [[ -z "${nexthop}" ]]; then
echo "Next-hop IP address is absent (blocked):" | ansi gray
echo "-*-*-*-" | show | ansi yellow
elif [[ "${nexthop}" != "10."* ]]; then
echo "Next-hop IP address must be 10.*.*.*:" | ansi red
echo "${nexthop}" | show | ansi red
else
echo "Next-hop IP address is as expected:" | ansi gray
echo "${nexthop}" | show | ansi green
fi
# Print some additional information about the networking setup.
# It is better to alwats see it directly rather than interpreted.
echo
echo "Available interfaces:" | ansi bold
ip -oneline -color a show | awk '{print $2 "\t" $3 "\t" $4}'
echo
echo "Next hops per interface (only tun* should be permitted):" | ansi bold
ip -o a | awk '{print $2}' | sort -u | grep -v '^lo' | xargs -n1 -I {} bash -c 'echo -en "{}\t"; traceroute -n -m1 -q3 -i {} "'"${STATUS_IP}"'" 2>&1 | tail -n+2 || true'
echo
} >"$temp_file" 2>&1 || true
mv -f "${temp_file}" "${ansi_file}" # atomic switch
# Generate the HTML file, to be served separately.
{
echo '<?xml version="1.0" encoding="utf-8"?>'
echo '<style>body {zoom: 150%;}</style>'
echo '<meta http-equiv="refresh" content="1" />'
ansi2html <"${ansi_file}"
} >"${temp_file}" 2>&1 || true
mv -f "${temp_file}" "${html_file}" # atomic switch
# Generate the text file, just in case.
{
ansi2txt <"${ansi_file}"
} >"${temp_file}" 2>&1 || true
mv -f "${temp_file}" "${text_file}" # atomic switch