492 lines
17 KiB
Bash
Executable File
492 lines
17 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
if [[ $( whoami ) != "root" ]]; then
|
|
echo "This script is designed to run as root within the installer only!"
|
|
exit 1
|
|
fi
|
|
|
|
logfile="/tmp/pvc-install.log"
|
|
debrelease="buster"
|
|
debmirror="http://debian.mirror.rafal.ca/debian"
|
|
debpkglist="lvm2,parted,gdisk,grub-pc,grub-efi-amd64,linux-image-amd64,sudo,vim,gpg,gpg-agent,aptitude,openssh-server,vlan,ifenslave,python,python2,python3,ca-certificates,ntp"
|
|
suppkglist="firmware-linux,firmware-linux-nonfree,firmware-bnx2,firmware-bnx2x"
|
|
|
|
# DANGER - THIS PASSWORD IS PUBLIC
|
|
# It should be used ONLY immediately after booting the PVC node in a SECURE environment
|
|
# to facilitate troubleshooting of a failed boot. It should NOT be exposed to the Internet,
|
|
# and it should NOT be left in place after system configuration. The PVC Ansible deployment
|
|
# roles will overwrite it by default during configuration.
|
|
root_password="hCb1y2PF"
|
|
|
|
clear
|
|
|
|
echo "-----------------------------------------------------"
|
|
echo "| PVC Node installer (XXISOXX) |"
|
|
echo "-----------------------------------------------------"
|
|
echo
|
|
echo "This LiveCD will install a PVC node base system ready for bootstrapping with 'pvc-ansible'."
|
|
echo
|
|
echo "NOTE: If you make a mistake and need to restart the installer while answering"
|
|
echo " the questions below, you may do so by typing ^C to cancel the script,"
|
|
echo " then re-running it by calling /install.sh in the resulting shell."
|
|
echo
|
|
|
|
echo "1) Please enter a fully-qualified hostname for the system. This should match the hostname"
|
|
echo "in the 'pvc-ansible' inventory."
|
|
while [[ -z ${target_hostname} ]]; do
|
|
echo
|
|
echo -n "> "
|
|
read target_hostname
|
|
if [[ -z ${target_hostname} ]]; then
|
|
echo
|
|
echo "Please enter a hostname."
|
|
continue
|
|
fi
|
|
echo
|
|
done
|
|
|
|
disks="$(
|
|
for disk in /dev/sd?; do
|
|
disk_data="$( fdisk -l ${disk} 2>/dev/null )"
|
|
echo -n "${disk}"
|
|
echo -en "\t$( grep "^Disk model:" <<<"${disk_data}" | awk '{ $1=""; print $0 }' )"
|
|
echo -en " $( grep "^Disk ${disk}:" <<<"${disk_data}" | awk '{ $1=""; $2="size:"; print $0 }' )"
|
|
echo
|
|
done
|
|
)"
|
|
|
|
echo "2) Please enter the disk to install the PVC base system to. This disk will be"
|
|
echo "wiped, an LVM PV created on it, and the system installed to this LVM."
|
|
echo "NOTE: PVC requires a disk of >16GB to be installed to. 32GB is the recommended"
|
|
echo "minimum size, and disks larger than 64GB are not particularly useful."
|
|
echo "NOTE: This disk should generally be a RAID-1 volume configured in hardware for"
|
|
echo "maximum redundancy and resiliency."
|
|
echo
|
|
echo "Available disks:"
|
|
echo
|
|
echo -e "$( sed 's/\(.*\)/ \1/' <<<"${disks[@]}" )"
|
|
while [[ ! -b ${target_disk} ]]; do
|
|
echo
|
|
echo -n "> "
|
|
read target_disk
|
|
if [[ ! -b ${target_disk} ]]; then
|
|
echo
|
|
echo "Please enter a valid target disk."
|
|
continue
|
|
fi
|
|
echo
|
|
done
|
|
|
|
for interface in $( ip address | grep '^[0-9]' | grep 'eno\|enp\|ens\|wlp' | awk '{ print $2 }' | tr -d ':' ); do
|
|
ip link set ${interface} up
|
|
done
|
|
sleep 2
|
|
interfaces="$(
|
|
ip address | grep '^[0-9]' | grep 'eno\|enp\|ens\|wlp' | awk '{ print $2"\t"$3 }' | tr -d ':'
|
|
)"
|
|
echo "3a) Please enter the primary network interface for external connectivity. If"
|
|
echo "no entries are shown here, ensure a cable is connected, then restart the"
|
|
echo "installer."
|
|
echo
|
|
echo "Available interfaces:"
|
|
echo
|
|
echo -e "$( sed 's/\(.*\)/ \1/' <<<"${interfaces[@]}" )"
|
|
while [[ -z ${target_interface} ]]; do
|
|
echo
|
|
echo -n "> "
|
|
read target_interface
|
|
if ! grep -qw "${target_interface}" <<<"${interfaces[@]}"; then
|
|
echo
|
|
echo "Please enter a valid interface."
|
|
target_interface=""
|
|
continue
|
|
fi
|
|
echo
|
|
done
|
|
|
|
echo -n "3b) Is a tagged vLAN required for the primary network interface? [y/N] "
|
|
read vlans_req
|
|
if [[ ${vlans_req} == 'y' || ${vlans_req} == 'Y' ]]; then
|
|
echo
|
|
echo "Please enter the vLAN ID for the interface."
|
|
while [[ -z ${vlan_id} ]]; do
|
|
echo
|
|
echo -n "> "
|
|
read vlan_id
|
|
if [[ -z ${vlan_id} ]]; then
|
|
echo
|
|
echo "Please enter a numeric vLAN ID."
|
|
continue
|
|
fi
|
|
done
|
|
echo
|
|
else
|
|
vlan_id=""
|
|
echo
|
|
fi
|
|
|
|
echo "3c) Please enter the IP address, in CIDR format [X.X.X.X/YY], of the primary"
|
|
echo "network interface. Leave blank for DHCP configuration of the interface on boot."
|
|
echo
|
|
echo -n "> "
|
|
read target_ipaddr
|
|
if [[ -n ${target_ipaddr} ]]; then
|
|
target_netformat="static"
|
|
echo
|
|
echo "3d) Please enter the default gateway IP address of the primary"
|
|
echo "network interface."
|
|
while [[ -z ${target_defgw} ]]; do
|
|
echo
|
|
echo -n "> "
|
|
read target_defgw
|
|
if [[ -z ${target_defgw} ]]; then
|
|
echo
|
|
echo "Please enter a default gateway; the installer requires Internet access."
|
|
continue
|
|
fi
|
|
echo
|
|
done
|
|
else
|
|
target_netformat="dhcp"
|
|
echo
|
|
fi
|
|
|
|
echo -n "Bringing up primary network interface in ${target_netformat} mode... "
|
|
case ${target_netformat} in
|
|
'static')
|
|
if [[ -n ${vlan_id} ]]; then
|
|
modprobe 8021q >&2
|
|
vconfig add ${target_interface} ${vlan_id} >&2
|
|
vlan_interface=${target_interface}.${vlan_id}
|
|
ip link set ${target_interface} up >&2 || true
|
|
ip link set ${vlan_interface} up >&2 || true
|
|
ip address add ${target_ipaddr} dev ${vlan_interface} >&2 || true
|
|
ip route add default via ${target_defgw} >&2 || true
|
|
formatted_ipaddr="$( sipcalc ${target_ipaddr} | grep -v '(' | awk '/Host address/{ print $NF }' )"
|
|
formatted_netmask="$( sipcalc ${target_ipaddr} | grep -v '(' | awk '/Network mask/{ print $NF }' )"
|
|
target_interfaces_block="auto ${vlan_interface}\niface ${vlan_interface} inet ${target_netformat}\n\tvlan_raw_device ${target_interface}\n\taddress ${formatted_ipaddr}\n\tnetmask ${formatted_netmask}\n\tgateway ${target_defgw}"
|
|
real_interface="${vlan_interface}"
|
|
else
|
|
ip link set ${target_interface} up >&2 || true
|
|
ip address add ${target_ipaddr} dev ${target_interface} >&2 || true
|
|
ip route add default via ${target_defgw} >&2 || true
|
|
formatted_ipaddr="$( sipcalc ${target_ipaddr} | grep -v '(' | awk '/Host address/{ print $NF }' )"
|
|
formatted_netmask="$( sipcalc ${target_ipaddr} | grep -v '(' | awk '/Network mask/{ print $NF }' )"
|
|
target_interfaces_block="auto ${target_interface}\niface ${target_interface} inet ${target_netformat}\n\taddress ${formatted_ipaddr}\n\tnetmask ${formatted_netmask}\n\tgateway ${target_defgw}"
|
|
real_interface="${target_interface}"
|
|
fi
|
|
cat <<EOF >/etc/resolv.conf
|
|
nameserver 8.8.8.8
|
|
EOF
|
|
;;
|
|
'dhcp')
|
|
if [[ -n ${vlan_id} ]]; then
|
|
modprobe 8021q >&2
|
|
vconfig add ${target_interface} ${vlan_id} >&2
|
|
vlan_interface=${target_interface}.${vlan_id}
|
|
target_interfaces_block="auto ${vlan_interface}\niface ${vlan_interface} inet ${target_netformat}\n\tvlan_raw_device${target_interface}"
|
|
dhclient ${vlan_interface} >&2
|
|
real_interface="${vlan_interface}"
|
|
else
|
|
target_interfaces_block="auto ${target_interface}\niface ${target_interface} inet ${target_netformat}"
|
|
dhclient ${target_interface} >&2
|
|
real_interface="${target_interface}"
|
|
fi
|
|
;;
|
|
esac
|
|
echo "done."
|
|
echo
|
|
|
|
echo "4) Please enter an HTTP URL containing a text list of SSH authorized keys to"
|
|
echo "fetch. These keys will be allowed access to the 'deploy' user via SSH."
|
|
echo "Leave blank to bypass this and use a password instead."
|
|
echo
|
|
echo -n "> "
|
|
read target_keys_url
|
|
if [[ -z ${target_keys_url} ]]; then
|
|
echo
|
|
echo "No SSH keys URL specified. Falling back to password configuration."
|
|
echo
|
|
echo "5) Please enter a password (hidden), twice, for the 'deploy' user."
|
|
while [[ -z "${target_password}" ]]; do
|
|
echo
|
|
echo -n "> "
|
|
read -s target_password_1
|
|
echo
|
|
echo -n "> "
|
|
read -s target_password_2
|
|
echo
|
|
if [[ -n "${target_password_1}" && "${target_password_1}" -eq "${target_password_2}" ]]; then
|
|
target_password="${target_password_1}"
|
|
else
|
|
echo
|
|
echo "The specified passwords do not match or are empty."
|
|
fi
|
|
done
|
|
else
|
|
while ! wget -O /dev/null ${target_keys_url} &>/dev/null; do
|
|
echo
|
|
echo "Please enter a valid SSH keys URL."
|
|
echo
|
|
echo -n "> "
|
|
read target_keys_url
|
|
done
|
|
fi
|
|
echo
|
|
|
|
titlestring_text="| Proceeding with installation of host '${target_hostname}'. |"
|
|
titlestring_len="$(( $( wc -c <<<"${titlestring_text}" ) - 2 ))"
|
|
for i in $( seq 0 ${titlestring_len} ); do echo -n "-"; done; echo
|
|
echo "${titlestring_text}"
|
|
for i in $( seq 0 ${titlestring_len} ); do echo -n "-"; done; echo
|
|
echo
|
|
|
|
### Script begins ###
|
|
echo "LOGFILE: ${logfile}"
|
|
echo
|
|
|
|
set -o errexit
|
|
exec 1> >( tee -ia ${logfile} )
|
|
exec 2> >( tee -ia ${logfile} >/dev/null )
|
|
|
|
cleanup() {
|
|
set +o errexit
|
|
echo -n "Cleaning up... "
|
|
umount ${target}/run >&2
|
|
umount ${target}/sys >&2
|
|
umount ${target}/proc >&2
|
|
umount ${target}/dev/pts >&2
|
|
umount ${target}/dev >&2
|
|
umount ${target}/var/lib/ceph >&2
|
|
umount ${target}/boot/efi >&2
|
|
umount ${target}/boot >&2
|
|
umount ${target} >&2
|
|
vgchange -an >&2
|
|
rmdir ${target} >&2
|
|
echo "done."
|
|
echo
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
echo -n "Determining block device and partition sizing... "
|
|
blockdev_size="$(( $( blockdev --getsize64 ${target_disk} ) / 1024 / 1024 / 1024 - 1))"
|
|
if [[ ${blockdev_size} -ge 58 ]]; then
|
|
# Sufficiently large system disk (>=64GB), use large partitions
|
|
size_root_lv="32"
|
|
size_ceph_lv="4"
|
|
size_swap_lv="16"
|
|
echo "found large disk, using partition sizes 32/4/16."
|
|
elif [[ ${blockdev_size} -ge 28 ]]; then
|
|
# Relatively large disk (>=32GB), use small partitions
|
|
size_root_lv="16"
|
|
size_ceph_lv="2"
|
|
size_swap_lv="8"
|
|
echo "found medium disk, using partition sizes 16/2/8."
|
|
elif [[ ${blockdev_size} -ge 13 ]]; then
|
|
# Small disk (>=16GB), use very small partitions
|
|
size_root_lv="8"
|
|
size_ceph_lv="2"
|
|
size_swap_lv="2"
|
|
echo "found small disk, using partition sizes 8/2/2."
|
|
else
|
|
# Extremely small disk, (<16GB) - bail out, this is too small
|
|
echo
|
|
echo "FAILURE - The specified disk is too small (<16GB). PVC must be installed on a disk of >16GB."
|
|
read
|
|
exit 1
|
|
fi
|
|
|
|
echo -n "Disabing existing volume groups... "
|
|
vgchange -an >&2 || true
|
|
echo "done."
|
|
|
|
echo -n "Zeroing block device '${target_disk}'... "
|
|
dd if=/dev/zero of=${target_disk} bs=4M >&2 || true
|
|
echo "done."
|
|
|
|
echo -n "Preparing block device '${target_disk}'... "
|
|
# New GPT, part 1 64MB ESP, part 2 960MB BOOT, part 3 inf LVM PV
|
|
echo -e "o\ny\nn\n1\n\n64M\nEF00\nn\n2\n\n960M\n8300\nn\n3\n\n\n8E00\nw\ny\n" | gdisk ${target_disk} >&2
|
|
echo "done."
|
|
|
|
echo -n "Rescanning disks... "
|
|
partprobe >&2 || true
|
|
echo "done."
|
|
|
|
echo -n "Creating LVM PV... "
|
|
yes | pvcreate -ffy ${target_disk}3 >&2
|
|
echo "done."
|
|
|
|
echo -n "Creating LVM VG named 'vgx'... "
|
|
yes | vgcreate vgx ${target_disk}3 >&2
|
|
echo "done."
|
|
|
|
echo -n "Creating root logical volume (${size_root_lv}GB)... "
|
|
lvcreate -L ${size_root_lv}G -n root vgx >&2
|
|
echo "done."
|
|
echo -n "Creating filesystem on root logical volume (ext4)... "
|
|
yes | mkfs.ext4 /dev/vgx/root >&2
|
|
echo "done."
|
|
|
|
echo -n "Creating ceph logical volume (${size_ceph_lv}GB)... "
|
|
yes | lvcreate -L ${size_ceph_lv}G -n ceph vgx >&2
|
|
echo "done."
|
|
echo -n "Creating filesystem on ceph logical volume (ext4)... "
|
|
mkfs.ext4 /dev/vgx/ceph >&2
|
|
echo "done."
|
|
|
|
echo -n "Creating swap logical volume (${size_swap_lv}GB)... "
|
|
lvcreate -L ${size_swap_lv}G -n swap vgx >&2
|
|
echo "done."
|
|
echo -n "Creating swap space on swap logical volume... "
|
|
yes | mkswap -f /dev/vgx/swap >&2
|
|
echo "done."
|
|
|
|
echo -n "Creating filesystem on boot partition (ext2)... "
|
|
yes | mkfs.ext2 ${target_disk}2 >&2
|
|
echo "done."
|
|
|
|
echo -n "Creating filesystem on ESP partition (vfat)... "
|
|
yes | mkdosfs -F32 ${target_disk}1 >&2
|
|
echo "done."
|
|
|
|
echo -n "Mounting disks on temporary target... "
|
|
target=$( mktemp -d )
|
|
mount /dev/vgx/root ${target} >&2
|
|
mkdir -p ${target}/boot >&2
|
|
mount ${target_disk}2 ${target}/boot >&2
|
|
mkdir -p ${target}/boot/efi >&2
|
|
mount ${target_disk}1 ${target}/boot/efi >&2
|
|
mkdir -p ${target}/var/lib/ceph >&2
|
|
mount /dev/vgx/ceph ${target}/var/lib/ceph >&2
|
|
echo "done."
|
|
|
|
echo -n "Running debootstrap install... "
|
|
debootstrap --include=${debpkglist} ${debrelease} ${target}/ ${debmirror} >&2
|
|
echo "done."
|
|
|
|
echo -n "Adding non-free repository (firmware, etc.)... "
|
|
mkdir -p ${target}/etc/apt/sources.list.d/ >&2
|
|
echo "deb ${debmirror} ${debrelease} contrib non-free" | tee -a ${target}/etc/apt/sources.list >&2
|
|
chroot ${target} apt update >&2
|
|
echo "done."
|
|
|
|
echo -n "Installing supplemental packages... "
|
|
chroot ${target} apt install -y --no-install-recommends $( sed 's/,/ /g' <<<"${suppkglist}" ) >&2
|
|
echo "done."
|
|
|
|
# Determine the bypath name of the specified system disk
|
|
for disk in /dev/disk/by-path/*; do
|
|
bypathlink="$( readlink ${disk} | awk -F'/' '{ print $NF }' )"
|
|
enteredname="$( awk -F'/' '{ print $NF }' <<<"${target_disk}" )"
|
|
if [[ ${bypathlink} == ${enteredname} ]]; then
|
|
bypath_disk="${disk}"
|
|
fi
|
|
done
|
|
|
|
echo -n "Adding fstab entries... "
|
|
echo "/dev/mapper/vgx-root / ext4 errors=remount-ro 0 1" | tee -a ${target}/etc/fstab >&2
|
|
echo "/dev/mapper/vgx-ceph /var/lib/ceph ext4 errors=remount-ro 0 2" | tee -a ${target}/etc/fstab >&2
|
|
echo "/dev/mapper/vgx-swap nonde swap sw 0 0" | tee -a ${target}/etc/fstab >&2
|
|
echo "${bypath_disk}-part2 /boot ext2 defaults 0 2" | tee -a ${target}/etc/fstab >&2
|
|
echo "${bypath_disk}-part1 /boot/efi vfat umask=0077 0 2" | tee -a ${target}/etc/fstab >&2
|
|
echo "done."
|
|
|
|
echo -n "Adding interface segment... "
|
|
echo -e "${target_interfaces_block}" | tee -a ${target}/etc/network/interfaces >&2
|
|
echo "done."
|
|
|
|
echo -n "Setting temporary 'root' password... "
|
|
echo "root:${root_password}" | chroot ${target} chpasswd >&2
|
|
echo "done."
|
|
|
|
echo -n "Adding 'deploy' user... "
|
|
mv ${target}/home ${target}/var/home >&2
|
|
chroot ${target} useradd -u 200 -d /var/home/deploy -m -s /bin/bash -g operator -G sudo deploy >&2
|
|
chroot ${target} mkdir -p /var/home/deploy/.ssh
|
|
if [[ -n ${target_keys_url} ]]; then
|
|
wget -O ${target}/var/home/deploy/.ssh/authorized_keys ${target_keys_url}
|
|
chroot ${target} chmod 0600 /var/home/deploy/.ssh/authorized_keys
|
|
chroot ${target} chown -R deploy:operator /var/home/deploy
|
|
else
|
|
echo "deploy:${target_password}" | chroot ${target} chpasswd >&2
|
|
fi
|
|
echo "done."
|
|
|
|
echo -n "Setting NOPASSWD for sudo group... "
|
|
sed -i 's/^%sudo\tALL=(ALL:ALL) ALL/%sudo\tALL=(ALL:ALL) NOPASSWD: ALL/' ${target}/etc/sudoers
|
|
echo "done."
|
|
|
|
echo -n "Setting /etc/issue generator... "
|
|
mkdir -p ${target}/etc/network/if-up.d >&2
|
|
echo -e "#!/bin/sh
|
|
IP=\"\$( ip -4 addr show dev ${real_interface} | grep inet | awk '{ print \$2 }' | head -1 )\"
|
|
cat <<EOF >/etc/issue
|
|
Debian GNU/Linux 10 \\\\n \\\\l
|
|
|
|
Primary interface IP address: \$IP
|
|
|
|
EOF" | tee ${target}/etc/network/if-up.d/issue-gen >&2
|
|
chmod +x ${target}/etc/network/if-up.d/issue-gen 1>&2
|
|
echo "done."
|
|
|
|
echo -n "Generating host rsa and ed25519 keys... "
|
|
rm ${target}/etc/ssh/ssh_host_*_key* >&2
|
|
chroot ${target} ssh-keygen -t rsa -N "" -f /etc/ssh/ssh_host_rsa_key >&2
|
|
chroot ${target} ssh-keygen -t ed25519 -N "" -f /etc/ssh/ssh_host_ed25519_key >&2
|
|
echo "done."
|
|
|
|
echo -n "Setting hostname... "
|
|
echo "${target_hostname}" | tee ${target}/etc/hostname >&2
|
|
echo "done."
|
|
|
|
echo -n "Setting resolv.conf... "
|
|
echo "nameserver 8.8.8.8" | tee ${target}/etc/resolv.conf >&2
|
|
echo "done."
|
|
|
|
echo -n "Installing GRUB bootloader... "
|
|
mount --bind /dev ${target}/dev >&2
|
|
mount --bind /dev/pts ${target}/dev/pts >&2
|
|
mount --bind /proc ${target}/proc >&2
|
|
mount --bind /sys ${target}/sys >&2
|
|
mount --bind /run ${target}/run >&2
|
|
if [[ -d /sys/firmware/efi ]]; then
|
|
bios_target="x86_64-efi"
|
|
else
|
|
bios_target="i386-pc"
|
|
fi
|
|
chroot ${target} grub-install --force --target=${bios_target} ${target_disk} >&2
|
|
chroot ${target} grub-mkconfig -o /boot/grub/grub.cfg >&2
|
|
echo "done."
|
|
|
|
set +o errexit
|
|
echo
|
|
echo -n "Edit the /etc/network/interfaces file in the target before completing setup? [y/N] "
|
|
read edit_ifaces
|
|
if [[ ${edit_ifaces} == 'y' || ${edit_ifaces} == 'Y' ]]; then
|
|
vim ${target}/etc/network/interfaces
|
|
fi
|
|
echo
|
|
|
|
echo -n "Launch a chroot shell in the target environment? [y/N] "
|
|
read launch_chroot
|
|
if [[ ${launch_chroot} == 'y' || ${edit_ifaces} == 'Y' ]]; then
|
|
echo "Type 'exit' or Ctrl+D to exit chroot."
|
|
chroot ${target} /bin/bash
|
|
fi
|
|
|
|
cleanup
|
|
|
|
echo "-------------------------------------------------------------------------------------"
|
|
echo "| PVC node installation finished. Next steps: |"
|
|
echo "| 1. Press <enter> to reboot the system. |"
|
|
echo "| 2. Boot the PVC base hypervisor and verify SSH access (IP shown on login screen). |"
|
|
echo "| 3. Proceed with system deployment via PVC Ansible. |"
|
|
echo "| |"
|
|
echo "| The INSECURE temporary root password if the system will not boot is: ${root_password} |"
|
|
echo "-------------------------------------------------------------------------------------"
|
|
echo
|
|
read
|
|
|
|
reboot
|