various fixes
[automated-distro-installer] / pxe-server
1 #!/bin/bash
2 # Copyright (C) 2016 Ian Kelling
3
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
8
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
18
19 [[ $EUID == 0 ]] || exec sudo -E "${BASH_SOURCE[0]}" "$@"
20
21 readonly this_file="$(readlink -f -- "${BASH_SOURCE[0]}")"
22 script_dir="${this_file%/*}"
23 # shellcheck source=./bash-trace
24 source "${script_dir}/bash-trace"
25 cd $script_dir
26 PATH="$PATH:$PWD"
27
28
29 usage() {
30 cat <<EOF
31 Usage: ${0##*/} [OPTIONS] [HOST] [TYPE]
32 Configure dnsmasq boot options and fai-chboot if appropriate. This is
33 not general purpose, it has code specific to dhcp servers I run.
34
35 Without TYPE, disable server and fai server. In that case, HOST is only
36 needed for fsf office network.
37
38 HOST Only do dhcp pxe for HOST. The hostname must be known to the dhcp
39 server to target its mac. Use "default" for all hosts.
40 Required in fsf office environment.
41
42 TYPE One of arch, parabola, plain, fai.
43
44 -a Don't setup pxe, just Wait for 2 dhcp acks, then disable the pxe
45 server after a delay. First ack is for pxe boot, 2nd ack is
46 for os boot. Sometimes on debian, there is a 3rd one shortly
47 after the 2nd. I can't remember exactly why this caused a
48 problem, but I'm hoping the sleep will take care of it.
49 -d Don't alter dhcp config. Only make sense for fai type, and on network
50 other than home or fsf, when using fai-cd, or pxe-kexec.
51 -k Pass -k to myfai-chboot.
52 --no-r Pass --no-r to myfai-chboot.
53 -r Don't redeploy fai config. For example, if there is a different host
54 that is mid-install.
55
56 -S sets FAI_ACTION=sysinfo, see myfai-chboot for more info.
57 -w Setup pxe, then wait like -a.
58 -h|--help Print help and exit
59
60
61 Note, when switching between plain and arch or parabola, you will need to
62 do something like:
63 ssh wrt
64 cd /mnt/usb
65 rm tftpboot
66 ln -s <arch/parabola/debian iso dir> tftpboot
67
68
69 Notes on debugging pxe dhcp tftp:
70
71 For debugging dhcp, add to /etc/dnsmasq.conf: log-dhcp
72
73 Newer openwrt runs dnsmasq with a whitelist of readable files and dirs:
74
75 ps ww :
76 /sbin/ujail -t 5 -n dnsmasq -u -l -r /bin/ubus -r /etc/TZ -r /etc/dnsmasq.conf -r /etc/ethers -r /etc/group -r /etc/hosts -r /etc/passwd -w /tmp/dhcp.leases -r /tmp/dnsmasq.d -r /tmp/hosts -r /tmp/resolv.conf.d -r /usr/bin/jshn -r /usr/lib/dnsmasq/dhcp-script.sh -r /usr/share/dnsmasq/dhcpbogushostname.conf -r /usr/share/dnsmasq/rfc6761.conf -r /usr/share/dnsmasq/trust-anchors.conf -r /usr/share/libubox/jshn.sh -r /var/etc/dnsmasq.conf.cfg01411c -w /var/run/dnsmasq/ -- /usr/sbin/dnsmasq -C /var/etc/dnsmasq.conf.cfg01411c -k -x /var/run/dnsmasq/dnsmasq.cfg01411c.pid
77
78 logging tftp requests:
79 /etc/default/tftpd-hpa:
80 add -vv:
81 TFTP_OPTIONS="--secure -vv"
82 jr -u tftpd-hpa -f
83
84 Note: Uses GNU getopt options parsing style
85 EOF
86 exit $1
87 }
88
89 pre="${0##*/}:"
90 m() { printf "$pre %s\n" "$*"; "$@"; }
91 e() { printf "$pre %s\n" "$*"; }
92 err() { echo "[$(date +'%Y-%m-%d %H:%M:%S%z')]: $pre: $*" >&2; }
93
94 PATH="/a/exe:$PATH"
95
96 ##### begin command line parsing ########
97
98 dhcp=true
99 redep=true
100 acks=2
101 wait=false
102 fsf_office=false
103
104 case $HOSTNAME in
105 x3|kw) fsf_office=true ;;
106 esac
107
108 chboot_args=()
109 temp=$(getopt -l no-r,help adkrSwh "$@") || usage 1
110 eval set -- "$temp"
111 while true; do
112 case $1 in
113 -a) wait=true; set=false; shift ;;
114 -d) dhcp=false; shift ;;
115 -k) chboot_args+=(-k); shift ;;
116 --no-r) chboot_args+=(--no-r); shift ;;
117 -r) redep=false; shift ;;
118 -S) chboot_args+=(-S); shift ;;
119 -w) wait=true; set=true; shift ;;
120 -h|--help) usage ;;
121 --) shift; break ;;
122 *) echo "$0: Internal error!" ; exit 1 ;;
123 esac
124 done
125
126 read -r host type <<<"$@"
127
128 case $# in
129 [01]);;
130 2)
131 case $type in
132 arch|parabola) cmd=archlike ;;
133 fai) cmd=fai ;;
134 *)
135 echo "$0: error expected type of arch|parabola|fai"
136 echo
137 usage 1
138 ;;
139 esac
140 ;;
141 *)
142 echo "$0: error: expected 0-2 arguments"
143 echo
144 usage 1
145 ;;
146 esac
147
148
149 if $wait && ! $dhcp; then
150 echo "$0: error -w conflicts with -d, choose one or other" >&2
151 exit 1
152 fi
153
154 if $fsf_office && [[ ! $host ]]; then
155 echo "$0: at fsf_office, provide HOST arg" >&2
156 exit 1
157 fi
158
159 if [[ $host && $host != default ]]; then
160 host_tag="tag:$host,"
161 fi
162
163 ##### end command line parsing ########
164
165 archlike() {
166 cat <<EOF
167 ${host_tag}209,boot/syslinux/${type}iso.cfg
168 ${host_tag}210,/${type}/
169 ${host_tag}option:bootfile-name,/${type}/boot/syslinux/lpxelinux.0
170 EOF
171 }
172
173 plain() {
174 # if arch based was used before, this additionally needs
175 # the tftp link in /mnt/usb to be changed.
176 cat <<EOF
177 ${host_tag}option:bootfile-name,pxelinux.0
178 EOF
179 }
180
181 fai() {
182 cat <<EOF
183 ${host_tag}option:bootfile-name,pxelinux.0
184 ${host_tag}option:server-ip-address,$faiserverip
185 ${host_tag}option:tftp-server,$faiserverip
186 EOF
187 # Note, previously used normal dnsmasq option, but it requires dnsmasq
188 # restart, which causes momentary dns failures, which can bork an
189 # install.
190 #
191 # dhcp-boot=${host_tag}pxelinux.0,faiserver.b8.nz,faiserver.b8.nz
192 }
193
194 ack-wait() {
195 if $fsf_office; then
196 wait_cmd="ssh tarantula.office.fsf.org tail -n0 -f /var/log/syslog"
197 else
198 wait_cmd="ssh cmc logread -f"
199 fi
200 wait_count=$1
201 if [[ $host ]]; then
202 if $fsf_office; then
203 host_regex=" $(getent hosts kw | awk '{print $1}' | sed 's/\./\\./g')"
204 else
205 host_regex=" $host"
206 fi
207 fi
208 regex=".*DHCPACK.*$host_regex\b"
209 i=0
210 while (( i != wait_count )) && read -r line; do
211 if [[ $line =~ $regex ]]; then
212 i=$((i+1))
213 echo $line
214 fi
215 done < <($wait_cmd ||:) # tail returns 2 it seems
216 m sleep 20
217 }
218
219 set-pxe() {
220 $dhcp || return 0
221 if $fsf_office; then
222 if [[ ! $cmd ]]; then
223 e "removing pxe for $host on tarantula"
224 ssh tarantula.office.fsf.org bash -e <<EOF
225 sed -ri 's/^( *host +$host *\{).*/\1/' /etc/dhcp/dhcpd.conf
226 systemctl restart isc-dhcp-server
227 EOF
228 elif [[ $cmd == fai ]]; then
229 e "adding pxe for $host on tarantula"
230 ssh tarantula.office.fsf.org bash -e <<EOF
231 sed -ri 's/^( *host +$host *\{).*/\1 next-server faiserver.office.fsf.org; filename "pxelinux.0";/' /etc/dhcp/dhcpd.conf
232 systemctl restart isc-dhcp-server
233 EOF
234 fi
235 else
236 e "updating dnsmasq.conf:"
237 m $cmd
238 ${cmd:-:}|ssh cmc "dd of=/var/run/dnsmasq/dhcpopts.conf; /etc/init.d/dnsmasq reload
239 $([[ $type == arch || $type == parabola ]] && echo archlike-pxe-mount)"
240 fi
241 }
242
243 type -t host &>/dev/null || sudo apt-get -y install dnsutils
244 faiserverip=$(host faiserver | sed -rn 's/^\S+ has address //p;T;q' ||:)
245 if [[ ! $faiserverip || $faiserverip =~ [[:space:]] ]]; then
246 echo "$0: error: failed to get \$faiserverip, got: $faiserverip"
247 exit 1
248 fi
249
250
251 if $set; then
252 set-pxe
253 if [[ $type == fai ]]; then
254 if $redep; then
255 m fai-redep
256 fi
257 m myfai-chboot ${chboot_args[@]} $host
258 else
259 # This will fail if faiserver is not setup, so ignore any
260 # failure and don't bother us about it.
261 m myfai-chboot &>/dev/null ||:
262 fi
263 fi
264
265 if $wait; then
266 # fai's debian jessie 8.5ish does 2 dhcp requests when booting,
267 # roughly 4 seconds apart. Earlier
268 # versions did just 1. Now testing on a vm, it does 1.
269 # bleh.
270 echo "waiting for $acks dhcp acks then disabling pxe"
271 ack-wait $acks
272 type=
273 unset cmd
274 set-pxe
275
276 # previously tried waiting for one more ack then disabling faiserver,
277 # since it can contain sensitive info, so turn it off when not in use,
278 # but disabling that for now as it's inconvenient to clean this
279 # up and run it in the background etc.
280
281 # if [[ $type == fai ]]; then
282 # echo "waiting for 1 dhcp ack then disabling fai server"
283 # ack-wait 1
284 # faiserver-disable
285 # fi
286 fi