First version
[virtualhosts.git] / update-virtualhosts
1 #!/bin/bash
2 # update-virtualhosts Virtual hosts updater
3 # (C) 2019 Nirgal Vourgère <nirgal@debian.org>
4 # GPL-3
5
6 DIRS=()
7 VERBOSE=1  # 0=only_warnings 1=notice 2=debug
8 COMMANDLINE="$0 $@"
9 OUTPUT=""
10
11 parsearg() {
12         _OPT="${1%=?*}"
13         _VAL="${1#?*=}"
14 }
15
16 usage() {
17         #echo $'\n' $@ $'\n'
18         echo "Usage: $0 [options] basedir..."
19         echo "  -h|--help             Display that help"
20         echo "  --verbose-lvl=level   Define verbosity level. Defaults to $VERBOSE."
21         echo "                        0: Only warnings"
22         echo "                        1: Include notices"
23         echo "                        2: debug"
24         echo "                        Fell free to ignore STDERR"
25         echo "  -q|--quiet            Identical to --verbose-lvl=0"
26         echo "  -d|--debug            Identical to --verbose-lvl=2"
27 }
28
29 for arg in "$@"; do
30         parsearg $arg
31
32         case $_OPT in
33         --verbose-lvl)
34                 VERBOSE=$_VAL
35                 continue
36         ;;
37         -q|--quiet)
38                 VERBOSE=0
39                 continue
40         ;;
41         -d|--debug)
42                 VERBOSE=2
43                 continue
44         ;;
45         -h|--help)
46                 usage 
47                 exit 0
48         ;;
49         -*)
50                 echo "Unknown option $_OPT" >&2
51                 usage >&2
52                 exit 22
53         ;;
54         *)
55                 DIRS+=("$_OPT")
56                 continue
57         ;;
58         esac
59 done
60
61 NORMAL=`tput sgr0 2>/dev/null`
62 RED=`tput setaf 1 2>/dev/null`
63 GREEN=`tput setaf 2 2>/dev/null`
64
65 log() {
66         echo "$@" >&2
67 }
68
69 log_error() {
70         log $RED"$@"$NORMAL
71 }
72
73 log_info() {
74         if (( $VERBOSE > 0 ))
75         then
76                 log "$@"
77         fi
78 }
79
80 log_debug() {
81         if (( $VERBOSE > 1 ))
82         then
83                 log "$@"
84         fi
85 }
86
87 output() {
88         if [[ -n "$OUTPUT" ]]
89         then
90                 echo "$@" >> $OUTPUT
91         else
92                 echo "$@"  # STDOUT
93         fi
94 }
95
96 main() {
97         basedir="$1"
98
99         # ======================================================================
100         # Step 1
101         # Parse $INPUT that reads like
102         #   www1.lan < example.com www.example.com
103         #   192.168.0.11 < example.net www.example.net
104         #   fdce:266b:c77a::b < example.net www.example.net
105         # and fill these 6 vars:
106         redirections4_src=()      # ( "example.com www.example.com" "example.net www.example.net" )
107         redirections4_dst=()      # ( "192.168.0.10" "192.168.0.11" )
108         redirections4_dstname=()  # ( "www1.lan" "192.168.0.11" )
109         redirections6_src=()      # ( "example.com www.example.com" "example.net www.example.net" )
110         redirections6_dst=()      # ( "fdce:266b:c77a::a" "fdce:266b:c77a::b" )
111         redirections6_dstname=()  # ( "www1.lan" "fdce:266b:c77a::b" )
112         redirectionsn_src=()      # ( "example.com www.example.com" )
113         redirectionsn_dst=()      # ( "www1.lan" )
114
115         if test -r "$basedir/config"
116         then
117                 log_info "Reading $basedir/config"
118                 source "$basedir/config"
119         else
120                 log_error "Cannot read $basedir/config. Skiping."
121                 return
122         fi
123         if [[ -z "$INPUT" ]]
124         then
125                 log_error "Variable INPUT is empty. Skiping $basedir."
126                 return
127         fi
128         while read line
129         do
130                 if [[ "$line" =~ ^([^\>]*)[[:space:]]*\>[[:space:]]*(.*)$ ]]
131                 then
132                         sources="${BASH_REMATCH[1]}"
133                         destination=${BASH_REMATCH[2]}
134                         if [[ "$destination" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]
135                         then
136                                 log_info "Virtual host(s) $sources redirect(s) to ipv4 $destination, no ipv6."
137                                 redirections4_src+=("$sources")
138                                 redirections4_dst+=($destination)
139                                 redirections4_dstname+=($destination)
140                         elif [[ "$destination" =~ ^[0-9a-f:]*:[0-9a-f:]* ]]
141                         then
142                                 log_info "Virtual host(s) $sources redirect(s) to ipv6 $destination, no ipv4."
143                                 redirections6_src+=("$sources")
144                                 redirections6_dst+=("$destination")
145                                 redirections6_dstname+=("$destination")
146                         else
147                                 msg="Virtual host(s) $sources redirect(s) to host $destination: "
148                                 redirectionsn_src+=("$sources")
149                                 redirectionsn_dst+=("$destination")
150                                 ipv4=$(getent ahostsv4 "$destination" | cut -d ' ' -f 1 | sort -u)
151                                 ipv6=$(getent ahostsv6 "$destination" | cut -d ' ' -f 1 | sort -u)
152                                 if (( $(echo "$ipv4" | wc -l) > 1 ))
153                                 then
154                                         log_error "ERROR: $destination has several ipv4 addresses. This is not supported. ipv4 disabled for this domain."
155                                         ipv4=""
156                                 fi
157                                 if (( $(echo "$ipv6" | wc -l) > 1 ))
158                                 then
159                                         log_error "ERROR: $destination has several ipv6 addresses. This is not supported. ipv6 disabled for this domain."
160                                         ipv6=""
161                                 fi
162                                 if [[ -n "$ipv4" ]]
163                                 then
164                                         msg+="ipv4=$ipv4, "
165                                         redirections4_src+=("$sources")
166                                         redirections4_dst+=($ipv4)
167                                         redirections4_dstname+=($destination)
168                                 else
169                                         msg+="no ipv4,"
170                                 fi
171                                 if [[ -n "$ipv6" ]]
172                                 then
173                                         msg+="ipv6=$ipv6."
174                                         redirections6_src+=("$sources")
175                                         redirections6_dst+=($ipv6)
176                                         redirections6_dstname+=($destination)
177                                 else
178                                         msg+="no ipv6."
179                                 fi
180                                 log_info "$msg"
181                                 if [[ -z "$ipv4" && -z "$ipv6" && ! -d "$basedir/hostname" ]]
182                                 then
183                                         log_error "ERROR: $destination has no ipv4 nor ipv6."
184                                 fi
185                         fi
186                 else
187                         log_error "ERROR: syntax error while parsing $line"
188                 fi
189         done < "$INPUT"
190
191         #log_debug "============================================================="
192         #
193         #for (( i=0; i<${#redirections4_dst[@]}; i++ ))
194         #do
195         #       log_debug "${redirections4_dst[$i]}"
196         #       sources=${redirections4_src[$i]}
197         #       for s in ${sources[*]}
198         #       do
199         #               log_debug "  if $s"
200         #       done
201         #done
202
203         # ======================================================================
204         # Step 2: write the file
205         if [[ -n "$OUTPUT" ]]
206         then
207                 log_debug "Backup'ing $OUTPUT"
208                 mv "$OUTPUT" "$OUTPUT.bak"
209                 log_info "Writing to $OUTPUT."
210                 echo -n > "$OUTPUT"
211         fi
212
213         log_debug "============================================================="
214
215         output "############################################################################"
216         output "# DO NOT EDIT THAT FILE"
217         output "# Notice: That file was generated using:"
218         output "#    $COMMANDLINE"
219         output "# See $basedir/config"
220         output "############################################################################"
221
222         if [[ -f "$basedir/prolog" ]]
223         then
224                 fragment=$(cat "$basedir/prolog")
225                 output "${fragment}"
226         fi
227
228         if [[ -d "$basedir/ipv4" ]]
229         then
230                 for fragmentdir in $( find "$basedir/ipv4" -mindepth 1 -maxdepth 1 -type d | sort )
231                 do
232                         log_debug "Processing $fragmentdir"
233                         if test -f "$fragmentdir/prolog"
234                         then
235                                 fragment=$(cat "$fragmentdir/prolog")
236                                 output "${fragment}"
237                         fi
238                         for (( i=0; i<${#redirections4_dst[@]}; i++ ))
239                         do
240                                 destination="${redirections4_dst[$i]}"
241                                 dstname="${redirections4_dstname[$i]}"
242                                 log_debug "Processing $fragmentdir for destination $destination"
243                                 if test -f "$fragmentdir/destination"
244                                 then
245                                         fragment=$(cat "$fragmentdir/destination")
246                                         fragment="${fragment//[$]destination/$destination}"
247                                         fragment="${fragment//[$]dstname/$dstname}"
248                                         output "${fragment}"
249                                 fi
250                                 sources=${redirections4_src[$i]}
251                                 for source in ${sources[*]}
252                                 do
253                                         if test -f "$fragmentdir/line_$source"
254                                         then
255                                                 fragment=$(cat "$fragmentdir/line_$source")
256                                                 fragment="${fragment//[$]destination/$destination}"
257                                                 fragment="${fragment//[$]dstname/$dstname}"
258                                                 fragment="${fragment//[$]source/$source}"
259                                                 output "${fragment}"
260                                         elif test -f "$fragmentdir/line"
261                                         then
262                                                 fragment=$(cat "$fragmentdir/line")
263                                                 fragment="${fragment//[$]destination/$destination}"
264                                                 fragment="${fragment//[$]dstname/$dstname}"
265                                                 fragment="${fragment//[$]source/$source}"
266                                                 output "${fragment}"
267                                         fi
268                                 done
269                         done
270                 done
271         fi
272
273         if [[ -d "$basedir/ipv6" ]]
274         then
275                 for fragmentdir in $( find "$basedir/ipv6" -mindepth 1 -maxdepth 1 -type d | sort )
276                 do
277                         log_debug "processing $fragmentdir"
278                         if test -f "$fragmentdir/prolog"
279                         then
280                                 fragment=$(cat "$fragmentdir/prolog")
281                                 output "${fragment}"
282                         fi
283                         for (( i=0; i<${#redirections6_dst[@]}; i++ ))
284                         do
285                                 destination="${redirections6_dst[$i]}"
286                                 dstname="${redirections6_dstname[$i]}"
287                                 log_debug "processing $fragmentdir for destination $destination"
288                                 if test -f "$fragmentdir/destination"
289                                 then
290                                         fragment=$(cat "$fragmentdir/destination")
291                                         fragment="${fragment//[$]destination/$destination}"
292                                         fragment="${fragment//[$]dstname/$dstname}"
293                                         output "${fragment}"
294                                 fi
295                                 sources=${redirections6_src[$i]}
296                                 for source in ${sources[*]}
297                                 do
298                                         if test -f "$fragmentdir/line_$source"
299                                         then
300                                                 fragment=$(cat "$fragmentdir/line_$source")
301                                                 fragment="${fragment//[$]destination/$destination}"
302                                                 fragment="${fragment//[$]dstname/$dstname}"
303                                                 fragment="${fragment//[$]source/$source}"
304                                                 output "${fragment}"
305                                         elif test -f "$fragmentdir/line"
306                                         then
307                                                 fragment=$(cat "$fragmentdir/line")
308                                                 fragment="${fragment//[$]destination/$destination}"
309                                                 fragment="${fragment//[$]dstname/$dstname}"
310                                                 fragment="${fragment//[$]source/$source}"
311                                                 output "${fragment}"
312                                         fi
313                                 done
314                         done
315                 done
316         fi
317
318         if [[ -d "$basedir/hostname" ]]
319         then
320                 for fragmentdir in $( find "$basedir/hostname" -mindepth 1 -maxdepth 1 -type d | sort )
321                 do
322                         log_debug "processing $fragmentdir"
323                         if test -f "$fragmentdir/prolog"
324                         then
325                                 fragment=$(cat "$fragmentdir/prolog")
326                                 output "${fragment}"
327                         fi
328                         for (( i=0; i<${#redirectionsn_dst[@]}; i++ ))
329                         do
330                                 destination="${redirectionsn_dst[$i]}"
331                                 log_debug "processing $fragmentdir for destination $destination"
332                                 if test -f "$fragmentdir/destination"
333                                 then
334                                         fragment=$(cat "$fragmentdir/destination")
335                                         fragment="${fragment//[$]destination/$destination}"
336                                         output "${fragment}"
337                                 fi
338                                 sources=${redirectionsn_src[$i]}
339                                 for source in ${sources[*]}
340                                 do
341                                         if test -f "$fragmentdir/line_$source"
342                                         then
343                                                 fragment=$(cat "$fragmentdir/line_$source")
344                                                 fragment="${fragment//[$]destination/$destination}"
345                                                 fragment="${fragment//[$]source/$source}"
346                                                 output "${fragment}"
347                                         elif test -f "$fragmentdir/line"
348                                         then
349                                                 fragment=$(cat "$fragmentdir/line")
350                                                 fragment="${fragment//[$]destination/$destination}"
351                                                 fragment="${fragment//[$]source/$source}"
352                                                 output "${fragment}"
353                                         fi
354                                 done
355                         done
356                 done
357         fi
358
359         # ======================================================================
360         # Step 3: run the hook
361         if [[ -n "$HOOK" ]]
362         then
363                 read -p "$HOOK ? (y/n): " -n 1 confirm
364                 echo >&2
365                 if [[ "$confirm" = "y" ]]
366                 then
367                         log_info "Running hook: $HOOK"
368                         $HOOK
369                 else
370                         log_debug "Skipping hook: $HOOK"
371                 fi
372         fi
373 }
374
375 for dir in "${DIRS[@]}"
376 do
377         # Reset some vars that are supposed to be in $dir/condig
378         # We don't want to keep the value from previous dir
379         INPUT=""
380         OUTPUT=""
381         HOOK=""
382         main "$dir"
383 done
384
385 # vim: set ts=4 noet:
386