#!/bin/bash # This script configures your backend so that a diskless # frontend can boot from it. function backtitle() { # no arguments. BT="${0##*/}" KMV='/etc/LinHES-release' if [ -f "$KMV" ]; then BT="-= $(cat $KMV) $BT =-" fi } function must_be_root() { # no arguments. test $(id -u) == 0 && return MSG="\Z1ERROR\Zn: This script \Z4should\Zn be run by \Z1root\Zn." dialog --backtitle "$BT" --colors --msgbox "$MSG" 5 45 exit 4 } backtitle must_be_root TITLE="Diskless FE Configuration" DOPTS=(--backtitle "$BT" --title "$TITLE" --trim --aspect 30 ) DAEMON_LOG=/var/log/daemon.log BOOT_DEFAULT=/tftpboot/pxelinux.cfg/default unable_to_determine_mac_for_default_fe () { if dialog "${DOPTS[@]}" --yes-label "Boot FE and try again" \ --no-label "Delete $fe_nfsroot" \ --yesno "Unable to automatically determine the MAC address of the current default frontend by interrogating $DAEMON_LOG. This may be because you have not yet booted the diskless FE. You can either boot the diskless FE and try running $0 again, or delete $fe_nfsroot and re-create it later." 0 0 ; then exit else rm -Rf $fe_nfsroot $BOOT_DEFAULT fi exit } find_existing_diskless_fes() { unset frontend_names unset frontend_nfsroots unset frontend_ips unset frontend_macs for frontend_config in $(ls /tftpboot/pxelinux.cfg 2> /dev/null) ; do frontend_name=$(basename $( grep append /tftpboot/pxelinux.cfg/$frontend_config | sed -e 's/.*nfsroot=//' | awk -F, '{print $1}' | awk -F/ '{print $NF}')) frontend_nfsroot=$( grep append /tftpboot/pxelinux.cfg/$frontend_config | cut -d ':' -f 2 | cut -d ',' -f 1) frontend_ip=\ $((0x${frontend_config:0:2})).$((0x${frontend_config:2:2})).\ $((0x${frontend_config:4:2})).$((0x${frontend_config:6:2})) frontend_mac=$( cat /etc/dhcpd.conf | grep "host $frontend_name" | sed 's/hardware ethernet /#/g' | cut -d '#' -f 2 | cut -d ';' -f 1) if [ -n "$frontend_name" -a -n "$frontend_nfsroot" -a \ -n "$frontend_ip" -a -n "$frontend_mac" ] ; then frontend_names=( "${frontend_names[@]}" $frontend_name ) frontend_nfsroots=( "${frontend_nfsroots[@]}" $frontend_nfsroot ) frontend_ips=( "${frontend_ips[@]}" $frontend_ip ) frontend_macs=( "${frontend_macs[@]}" $frontend_mac ) fi done echo frontend_names="${frontend_names[@]}" echo frontend_nfsroots="${frontend_nfsroots[@]}" echo frontend_ips="${frontend_ips[@]}" echo frontend_macs="${frontend_macs[@]}" } check_for_default_fe () { # This function looks to see if there is a diskless FE that has only been # partially configured (i.e. it's MAC address has not been determined and # as a result it has not been allocated a fixed IP. if [ -e $BOOT_DEFAULT ] ; then # There is a $BOOT_DEFAULT config file that has not yet been set to a # fixed IP address. See if we can interrogate the log files to # determine what the MAC adderss of the FE is so that we can turn it # into a fixed ip node. This is the first step in allowing multiple # diskless frontends. # First determine from the default file, the nfsroot that it is for. fe_nfsroot=$(grep append $BOOT_DEFAULT | cut -d ':' -f 2 | cut -d ',' -f 1) if [ -z "$fe_nfsroot" ] ; then echo "Unable to determine the location of the nfsroot from" \ $BOOT_DEFAULT exit fi echo fe_nfsroot=$fe_nfsroot # Now determine the IP address by looking for an instance in the # daemon log file for which the specified nfsroot was mounted. fe_ipaddress=$( grep "authenticated mount request from " $DAEMON_LOG | grep $fe_nfsroot | tail -1 | sed 's/authenticated mount request from /#/g' | cut -d '#' -f 2 | cut -d ':' -f 1) echo fe_ipaddress=$fe_ipaddress unset fe_macaddress if [ ! -z "$fe_ipaddress" ] ; then fe_macaddress=$( grep "DHCPACK on $fe_ipaddress" $DAEMON_LOG | tail -1 | sed 's/DHCPACK on '$fe_ipaddress' to /#/g' | cut -d '#' -f 2 | cut -d ' ' -f 1) fi echo fe_macaddress=$fe_macaddress if [ -z "$fe_macaddress" ] ; then unable_to_determine_mac_for_default_fe else if ! dialog "${DOPTS[@]}" --yesno "By interrogating $DAEMON_LOG, it appears that the $(basename $fe_nfsroot) frontend was last used by a host with MAC Address of $fe_macaddress which was allocated IP address $fe_ipaddress. Do you wish to allocate the IP to that host (so that you can configure another diskless FE)?" 0 0 ; then dialog "${DOPTS[@]}" --msgbox "Boot the correct diskless FE and run $0 again." 0 0 exit fi # Setup dhcpd.conf so that the FE's MAC address always uses a fixed # ip address and the PXE configuration is fixed for that address. hex_ip_address=$(printf "%02X%02X%02X%02X" $(echo $fe_ipaddress | tr '.' ' ')) echo hex_ip_address=$hex_ip_address mv -fv $BOOT_DEFAULT $(dirname $BOOT_DEFAULT)/$hex_ip_address cp -f /etc/dhcpd.conf /etc/dhcpd.conf.bak cat /etc/dhcpd.conf.bak | grep -v "host $(basename $fe_nfsroot)" > \ /etc/dhcpd.conf echo "\ host $(basename $fe_nfsroot) \ {hardware ethernet $fe_macaddress; fixed-address $fe_ipaddress;}" \ >> /etc/dhcpd.conf /sbin/sv restart dhcpd dialog "${DOPTS[@]}" --msgbox "The frontend $(basename $fe_nfsroot) with a MAC address of $fe_macaddress and an nfsroot directory of $fe_nfsroot has been configured with a fixed IP address of $fe_ipaddress." 0 0 exit fi fi } get_network_interface() { NETDEVICES="$(cat /proc/net/dev | awk -F: '/ath.:|wlan.:|eth.:|tr.:/{print $1}')" echo NETDEVICES=$NETDEVICES NUM_DEVICES=$(echo $NETDEVICES | wc -w) echo NUM_DEVICES=$NUM_DEVICES if [ $NUM_DEVICES = 1 ] ; then INTERFACE=$NETDEVICES else DEVICELIST="" DEVNUM=0 for DEVICE in $NETDEVICES do DEVNUM=$(($DEVNUM + 1)) DEVICELIST="$DEVICELIST $DEVICE Interface${DEVNUM} " done echo DEVICELIST=$DEVICELIST TMP=/tmp/interface rm -Rf $TMP dialog "${DOPTS[@]}" --menu "Which network interface?" 18 70 12 \ $DEVICELIST 2>$TMP || exit INTERFACE=$(cat $TMP) fi echo INTERFACE=$INTERFACE } validip(){ echo "$1" | egrep -q -e '[0-9]+\.[0-9]+\.[0-9]+.[0-9]+' return $? } get_network_info() { BACKEND_IP=$(echo $(ifconfig $INTERFACE | grep "inet addr" | tr ':' ' ') | cut -d ' ' -f 3) echo BACKEND_IP=$BACKEND_IP NAMESERVERS="$(awk '/^nameserver/{printf "%s ",$2}' /etc/resolv.conf)" echo NAMESERVERS=$NAMESERVERS GATEWAY="$(LANG=C LC_ALL=C route -n | awk '/^0\.0\.0\.0/{print $2; exit}')" echo GATEWAY=$GATEWAY NETWORK="${BACKEND_IP%.*}" echo NETWORK=$NETWORK HOST="${BACKEND_IP##*.}" echo HOST=$HOST NETMASK="$(LANG=C LC_ALL=C ifconfig $INTERFACE | awk '/[Mm]ask/{FS="[: ]*";$0=$0; print $8; exit}')" echo NETMASK=$NETMASK # See if we can determine the range of IP addresses currently allowed in # dhcpd.conf IPRANGE=( $(grep ^[[:space:]]*range /etc/dhcpd.conf | sed 's/range//g' | tr -d ';') ) echo ${IPRANGE[@]} if [ $(echo ${IPRANGE[@]} | wc -w) -eq 2 ] ; then START=$(echo ${IPRANGE[0]} | tr '.' '\t' | cut -f 4) END=$(echo ${IPRANGE[1]} | tr '.' '\t' | cut -f 4) echo START=$START echo END=$END fi # Could not if [ -z "$START" -o -z "$END" ] ; then if [ "$HOST" -lt "20" ] ; then START=21 END=26 else START=11 END=16 fi fi IPRANGE_FROM="" IPRANGE_TO="" while [ -z "$IPRANGE_FROM" -o -z "$IPRANGE_TO" -o -z "$IPRANGE" ] do IPRANGE="$NETWORK.$START $NETWORK.$END" rm -f /tmp/iprange if ! dialog "${DOPTS[@]}" --clear --inputbox "Please enter the desired IP-Range of addresses that should be allocated by clients, separated by a single space." 10 75 "$IPRANGE" \ 2> /tmp/iprange ; then exit fi IPRANGE=$(cat /tmp/iprange) echo IPRANGE=$IPRANGE IPRANGE_FROM="${IPRANGE%% *}" IPRANGE_TO="${IPRANGE##* }" for i in "$IPRANGE_FROM" "$IPRANGE_TO" do validip "$i" || IPRANGE="" done done } setup_dhcpd() { # Generate dhcpd.conf from template if [ ! -f /etc/dhcpd.conf.orig ] ; then mv -f /etc/dhcpd.conf /etc/dhcpd.conf.orig fi ALLNAMESERVERS="" for i in $NAMESERVERS; do ALLNAMESERVERS="${ALLNAMESERVERS:+$ALLNAMESERVERS,} $i" done echo ALLNAMESERVERS=$ALLNAMESERVERS cat >/etc/dhcpd.conf <> \ /etc/dhcpd.conf done /sbin/add_service.sh dhcpd } setup_tftpd() { /sbin/add_service.sh tftpd } check_delete_of_existing_nfsroot() { if [ -d $NFSROOT ] ; then if dialog "${DOPTS[@]}" --yesno "\ There is already a directory $NFSROOT. Do you want to delete it and \ rebuild it from scratch?" 10 70 ; then echo Deleting $NFSROOT rm -Rf $NFSROOT else echo "OK then, bye." exit fi fi } enable_mysql_and_backend_networking() { # Enable mysql networking on the backend. /sbin/sv stop mythbackend if grep -q ^skip-networking /etc/my.cnf ; then echo "Commenting out skip-networking." cp /etc/my.cnf /etc/my.cnf~ cat /etc/my.cnf~ | sed 's/^skip-networking/#skip-networking/g' > \ /etc/my.cnf /sbin/sv restart mysql else echo "Already commented out skip-networking." fi # Make sure that the backend ip settings in the mythtv mysql database have # the actual IP address of the backend rather than the loopback address. # Otherwise the frontend will not be able to connect to the backend. echo "Setting backend IP in mythtv's mysql settings" echo " UPDATE settings SET data='$BACKEND_IP' WHERE value='BackendServerIP'; UPDATE settings SET data='$BACKEND_IP' WHERE value='MasterServerIP';" | mysql mythconverg /sbin/sv start mythbackend } export_mounts() { # Ensure that the /myth directory is exported. if ! grep -q ^/myth[[:space:]] /etc/exports ; then echo "Adding line for /myth in /etc/exports" echo "/myth *(rw,async,no_subtree_check)" >> /etc/exports else echo "Already added line for /myth in /etc/exports" fi if ! grep -q ^/data/var/cache/pacman[[:space:]] /etc/exports ; then echo "Adding line for /data/var/cache/pacman in /etc/exports" echo "/data/var/cache/pacman *(rw,async,no_subtree_check)" >> /etc/exports else echo "Already added line for /data/var/cache/pacman in /etc/exports" fi # Ensure that the montpoints that are used as storage groups are exported. for storage_mount in ${storage_mounts[@]} ; do if ! grep -q ^${storage_mount}[[:space:]] /etc/exports ; then echo "Adding line for ${storage_mount} in /etc/exports" echo "${storage_mount} *(rw,async,no_subtree_check)" >> /etc/exports else echo "Already added line for ${storage_mount} in /etc/exports" fi done } find_storage_mounts() { unset storage_mounts for group in $(mysql -sB mythconverg -e \ "select dirname from storagegroup where hostname = '$(hostname)'") ; do group_mount=$group while ! mountpoint -q $group_mount ; do group_mount=$(dirname $group_mount) done echo Storage group $group mountpoint is $group_mount storage_mounts=( "${storage_mounts[@]}" $group_mount ) done } enable_nfs() { /sbin/add_service.sh nfsd } restart_nfs (){ /usr/sbin/exportfs -arv } export_nfsroot() { # Ensure that the NFSROOT directory is appropriately exported. if ! grep -q ^$NFSROOT[[:space:]] /etc/exports ; then echo "Adding line for $NFSROOT in /etc/exports" echo "$NFSROOT *(rw,no_root_squash,async,no_subtree_check)" >> /etc/exports else echo "Already added line for $NFSROOT in /etc/exports" fi } create_tftpboot_directory() { # Create the directory with the tftp stuff. echo "Creating /tftpboot directories" mkdir -p /tftpboot/pxelinux.cfg if [ ! -e /tftpboot/pxelinux.0 ] ; then cp -fv /usr/lib/syslinux/pxelinux.0 /tftpboot/pxelinux.0 fi if [ ! -e /tftpboot/vmlinuz26 ] ; then cp -fv /boot/vmlinuz26 /tftpboot/vmlinuz26 fi if [ ! -e /tftpboot/kernel26.img ] ; then echo "Building kernel miniroot" # if [ -z "$(awk -F\" '$1 ~ /^MODULES=/ && $2 ~ /nfs/' /etc/mkinitcpio.conf)" ] ; then # cp /etc/mkinitcpio.conf /etc/mkinitcpio.conf~ # sed -e '/^MODULES=/s/\"$/ nfs\"/' < /etc/mkinitcpio.conf~ > /etc/mkinitcpio.conf # fi if [ -z "$(awk -F\" '$1 ~ /^HOOKS=/ && $2 ~ /net/' /etc/mkinitcpio.conf)" ] ; then cp /etc/mkinitcpio.conf /etc/mkinitcpio.conf~ sed -e '/^HOOKS=/s/\"$/ net\"/' < /etc/mkinitcpio.conf~ > /etc/mkinitcpio.conf fi /sbin/mkinitcpio -g /tftpboot/kernel26.img fi } create_default_pxelinux_entry() { echo "\ default linux label linux kernel vmlinuz26 append initrd=kernel26.img rootfstype=nfs root=/dev/nfs nfsroot=$BACKEND_IP:$NFSROOT,v3,rsize=16384,wsize=16384 init=/sbin/runit ip=dhcp" \ > /tftpboot/pxelinux.cfg/default } create_new_nfsroot() { # Prompt the user for the name of the NFSROOT. FRONTEND= NFSROOT= while [ -z "$FRONTEND" ] ; do if ! dialog "${DOPTS[@]}" --inputbox "Enter the hostname of the new diskless FE:" 0 0 2> /tmp/frontend_hostname ; then exit fi FRONTEND=$(cat /tmp/frontend_hostname) done # Prompt the user for the location of the NFSROOT. while [ -z "$NFSROOT" ] ; do NFSROOT=/nfsroot/$FRONTEND if ! dialog "${DOPTS[@]}" --clear --inputbox "Enter the location of the NFSROOT for the diskless FE:" \ 0 0 "$NFSROOT" 2> /tmp/nfsroot_location ; then exit fi NFSROOT=$(cat /tmp/nfsroot_location) done check_delete_of_existing_nfsroot # Create the nfsroot directory that the FE will use as its root filesystem. echo "Creating the $NFSROOT directory." mkdir -p $NFSROOT for DIR in /* ; do if [[ "$DIR" != /mnt && \ "$DIR" != /data && \ "$DIR" != /media && \ "$DIR" != /tmp && \ "$DIR" != /etc.old && \ "$DIR" != /storage && \ "$DIR" != /var && \ "$DIR" != /nfsroot && \ "$DIR" != /tftpboot && \ "$DIR" != /cdrom ]] then if mountpoint -q $DIR && [ "$DIR" != "/dev" ] ; then echo " Making mountpoint dir $DIR" cd $NFSROOT tar c $DIR --exclude=$DIR/* 2> /dev/null | tar x 2> /dev/null else echo " Copying $DIR to $NFSROOT" cp -ax $DIR $NFSROOT fi fi done cd $NFSROOT # Exclude specific bits of /var tar c /var \ --exclude=/var/lib/dhcpcd \ --exclude=/var/lib/locate \ --exclude=/var/lib/mlocate \ --exclude=/var/lib/named \ 2> /dev/null | tar x 2> /dev/null for DIR in /mnt /data /tmp /media /cdrom /var/lib/mlocate ; do echo " Creating $DIR" tar c $DIR --exclude=$DIR/* 2> /dev/null | tar x 2> /dev/null done mkdir -p $NFSROOT/data/var/cache/pacman chroot $NFSROOT /sbin/remove_service.sh dhcpd chroot $NFSROOT /sbin/remove_service.sh lighttpd chroot $NFSROOT /sbin/remove_service.sh mysql chroot $NFSROOT /sbin/remove_service.sh mythbackend chroot $NFSROOT /sbin/remove_service.sh nfsd chroot $NFSROOT /sbin/remove_service.sh nmbd chroot $NFSROOT /sbin/remove_service.sh smbd chroot $NFSROOT /sbin/remove_service.sh tftpd chroot $NFSROOT /sbin/remove_service.sh avahi # Update the fstab. cp $NSFROOT/etc/fstab $NFSROOT/etc/fstab~ cat $NFSROOT/etc/fstab~ | grep -v ext[34] | grep -v xfs | grep -v ^UUID= > $NFSROOT/etc/fstab echo "\ $BACKEND_IP:/nfsroot / nfs defaults,nolock,auto,noatime 0 2 $BACKEND_IP:/myth /myth nfs defaults,nolock,auto,noatime 0 0 $BACKEND_IP:/data/var/cache/pacman /data/var/cache/pacman nfs defaults,nolock,auto,noatime 0 0 " >> $NFSROOT/etc/fstab for storage_mount in ${storage_mounts[@]} ; do echo "\ $BACKEND_IP:${storage_mount} ${storage_mount} nfs auto,noatime,nolock,rsize=32768,wsize=32768 0 0" \ >> $NFSROOT/etc/fstab done cp $NFSROOT/etc/rc.sysinit $NFSROOT/etc/rc.sysinit~ sed -e '/^\/sbin\/minilogd/s/$/\n\n# Mount NFS early\n\/bin\/mount -a -t nfs/' \ < $NFSROOT/etc/rc.sysinit~ > $NFSROOT/etc/rc.sysinit # Update networking echo $FRONTEND > $NFSROOT/etc/hostname cp $NFSROOT/etc/hosts $NFSROOT/etc/hosts~ echo "127.0.0.1 $FRONTEND localhost" > $NFSROOT/etc/hosts cat $NFSROOT/etc/hosts~ | grep -v 127.0.0.1 >> $NFSROOT/etc/hosts cp $NFSROOT/etc/net/ifaces/eth0/options $NFSROOT/etc/net/ifaces/eth0/options~ sed -e 's/^BOOTPROTO=.*/BOOTPROTO=none/' < $NFSROOT/etc/net/ifaces/eth0/options~ \ > $NFSROOT/etc/net/ifaces/eth0/options # Update mysql settings cp /usr/share/mythtv/mysql.txt $NFSROOT/home/mythtv/.mythtv/mysql.txt if grep -q DBHostName=localhost $NFSROOT/home/mythtv/.mythtv/mysql.txt ; then echo "Setting database host in frontend's mysql.txt." cp $NFSROOT/home/mythtv/.mythtv/mysql.txt \ $NFSROOT/home/mythtv/.mythtv/mysql.txt.orig cat $NFSROOT/home/mythtv/.mythtv/mysql.txt.orig | sed 's/DBHostName=localhost/DBHostName='$BACKEND_IP'/g' > \ $NFSROOT/home/mythtv/.mythtv/mysql.txt else echo "Already set Database host in frontend's mysql.txt." fi chown mythtv:mythtv $NFSROOT/home/mythtv/.mythtv/mysql.txt # Check if the user has a diskless_tweak. file in root's # home directory. If they do, run it inside the chroot of the NFS root. if [[ -x ~/diskless_tweak.$FRONTEND ]] ; then echo Running tweak file ~/diskless_tweak.$FRONTEND chroot $NFSROOT bash -v ~/diskless_tweak.$FRONTEND fi create_default_pxelinux_entry export_nfsroot restart_nfs dialog "${DOPTS[@]}" --msgbox "Boot your diskless FE and then re-run this script ($0) so that the MAC address of the Diskless FE can be obtained." 0 0 } ########################################## ########################################## ## ## MAIN BODY OF SCRIPT ## ## ########################################## ########################################## check_for_default_fe find_existing_diskless_fes get_network_interface get_network_info setup_tftpd setup_dhcpd enable_mysql_and_backend_networking find_storage_mounts export_mounts create_tftpboot_directory enable_nfs # Restart nfs to ensure that the exported /myth directory can be mounted. This # is required to create a new frontend. restart_nfs if [ ${#frontend_names[@]} -eq 0 ] ; then create_new_nfsroot else for (( fe = 0 ; fe < ${#frontend_names[@]} ; fe++ )) ; do frontend_options=( "${frontend_options[@]}" \ "${frontend_names[$fe]} (MAC:${frontend_macs[$fe]} \ IP:${frontend_ips[$fe]})" "" ) done echo 0=${frontend_names[0]} echo 1=${frontend_names[1]} echo 2=${frontend_names[2]} echo 3=${frontend_names[3]} if ! dialog "${DOPTS[@]}" --menu "Select a frontend to delete or to create a new one." 0 0 0 "${frontend_options[@]}" \ "New Frontend" "" 2> /tmp/selected_fe; then exit fi selected_option=$(cat /tmp/selected_fe) if [ "$selected_option" = "New Frontend" ] ; then create_new_nfsroot exit fi echo $selected_option echo ${frontend_options[0]} echo ${frontend_options[2]} echo ${frontend_nfsroots[@]} for (( fe = 0 ; fe < ${#frontend_names[@]} ; fe++ )) ; do if [ "$selected_option" = "${frontend_options[$(($fe*2))]}" ] ; then if dialog "${DOPTS[@]}" --yesno "Do you wish to delete the frontend with nfsroot ${frontend_nfsroots[$fe]}?" 0 0 ; then # Delete the nfsroot directory. rm -Rfv ${frontend_nfsroots[$fe]} # Delete the tftpboot pxe boot config file. hex_ip_address=$(printf "%02X%02X%02X%02X" \ $(echo ${frontend_ips[$fe]} | tr '.' ' ')) rm /tftpboot/pxelinux.cfg/$hex_ip_address # Remove reference to the frontend from the dhcp.conf cp -fv /etc/dhcpd.conf /etc/dhcpd.conf.bak grep -v "host ${frontend_names[$fe]}" /etc/dhcpd.conf.bak > \ /etc/dhcpd.conf /sbin/sv restart dhcpd fi fi done fi