From c8f1d853a007424eb32878603aad1e880770ce7c Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Fri, 1 Sep 2023 15:41:57 -0400 Subject: [PATCH] Move to template format instead of cat-EOF Simplifies the scripts and adds the ability to easily and cleanly change the contents of these files without dealing with escape characters, etc. --- buildiso.sh | 85 +- buildpxe.sh | 23 +- install.sh | 864 +----------------- pxelinux.0 | 0 templates/2000-remove-root-pw.sh | 3 + templates/9990-initramfs-tools.sh | 67 ++ templates/boot.pxe | 24 + templates/firmware.list.chroot | 1 + templates/getty-override.conf | 3 + grub.cfg => templates/grub.cfg | 0 templates/hostname | 1 + templates/install.sh | 863 +++++++++++++++++ templates/installer.list.chroot | 1 + isolinux.cfg => templates/isolinux.cfg | 0 templates/logind.conf | 2 + .../pxe-install-seed.example | 0 templates/resolv.conf | 1 + templates/root.bashrc | 1 + templates/serial-getty-override.conf | 3 + splash.png => templates/splash.png | Bin splash.xcf => templates/splash.xcf | Bin theme.txt => templates/theme.txt | 0 22 files changed, 1018 insertions(+), 924 deletions(-) mode change 100755 => 120000 install.sh delete mode 100644 pxelinux.0 create mode 100755 templates/2000-remove-root-pw.sh create mode 100755 templates/9990-initramfs-tools.sh create mode 100644 templates/boot.pxe create mode 100644 templates/firmware.list.chroot create mode 100644 templates/getty-override.conf rename grub.cfg => templates/grub.cfg (100%) create mode 100644 templates/hostname create mode 100755 templates/install.sh create mode 100644 templates/installer.list.chroot rename isolinux.cfg => templates/isolinux.cfg (100%) create mode 100644 templates/logind.conf rename install.seed.example => templates/pxe-install-seed.example (100%) create mode 100644 templates/resolv.conf create mode 100644 templates/root.bashrc create mode 100644 templates/serial-getty-override.conf rename splash.png => templates/splash.png (100%) rename splash.xcf => templates/splash.xcf (100%) rename theme.txt => templates/theme.txt (100%) diff --git a/buildiso.sh b/buildiso.sh index 3a07b19..8952300 100755 --- a/buildiso.sh +++ b/buildiso.sh @@ -63,85 +63,86 @@ while [ $# -gt 0 ]; do esac done -PACKAGE_LIST_MAIN="live-tools live-boot live-boot-initramfs-tools linux-image-amd64 mdadm lvm2 parted gdisk dosfstools debootstrap grub-pc-bin grub-efi-amd64 sipcalc vim ca-certificates vlan tftp-hpa curl ipmitool" -PACKAGE_LIST_NONFREE="firmware-bnx2 firmware-bnx2x" - mkdir -p artifacts/lb pushd artifacts/lb &>/dev/null echo "Pre-cleaning live-build environment..." sudo lb clean +echo echo "Initializing config..." # Initialize the live-build config -lb config --distribution buster --architectures amd64 --archive-areas "main contrib non-free" --apt-recommends false +lb config --distribution buster --architectures amd64 --archive-areas "main contrib non-free" --apt-recommends false || fail "Failed to initialize live-build config" +echo -# Configure the "standard" live task (no GUI) -echo "live-task-standard" > config/package-lists/desktop.list.chroot - -# Add additional live packages -echo ${PACKAGE_LIST_MAIN} > config/package-lists/installer.list.chroot -echo ${PACKAGE_LIST_NONFREE} > config/package-lists/nonfree.list.chroot +# Configure the package lists +echo -n "Copying package lists... " +cp ../../templates/installer.list.chroot config/package-lists/installer.list.chroot || fail "Failed to copy critical template file" +cp ../../templates/firmware.list.chroot config/package-lists/firmware.list.chroot || fail "Failed to copy critical template file" +echo "done." # Add root password hook -mkdir -p config/includes.chroot/lib/live/config/ -cat < config/includes.chroot/lib/live/config/2000-remove-root-pw -#!/bin/sh -echo "I: remove root password" -passwd --delete root -EOF -chmod +x config/includes.chroot/lib/live/config/2000-remove-root-pw +echo -n "Copying live-boot templates... " +mkdir -p config/includes.chroot/lib/live/boot/ +cp ../../templates/2000-remove-root-pw.sh config/includes.chroot/lib/live/boot/2000-remove-root-pw.sh || fail "Failed to copy critical template file" +chmod +x config/includes.chroot/lib/live/boot/2000-remove-root-pw.sh || fail "Failed to copy critical template file" +cp ../../templates/9990-initramfs-tools.sh config/includes.chroot/lib/live/boot/9990-initramfs-tools.sh || fail "Failed to copy critical template file" +chmod +x config/includes.chroot/lib/live/boot/9990-initramfs-tools.sh || fail "Failed to copy critical template file" +echo "done." # Set root bashrc +echo -n "Copying root bashrc template... " mkdir -p config/includes.chroot/root -echo "/install.sh" > config/includes.chroot/root/.bashrc +cp ../../templates/root.bashrc config/includes.chroot/root/.bashrc || fail "Failed to copy critical template file" +echo "done." # Set hostname and resolv.conf +echo -n "Copying networking templates... " mkdir -p config/includes.chroot/etc -echo "pvc-live-installer" > config/includes.chroot/etc/hostname -echo "nameserver 8.8.8.8" > config/includes.chroot/etc/resolv.conf +cp ../../templates/hostname config/includes.chroot/etc/hostname || fail "Failed to copy critical template file" +cp ../../templates/resolv.conf config/includes.chroot/etc/resolv.conf || fail "Failed to copy critical template file" +echo "done." -# Set single vty +# Set single vty and autologin +echo -n "Copying getty templates... " mkdir -p config/includes.chroot/etc/systemd/ -cat < config/includes.chroot/etc/systemd/logind.conf -[Login] -NAutoVTs=2 -EOF - +cp ../../templates/logind.conf config/includes.chroot/etc/systemd/logind.conf || fail "Failed to copy critical template file" mkdir -p config/includes.chroot/etc/systemd/system/getty@.service.d -cat < config/includes.chroot/etc/systemd/system/getty@.service.d/override.conf -[Service] -ExecStart= -ExecStart=-/sbin/agetty -o '-p -- \\\u' --autologin root --noclear %I \$TERM -EOF - +cp ../../templates/getty-override.conf config/includes.chroot/etc/systemd/system/getty@.service.d/override.conf || fail "Failed to copy critical template file" mkdir -p config/includes.chroot/etc/systemd/system/serial-getty@.service.d -cat < config/includes.chroot/etc/systemd/system/serial-getty@.service.d/override.conf -[Service] -ExecStart= -ExecStart=-/sbin/agetty -o '-p -- \\\u' --autologin root --noclear --keep-baud 115200,38400,9600 %I \$TERM -EOF +cp ../../templates/serial-getty-override.conf config/includes.chroot/etc/systemd/system/serial-getty@.service.d/override.conf || fail "Failed to copy critical template file" +echo "done." # Install GRUB config, theme, and splash +echo -n "Copying GRUB templates... " mkdir -p config/includes.chroot/boot/grub -cp ../../grub.cfg config/includes.chroot/boot/grub/grub.cfg -cp ../../theme.txt config/includes.chroot/boot/grub/theme.txt -cp ../../splash.png config/includes.chroot/splash.png +cp ../../templates/grub.cfg config/includes.chroot/boot/grub/grub.cfg || fail "Failed to copy critical template file" +cp ../../templates/theme.txt config/includes.chroot/boot/grub/theme.txt || fail "Failed to copy critical template file" +cp ../../templates/splash.png config/includes.chroot/splash.png || fail "Failed to copy critical template file" +echo "done." # Install install.sh script -cp ../../install.sh config/includes.chroot/install.sh +echo -n "Copying PVC node installer script template... " +cp ../../templates/install.sh config/includes.chroot/install.sh || fail "Failed to copy critical template file" chmod +x config/includes.chroot/install.sh +echo "done." # Customize install.sh script +echo -n "Customizing PVC node installer script... " sed -i "s/XXDATEXX/$(date)/g" config/includes.chroot/install.sh sed -i "s/XXDEPLOYUSERXX/${deployusername}/g" config/includes.chroot/install.sh +echo "done." +echo # Build the live image echo "Building live image..." -sudo lb build +sudo lb build || fail "Failed to build live image" +echo # Move the ISO image out +echo -n "Copying generated ISO to repository root... " cp live-image-amd64.hybrid.iso ../../${isofilename} +echo "done." # Clean up the artifacts if [[ -z ${preserve_artifacts} ]]; then diff --git a/buildpxe.sh b/buildpxe.sh index 8ef353b..04c0328 100755 --- a/buildpxe.sh +++ b/buildpxe.sh @@ -108,25 +108,6 @@ build_pxe() { rmdir ${tmpdir} echo "done." - echo -n "Creating base iPXE configuration... " - cat < ${outputdir}/boot.pxe -#!ipxe - -# Set global variables -set root-url tftp://\${next-server} - -set menu-default pvc-installer -set submenu-default pvc-installer - -:pvc-installer -kernel \${root-url}/vmlinuz -initrd \${root-url}/initrd.img -imgargs vmlinuz console=tty0 vga=normal nomodeset boot=live components ethdevice-timeout=600 timezone=America/Toronto fetch=\${root-url}/filesystem.squashfs username=root pvcinstall.preseed=on pvcinstall.seed_host=\${next-server} pvcinstall.seed_file=/host/mac-\${mac:hexraw}.preseed - -boot -EOF - echo "done." - echo -n "Downloading iPXE binary undionly.kpxe (chainloads arbitrary PXE clients)... " pushd ${outputdir} &>/dev/null wget -O undionly.kpxe https://boot.ipxe.org/undionly.kpxe &>/dev/null || fail "failed to download undionly.kpxe." @@ -139,6 +120,10 @@ EOF popd &>/dev/null echo "done." + echo -n "Copying base iPXE configuration... " + cp templates/boot.pxe ${outputdir}/boot.pxe + echo "done." + sudo chown -R $(whoami) ${outputdir} sudo chmod -R u+w ${outputdir} diff --git a/install.sh b/install.sh deleted file mode 100755 index 1a220a5..0000000 --- a/install.sh +++ /dev/null @@ -1,863 +0,0 @@ -#!/usr/bin/env bash - -logfile="/tmp/pvc-install.log" -lockfile="/run/pvc-install.lock" - -if [[ $( whoami ) != "root" ]]; then - echo "STOP! This script is designed to run as root within the installer only!" - echo "Do not run it on your system. To build a PVC installer ISO, use './buildiso.sh' instead!" - exit 1 -fi - -# Random delay to prevent overlaps -DELAY=$(( ${RANDOM} % 10 )) -echo -n "Waiting ${DELAY} seconds... " -sleep ${DELAY} -echo "done." - -if [[ -f ${lockfile} ]]; then - echo "An instance of 'install.sh' is already running!" - exit 1 -fi -touch ${lockfile} - -iso_name="XXDATEXX" -target_deploy_user="XXDEPLOYUSERXX" - -supported_debrelease="buster bullseye" -default_debrelease="buster" -default_debmirror="http://debian.mirror.rafal.ca/debian" - -inclpkglist="lvm2,parted,gdisk,grub-pc,grub-efi-amd64,linux-image-amd64,sudo,vim,gpg,gpg-agent,aptitude,openssh-server,vlan,ifenslave,python3,ca-certificates,curl" -suppkglist="firmware-linux,firmware-linux-nonfree,firmware-bnx2,firmware-bnx2x,ntp" - -# 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" - -# Respawn function -respawn() ( - $0 & disown -) - -# Checkin function -seed_checkin() ( - case ${1} in - start) - action="install-start" - ;; - end) - action="install-complete" - ;; - esac - macaddr=$( ip -br link show ${target_interface} | awk '{ print $3 }' ) - ipaddr=$( ip -br address show ${target_interface} | awk '{ print $3 }' | awk -F '/' '{ print $1 }' ) - curl -X POST \ - -H "Content-Type: application/json" \ - -d "{\"action\":\"${action}\",\"macaddr\":\"${macaddr}\",\"ipaddr\":\"${ipaddr}\",\"hostname\":\"${target_hostname}\",\"bmc_macaddr\":\"${bmc_macaddr}\",\"bmc_ipaddr\":\"${bmc_ipaddr}\"}" \ - ${pvcbootstrapd_checkin_uri} -) - -# Obtain the preseed options from the kernel command line -install_option="" -seed_host="" -seed_file="" -kernel_cmdline=( $( cat /proc/cmdline ) ) -for option in ${kernel_cmdline[@]}; do - case ${option} in - pvcinstall.preseed=*) - install_option=${option#pvcinstall.preseed=} - ;; - pvcinstall.seed_host=*) - seed_host=${option#pvcinstall.seed_host=} - ;; - pvcinstall.seed_file=*) - seed_file=${option#pvcinstall.seed_file=} - ;; - esac -done - -seed_config() { - # Get IPMI BMC MAC for checkings - bmc_macaddr="$( ipmitool lan print | grep 'MAC Address ' | awk '{ print $NF }' )" - bmc_ipaddr="$( ipmitool lan print | grep 'IP Address ' | awk '{ print $NF }' )" - - # Perform DHCP on all interfaces to come online - for interface in $( ip address | grep '^[0-9]' | grep 'eno\|enp\|ens\|wlp' | awk '{ print $2 }' | tr -d ':' ); do - ip link set ${interface} up - pgrep dhclient || dhclient ${interface} - done - - # Fetch the seed config - tftp -m binary "${seed_host}" -c get "${seed_file}" /tmp/install.seed - - . /tmp/install.seed || exit 1 - - # Handle the target interface - target_route="$( ip route show to match ${seed_host} | grep 'scope link' )" - target_interface="$( grep -E -o 'e[a-z]+[0-9]+[a-z0-9]*' <<<"${target_route}" )" - - # Handle the target disk - if [[ -n ${target_disk_path} ]]; then - target_disk="$( realpath ${target_disk_path} )" - else - # Find the (first) disk with the given model - for disk in /dev/sd?; do - disk_model="$( fdisk -l ${disk} | grep 'Disk model:' | sed 's/Disk model: //g' )" - if [[ ${disk_model} == ${target_disk_model} ]]; then - target_disk="${disk}" - break - fi - done - fi - if [[ ! -b ${target_disk} ]]; then - echo "Invalid disk or disk not found!" - exit 1 - fi -} - -interactive_config() { - clear - - echo "--------------------------------------------------------" - echo "| PVC Node installer (${iso_name}) |" - 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? /dev/nvme?n?; do - if [[ ! -b ${disk} ]]; then - continue - fi - 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 at least 30GB to be installed to, and 100GB is the" - echo "recommended minimum size for optimal production partition sizes." - echo "* NOTE: For optimal performance, this disk should be high-performance flash (SSD, etc.)." - echo "* NOTE: This disk should be a RAID-1 volume configured in hardware, or a durable storage" - echo "device, 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 - blockdev_size_gbytes="$(( $( blockdev --getsize64 ${target_disk} ) / 1024 / 1024 / 1024 - 1))" - if [[ ${blockdev_size_gbytes} -lt 30 ]]; then - target_disk="" - echo - echo "The specified disk is too small (<30 GB) to use as a PVC system disk." - echo "Please choose an alternative 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 with ^C and '/install.sh'." - echo - echo "Available interfaces:" - echo - echo -e "$( sed 's/\(.*\)/ \1/' <<<"${interfaces[@]}" )" - while [[ -z ${target_interface} ]]; do - echo - echo -n "> " - read target_interface - if [[ -z ${target_interface} ]]; then - echo - echo "Please enter a valid interface." - continue - fi - if ! grep -qw "${target_interface}" <<<"${interfaces[@]}"; then - target_interface="" - echo - echo "Please enter a valid 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 }' )" - real_interface="${vlan_interface}" - target_interfaces_is="static-vlan" - 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 }' )" - real_interface="${target_interface}" - target_interfaces_is="static-raw" - fi - cat </etc/resolv.conf -nameserver 8.8.8.8 -EOF - ;; - 'dhcp') - if [[ -n ${vlan_id} ]]; then - modprobe 8021q >&2 - vconfig add ${target_interface} ${vlan_id} &>/dev/null - vlan_interface=${target_interface}.${vlan_id} - dhclient ${vlan_interface} >&2 - real_interface="${vlan_interface}" - target_interfaces_is="dhcp-vlan" - else - dhclient ${target_interface} >&2 - real_interface="${target_interface}" - target_interfaces_is="dhcp-raw" - fi - ;; - esac - echo "done." - echo - - echo -n "Waiting for networking to become ready... " - while ! ping -q -c 1 8.8.8.8 &>/dev/null; do - sleep 1 - done - echo "done." - echo - - echo "4a) Please enter an alternate Debian release codename for the system if desired." - echo " Supported: ${supported_debrelease}" - echo " Default: ${default_debrelease}" - while [[ -z ${debrelease} ]]; do - echo - echo -n "> " - read debrelease - if [[ -z ${debrelease} ]]; then - debrelease="${default_debrelease}" - fi - if ! grep -qw "${debrelease}" <<<"${supported_debrelease}"; then - debrelease="" - echo - echo "Please enter a valid release." - continue - fi - echo - done - - echo "4b) Please enter an HTTP URL for an alternate Debian mirror if desired." - echo " Default: ${default_debmirror}" - while [[ -z ${debmirror} ]]; do - echo - echo -n "> " - read debmirror - if [[ -z ${debmirror} ]]; then - debmirror="${default_debmirror}" - fi - if ! wget -O /dev/null ${debmirror}/dists/${debrelease}/Release &>/dev/null; then - debmirror="" - echo - echo "Please enter a valid Debian mirror URL." - continue - fi - echo - echo "Repository mirror '${debmirror}' successfully validated." - echo - done - - target_keys_method="wget" - echo "5) Please enter an HTTP URL containing a text list of SSH authorized keys to" - echo "fetch. These keys will be allowed access to the deployment user 'XXDEPLOYUSER'" - echo "via SSH." - echo "" - echo "Leave blank to bypass this and use a password instead." - echo - echo -n "> " - read target_keys_path - if [[ -z ${target_keys_path} ]]; then - echo - echo "No SSH keys URL specified. Falling back to password configuration." - echo - echo "5) Please enter a password (hidden), twice, for the deployment user '${target_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_path} &>/dev/null; do - echo - echo "Please enter a valid SSH keys URL." - echo - echo -n "> " - read target_keys_path - done - echo - echo "SSH key source '${target_keys_path}' successfully validated." - fi - echo -} - -case ${install_option} in - on) - seed_config - ;; - *) - interactive_config - ;; -esac - -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 - -exec 1> >( tee -ia ${logfile} ) -exec 2> >( tee -ia ${logfile} >/dev/null ) -set -o errexit -set -o xtrace - -case ${install_option} in - on) - seed_checkin start - ;; - *) - # noop - true - ;; -esac - -cleanup() { - set +o errexit - echo -n "Cleaning up... " - umount ${target}/tmp >&2 - 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}/var/lib/zookeeper >&2 - umount ${target}/boot/efi >&2 - umount ${target}/boot >&2 - umount ${target} >&2 - vgchange -an >&2 - rmdir ${target} >&2 - rm ${lockfile} - echo "done." - echo - - case ${install_option} in - on) - respawn - ;; - *) - # noop - true - ;; - esac -} -trap cleanup EXIT - -echo -n "Determining partition sizes... " -blockdev_size_bytes="$( blockdev --getsize64 ${target_disk} )" -blockdev_size_gbytes="$(( ${blockdev_size_bytes} / 1024 / 1024 / 1024 - 1))" -if [[ ${blockdev_size_gbytes} -ge 100 ]]; then - # Optimal sized system disk (>=100GB), use large partitions - size_root_lv="32" - size_ceph_lv="8" - size_zookeeper_lv="32" - size_swap_lv="16" - echo "found large disk (${blockdev_size_gbytes} >= 100GB), using optimal partition sizes." -else - # Minimum sized disk (>=30GB), use small partitions - size_root_lv="8" - size_ceph_lv="4" - size_zookeeper_lv="8" - size_swap_lv="8" - echo "found small disk (${blockdev_size_gbytes} < 100GB), using small partition sizes." -fi - -echo -n "Disabing existing volume groups... " -vgchange -an >&2 || true -echo "done." - -blockcheck() { - # Use for testing only - if [[ -n ${SKIP_BLOCKCHECK} ]]; then - return - fi - - # Determine optimal block size for zeroing - exponent=16 - remainder=1 - while [[ ${remainder} -gt 0 && ${exponent} -gt 0 ]]; do - exponent=$(( ${exponent} - 1 )) - size=$(( 2**9 * 2 ** ${exponent} )) - count=$(( ${blockdev_size_bytes} / ${size} )) - remainder=$(( ${blockdev_size_bytes} - ${count} * ${size} )) - done - if [[ ${remainder} -gt 0 ]]; then - echo "Failed to find a suitable block size for wiping... skipping." - return - fi - - echo -n "Checking if block device '${target_disk}' is already wiped... " - if ! cmp --silent --bytes ${blockdev_size_bytes} /dev/zero ${target_disk}; then - echo "false." - echo "Wiping block device '${target_disk}' (${count} blocks of ${size} bytes)..." - dd if=/dev/zero of=${target_disk} bs=${size} count=${count} oflag=direct status=progress 2>&1 - else - echo "done." - fi -} -blockcheck - -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 'vgx'... " -yes | vgcreate vgx ${target_disk}3 >&2 -echo "done." - -echo -n "Creating root logical volume (${size_root_lv}GB)... " -yes | 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)... " -yes | mkfs.ext4 /dev/vgx/ceph >&2 -echo "done." - -echo -n "Creating zookeeper logical volume (${size_zookeeper_lv}GB)... " -yes | lvcreate -L ${size_zookeeper_lv}G -n zookeeper vgx >&2 -echo "done." -echo -n "Creating filesystem on zookeeper logical volume (ext4)... " -yes | mkfs.ext4 /dev/vgx/zookeeper >&2 -echo "done." - -echo -n "Creating swap logical volume (${size_swap_lv}GB)... " -yes | 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." - -vgchange -ay >&2 -target="/tmp/target" -mkdir -p ${target} >&2 -echo -n "Mounting disks on temporary target '${target}'... " -mount /dev/vgx/root ${target} >&2 -mkdir -p ${target}/boot >&2 -chattr +i ${target}/boot >&2 -mount ${target_disk}2 ${target}/boot >&2 -mkdir -p ${target}/boot/efi >&2 -chattr +i ${target}/boot/efi >&2 -mount ${target_disk}1 ${target}/boot/efi >&2 -mkdir -p ${target}/var/lib/ceph >&2 -chattr +i ${target}/var/lib/ceph >&2 -mount /dev/vgx/ceph ${target}/var/lib/ceph >&2 -mkdir -p ${target}/var/lib/zookeeper >&2 -chattr +i ${target}/var/lib/zookeeper >&2 -mount /dev/vgx/zookeeper ${target}/var/lib/zookeeper >&2 -mkdir -p ${target}/tmp >&2 -chattr +i ${target}/tmp >&2 -mount -t tmpfs tmpfs ${target}/tmp >&2 -echo "done." - -echo -n "Running debootstrap install... " -echo "Command: debootstrap --include=${inclpkglist} ${debrelease} ${target}/ ${debmirror}" >&2 -debootstrap --include=${inclpkglist} ${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="$( realpath ${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-zookeeper /var/lib/zookeeper ext4 errors=remount-ro 0 2" | tee -a ${target}/etc/fstab >&2 -echo "/dev/mapper/vgx-swap none 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 "tmpfs /tmp tmpfs defaults 0 0" | tee -a ${target}/etc/fstab >&2 -echo "done." - -seed_interfaces_segment() { - # A seed install is always "dhcp-raw" since the provisioner is always a dedicated, access port - target_interfaces_block="auto ${target_interface}\niface ${target_interface} inet dhcp\npost-up /etc/network/pvcprovisionerd.checkin.sh \$IFACE" -} - -interactive_interfaces_segment() { - case ${target_interfaces_is} in - static-vlan) - 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}" - ;; - static-raw) - target_interfaces_block="auto ${target_interface}\niface ${target_interface} inet ${target_netformat}\n\taddress ${formatted_ipaddr}\n\tnetmask ${formatted_netmask}\n\tgateway ${target_defgw}" - ;; - dhcp-vlan) - target_interfaces_block="auto ${vlan_interface}\niface ${vlan_interface} inet ${target_netformat}\n\tvlan_raw_device${target_interface}" - ;; - dhcp-raw) - target_interfaces_block="auto ${target_interface}\niface ${target_interface} inet ${target_netformat}" - ;; - esac -} - -echo -n "Creating bootstrap interface segment... " -case ${install_option} in - on) - seed_interfaces_segment - ;; - *) - interactive_interfaces_segment - ;; -esac -echo "done." - -echo -n "Adding bootstrap interface segment... " -echo -e "${target_interfaces_block}" | tee ${target}/etc/network/interfaces.d/${target_interface} >&2 -echo "done." - -case ${install_option} in - on) - echo -n "Creating bond interface segment... " - bond_interfaces="$( ip -br link show | grep -E -o '^e[a-z]+[0-9]+[a-z0-9]*' | grep -v "^${target_interface}$" | tr '\n' ' ' )" - bond_interfaces_block="auto bond0\niface bond0 inet manual\n\tbond-mode 802.3ad\n\tbond-slaves ${bond_interfaces}\n\tpost-up ip link set mtu 9000 dev \$IFACE" - echo "done." - - echo -n "Adding bond interface segment... " - echo -e "${bond_interfaces_block}" | tee ${target}/etc/network/interfaces.d/bond0 >&2 - echo "done." - - echo -n "Adding bootstrap interface post-up checkin script... " - cat <&2 -#!/usr/bin/env bash -target_interface=\${1} -pvcbootstrapd_checkin_uri="${pvcbootstrapd_checkin_uri}" -macaddr=\$( ip -br link show \${target_interface} | awk '{ print \$3 }' ) -ipaddr=\$( ip -br address show \${target_interface} | awk '{ print \$3 }' | awk -F '/' '{ print \$1 }' ) -bmc_macaddr=\$( ipmitool lan print | grep 'MAC Address ' | awk '{ print $NF }' ) -bmc_ipaddr=\$( ipmitool lan print | grep 'IP Address ' | awk '{ print $NF }' ) -if [[ -f /etc/pvc-install.pvcbootstrapd_completed ]]; then - # The third boot, when all pvcprovisionerd plugins have been run (this script will henceforth do nothing) - action="system-boot_bootstrap-completed" -elif [[ -f /etc/pvc-install.base && -f /etc/pvc-install.pvc ]]; then - # The second boot, when Ansible has been run and plugins running - action="system-boot_ansible-completed" - touch /etc/pvc-install.pvcbootstrapd_completed -else - # The first boot, when Ansible has not been run yet - action="system-boot_initial" -fi -curl -X POST \ - -H "Content-Type: application/json" \ - -d "{\"action\":\"\${action}\",\"macaddr\":\"\${macaddr}\",\"ipaddr\":\"\${ipaddr}\",\"hostname\":\"\$( hostname -s )\",\"bmc_macaddr\":\"\${bmc_macaddr}\",\"bmc_ipaddr\":\"\${bmc_ipaddr}\"}" \ - \${pvcbootstrapd_checkin_uri} - -if [[ -f /etc/pvc-install.pvcbootstrapd_completed ]]; then - # Clean up the bootstrap interface and this script - rm /etc/network/interfaces.d/\${target_interface} - rm \$0 - exit 0 -fi -EOF - chmod +x ${target}/etc/network/pvcprovisionerd.checkin.sh - echo "done." - ;; - *) - # noop - true - ;; -esac -echo "done." - -echo -n "Setting temporary 'root' password... " -echo "root:${root_password}" | chroot ${target} chpasswd >&2 -echo "done." - -echo -n "Adding deployment user... " -mv ${target}/home ${target}/var/home >&2 -chroot ${target} useradd -u 200 -d /var/home/${target_deploy_user} -m -s /bin/bash -g operator -G sudo ${target_deploy_user} >&2 -chroot ${target} mkdir -p /var/home/${target_deploy_user}/.ssh -if [[ -n ${target_keys_path} ]]; then - case ${target_keys_method} in - wget) - wget -O ${target}/var/home/${target_deploy_user}/.ssh/authorized_keys ${target_keys_path} - ;; - tftp) - tftp -m binary "${seed_host}" -c get "${target_keys_path}" ${target}/var/home/${target_deploy_user}/.ssh/authorized_keys - ;; - esac - chroot ${target} chmod 0600 /var/home/${target_deploy_user}/.ssh/authorized_keys - chroot ${target} chown -R ${target_deploy_user}:operator /var/home/${target_deploy_user} -else - echo "${target_deploy_user}:${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 ${target_interface} | grep inet | awk '{ print \$2 }' | head -1 )\" -cat </etc/issue -Debian GNU/Linux 10 \\\\n \\\\l - -Bootstrap 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 -cat <&2 -GRUB_DEFAULT=0 -GRUB_TIMEOUT=5 -GRUB_DISTRIBUTOR="Parallel Virtual Cluster (PVC) - Debian" -GRUB_CMDLINE_LINUX="console=hvc0 console=tty0 console=ttyS0,115200" -GRUB_TERMINAL_INPUT="console serial" -GRUB_TERMINAL_OUTPUT="gfxterm serial" -GRUB_SERIAL_COMMAND="serial --unit=0 --unit=1 --speed=115200" -EOF -chroot ${target} grub-install --force --target=${bios_target} ${target_disk} >&2 -chroot ${target} grub-mkconfig -o /boot/grub/grub.cfg >&2 -echo "done." - -seed_postinst() { - cleanup - echo "Temporary root password: ${root_password}" - seed_checkin end - - echo -n "Rebooting in 10 seconds..." - i=10 - while [[ ${i} -gt 0 ]]; do - sleep 1 - i=$(( ${1} - 1 )) - echo -n "." - done - echo - reboot -} - -interactive_postinst() { - set +o errexit - echo - echo -n "Edit the /etc/network/interfaces file in the target before completing setup? If you plan to use bonding, it is prudent to set this up in basic form now! [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 to reboot the system. |" - echo "| 2. Boot the system 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 -} - -case ${install_option} in - on) - seed_postinst - ;; - *) - interactive_postinst - ;; -esac diff --git a/install.sh b/install.sh new file mode 120000 index 0000000..aaacdaa --- /dev/null +++ b/install.sh @@ -0,0 +1 @@ +templates/install.sh \ No newline at end of file diff --git a/pxelinux.0 b/pxelinux.0 deleted file mode 100644 index e69de29..0000000 diff --git a/templates/2000-remove-root-pw.sh b/templates/2000-remove-root-pw.sh new file mode 100755 index 0000000..c061e73 --- /dev/null +++ b/templates/2000-remove-root-pw.sh @@ -0,0 +1,3 @@ +#!/bin/sh +echo "I: remove root password +passwd --delete root diff --git a/templates/9990-initramfs-tools.sh b/templates/9990-initramfs-tools.sh new file mode 100755 index 0000000..5ebdefd --- /dev/null +++ b/templates/9990-initramfs-tools.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +#set -e + +log_wait_msg () +{ + # Print a message and wait for enter + if [ -x /bin/plymouth ] && plymouth --ping + then + plymouth message --text="$@" + plymouth watch-keystroke | read nunya + fi + + _log_msg "Waiting: ${@} ... \n" +} + +# Override maybe_break from scripts/functions +maybe_break() +{ + if [ "${break}" = "$1" ]; then + # Call original panic + . /scripts/functions + panic "Spawning shell within the initramfs" + fi +} + +# Override panic from scripts/functions +panic() +{ + for _PARAMETER in ${LIVE_BOOT_CMDLINE} + do + case "${_PARAMETER}" in + panic=*) + panic="${_PARAMETER#*panic=}" + ;; + esac + done + + DEB_1="\033[1;31m .''\`. \033[0m" + DEB_2="\033[1;31m: :' : \033[0m" + DEB_3="\033[1;31m\`. \`'\` \033[0m" + DEB_4="\033[1;31m \`- \033[0m" + + LIVELOG="\033[1;37m/boot.log\033[0m" + DEBUG="\033[1;37mdebug\033[0m" + + # Reset redirections to avoid buffering + exec 1>&6 6>&- + exec 2>&7 7>&- + kill ${tailpid} + + printf "\n\n" + printf " \033[1;37mBOOT FAILED!\033[0m\n" + printf "\n" + printf " The PVC installer image failed to boot.\n\n" + printf "The error message was:\n\n " + + # Call original panic + . /scripts/functions + panic "$@" + + # Reboot system + printf + printf "System will reboot in 30 seconds" + sleep 30 + reboot -f +} diff --git a/templates/boot.pxe b/templates/boot.pxe new file mode 100644 index 0000000..8a41597 --- /dev/null +++ b/templates/boot.pxe @@ -0,0 +1,24 @@ +#!ipxe + +# Set global variables +set root-url tftp://${next-server} +set kernel vmlinuz +set initrd initrd.img + +# Set kernel command line parameters +set imgargs-base vga=normal nomodeset boot=live components ethdevice-timeout=600 timezone=America/Toronto fetch=${root-url}/filesystem.squashfs username=root +set imgargs-pvcinstall pvcinstall.preseed=on pvcinstall.seed_host=${next-server} pvcinstall.seed_file=/host/mac-${mac:hexraw}.preseed + +# Load per-host kernel command line parameters (should contain ${imgargs-host} if present) +chain --autofree ${root-url}/host/mac-${mac:hexraw}.ipxe || + +# Set default menu options +set menu-default pvc-installer +set submenu-default pvc-installer + +# PVC installer menu option +:pvc-installer +kernel ${root-url}/vmlinuz +initrd ${root-url}/initrd.img +imgargs vmlinuz ${imgargs-host} ${imgargs-base} ${imgargs-pvcinstall} +boot diff --git a/templates/firmware.list.chroot b/templates/firmware.list.chroot new file mode 100644 index 0000000..5fe585e --- /dev/null +++ b/templates/firmware.list.chroot @@ -0,0 +1 @@ +firmware-bnx2 firmware-bnx2x diff --git a/templates/getty-override.conf b/templates/getty-override.conf new file mode 100644 index 0000000..18a040e --- /dev/null +++ b/templates/getty-override.conf @@ -0,0 +1,3 @@ +[Service] +ExecStart= +ExecStart=-/sbin/agetty -o '-p -- \\u' --autologin root --noclear %I $TERM diff --git a/grub.cfg b/templates/grub.cfg similarity index 100% rename from grub.cfg rename to templates/grub.cfg diff --git a/templates/hostname b/templates/hostname new file mode 100644 index 0000000..4e16cd7 --- /dev/null +++ b/templates/hostname @@ -0,0 +1 @@ +pvc-live-installer diff --git a/templates/install.sh b/templates/install.sh new file mode 100755 index 0000000..1a220a5 --- /dev/null +++ b/templates/install.sh @@ -0,0 +1,863 @@ +#!/usr/bin/env bash + +logfile="/tmp/pvc-install.log" +lockfile="/run/pvc-install.lock" + +if [[ $( whoami ) != "root" ]]; then + echo "STOP! This script is designed to run as root within the installer only!" + echo "Do not run it on your system. To build a PVC installer ISO, use './buildiso.sh' instead!" + exit 1 +fi + +# Random delay to prevent overlaps +DELAY=$(( ${RANDOM} % 10 )) +echo -n "Waiting ${DELAY} seconds... " +sleep ${DELAY} +echo "done." + +if [[ -f ${lockfile} ]]; then + echo "An instance of 'install.sh' is already running!" + exit 1 +fi +touch ${lockfile} + +iso_name="XXDATEXX" +target_deploy_user="XXDEPLOYUSERXX" + +supported_debrelease="buster bullseye" +default_debrelease="buster" +default_debmirror="http://debian.mirror.rafal.ca/debian" + +inclpkglist="lvm2,parted,gdisk,grub-pc,grub-efi-amd64,linux-image-amd64,sudo,vim,gpg,gpg-agent,aptitude,openssh-server,vlan,ifenslave,python3,ca-certificates,curl" +suppkglist="firmware-linux,firmware-linux-nonfree,firmware-bnx2,firmware-bnx2x,ntp" + +# 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" + +# Respawn function +respawn() ( + $0 & disown +) + +# Checkin function +seed_checkin() ( + case ${1} in + start) + action="install-start" + ;; + end) + action="install-complete" + ;; + esac + macaddr=$( ip -br link show ${target_interface} | awk '{ print $3 }' ) + ipaddr=$( ip -br address show ${target_interface} | awk '{ print $3 }' | awk -F '/' '{ print $1 }' ) + curl -X POST \ + -H "Content-Type: application/json" \ + -d "{\"action\":\"${action}\",\"macaddr\":\"${macaddr}\",\"ipaddr\":\"${ipaddr}\",\"hostname\":\"${target_hostname}\",\"bmc_macaddr\":\"${bmc_macaddr}\",\"bmc_ipaddr\":\"${bmc_ipaddr}\"}" \ + ${pvcbootstrapd_checkin_uri} +) + +# Obtain the preseed options from the kernel command line +install_option="" +seed_host="" +seed_file="" +kernel_cmdline=( $( cat /proc/cmdline ) ) +for option in ${kernel_cmdline[@]}; do + case ${option} in + pvcinstall.preseed=*) + install_option=${option#pvcinstall.preseed=} + ;; + pvcinstall.seed_host=*) + seed_host=${option#pvcinstall.seed_host=} + ;; + pvcinstall.seed_file=*) + seed_file=${option#pvcinstall.seed_file=} + ;; + esac +done + +seed_config() { + # Get IPMI BMC MAC for checkings + bmc_macaddr="$( ipmitool lan print | grep 'MAC Address ' | awk '{ print $NF }' )" + bmc_ipaddr="$( ipmitool lan print | grep 'IP Address ' | awk '{ print $NF }' )" + + # Perform DHCP on all interfaces to come online + for interface in $( ip address | grep '^[0-9]' | grep 'eno\|enp\|ens\|wlp' | awk '{ print $2 }' | tr -d ':' ); do + ip link set ${interface} up + pgrep dhclient || dhclient ${interface} + done + + # Fetch the seed config + tftp -m binary "${seed_host}" -c get "${seed_file}" /tmp/install.seed + + . /tmp/install.seed || exit 1 + + # Handle the target interface + target_route="$( ip route show to match ${seed_host} | grep 'scope link' )" + target_interface="$( grep -E -o 'e[a-z]+[0-9]+[a-z0-9]*' <<<"${target_route}" )" + + # Handle the target disk + if [[ -n ${target_disk_path} ]]; then + target_disk="$( realpath ${target_disk_path} )" + else + # Find the (first) disk with the given model + for disk in /dev/sd?; do + disk_model="$( fdisk -l ${disk} | grep 'Disk model:' | sed 's/Disk model: //g' )" + if [[ ${disk_model} == ${target_disk_model} ]]; then + target_disk="${disk}" + break + fi + done + fi + if [[ ! -b ${target_disk} ]]; then + echo "Invalid disk or disk not found!" + exit 1 + fi +} + +interactive_config() { + clear + + echo "--------------------------------------------------------" + echo "| PVC Node installer (${iso_name}) |" + 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? /dev/nvme?n?; do + if [[ ! -b ${disk} ]]; then + continue + fi + 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 at least 30GB to be installed to, and 100GB is the" + echo "recommended minimum size for optimal production partition sizes." + echo "* NOTE: For optimal performance, this disk should be high-performance flash (SSD, etc.)." + echo "* NOTE: This disk should be a RAID-1 volume configured in hardware, or a durable storage" + echo "device, 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 + blockdev_size_gbytes="$(( $( blockdev --getsize64 ${target_disk} ) / 1024 / 1024 / 1024 - 1))" + if [[ ${blockdev_size_gbytes} -lt 30 ]]; then + target_disk="" + echo + echo "The specified disk is too small (<30 GB) to use as a PVC system disk." + echo "Please choose an alternative 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 with ^C and '/install.sh'." + echo + echo "Available interfaces:" + echo + echo -e "$( sed 's/\(.*\)/ \1/' <<<"${interfaces[@]}" )" + while [[ -z ${target_interface} ]]; do + echo + echo -n "> " + read target_interface + if [[ -z ${target_interface} ]]; then + echo + echo "Please enter a valid interface." + continue + fi + if ! grep -qw "${target_interface}" <<<"${interfaces[@]}"; then + target_interface="" + echo + echo "Please enter a valid 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 }' )" + real_interface="${vlan_interface}" + target_interfaces_is="static-vlan" + 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 }' )" + real_interface="${target_interface}" + target_interfaces_is="static-raw" + fi + cat </etc/resolv.conf +nameserver 8.8.8.8 +EOF + ;; + 'dhcp') + if [[ -n ${vlan_id} ]]; then + modprobe 8021q >&2 + vconfig add ${target_interface} ${vlan_id} &>/dev/null + vlan_interface=${target_interface}.${vlan_id} + dhclient ${vlan_interface} >&2 + real_interface="${vlan_interface}" + target_interfaces_is="dhcp-vlan" + else + dhclient ${target_interface} >&2 + real_interface="${target_interface}" + target_interfaces_is="dhcp-raw" + fi + ;; + esac + echo "done." + echo + + echo -n "Waiting for networking to become ready... " + while ! ping -q -c 1 8.8.8.8 &>/dev/null; do + sleep 1 + done + echo "done." + echo + + echo "4a) Please enter an alternate Debian release codename for the system if desired." + echo " Supported: ${supported_debrelease}" + echo " Default: ${default_debrelease}" + while [[ -z ${debrelease} ]]; do + echo + echo -n "> " + read debrelease + if [[ -z ${debrelease} ]]; then + debrelease="${default_debrelease}" + fi + if ! grep -qw "${debrelease}" <<<"${supported_debrelease}"; then + debrelease="" + echo + echo "Please enter a valid release." + continue + fi + echo + done + + echo "4b) Please enter an HTTP URL for an alternate Debian mirror if desired." + echo " Default: ${default_debmirror}" + while [[ -z ${debmirror} ]]; do + echo + echo -n "> " + read debmirror + if [[ -z ${debmirror} ]]; then + debmirror="${default_debmirror}" + fi + if ! wget -O /dev/null ${debmirror}/dists/${debrelease}/Release &>/dev/null; then + debmirror="" + echo + echo "Please enter a valid Debian mirror URL." + continue + fi + echo + echo "Repository mirror '${debmirror}' successfully validated." + echo + done + + target_keys_method="wget" + echo "5) Please enter an HTTP URL containing a text list of SSH authorized keys to" + echo "fetch. These keys will be allowed access to the deployment user 'XXDEPLOYUSER'" + echo "via SSH." + echo "" + echo "Leave blank to bypass this and use a password instead." + echo + echo -n "> " + read target_keys_path + if [[ -z ${target_keys_path} ]]; then + echo + echo "No SSH keys URL specified. Falling back to password configuration." + echo + echo "5) Please enter a password (hidden), twice, for the deployment user '${target_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_path} &>/dev/null; do + echo + echo "Please enter a valid SSH keys URL." + echo + echo -n "> " + read target_keys_path + done + echo + echo "SSH key source '${target_keys_path}' successfully validated." + fi + echo +} + +case ${install_option} in + on) + seed_config + ;; + *) + interactive_config + ;; +esac + +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 + +exec 1> >( tee -ia ${logfile} ) +exec 2> >( tee -ia ${logfile} >/dev/null ) +set -o errexit +set -o xtrace + +case ${install_option} in + on) + seed_checkin start + ;; + *) + # noop + true + ;; +esac + +cleanup() { + set +o errexit + echo -n "Cleaning up... " + umount ${target}/tmp >&2 + 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}/var/lib/zookeeper >&2 + umount ${target}/boot/efi >&2 + umount ${target}/boot >&2 + umount ${target} >&2 + vgchange -an >&2 + rmdir ${target} >&2 + rm ${lockfile} + echo "done." + echo + + case ${install_option} in + on) + respawn + ;; + *) + # noop + true + ;; + esac +} +trap cleanup EXIT + +echo -n "Determining partition sizes... " +blockdev_size_bytes="$( blockdev --getsize64 ${target_disk} )" +blockdev_size_gbytes="$(( ${blockdev_size_bytes} / 1024 / 1024 / 1024 - 1))" +if [[ ${blockdev_size_gbytes} -ge 100 ]]; then + # Optimal sized system disk (>=100GB), use large partitions + size_root_lv="32" + size_ceph_lv="8" + size_zookeeper_lv="32" + size_swap_lv="16" + echo "found large disk (${blockdev_size_gbytes} >= 100GB), using optimal partition sizes." +else + # Minimum sized disk (>=30GB), use small partitions + size_root_lv="8" + size_ceph_lv="4" + size_zookeeper_lv="8" + size_swap_lv="8" + echo "found small disk (${blockdev_size_gbytes} < 100GB), using small partition sizes." +fi + +echo -n "Disabing existing volume groups... " +vgchange -an >&2 || true +echo "done." + +blockcheck() { + # Use for testing only + if [[ -n ${SKIP_BLOCKCHECK} ]]; then + return + fi + + # Determine optimal block size for zeroing + exponent=16 + remainder=1 + while [[ ${remainder} -gt 0 && ${exponent} -gt 0 ]]; do + exponent=$(( ${exponent} - 1 )) + size=$(( 2**9 * 2 ** ${exponent} )) + count=$(( ${blockdev_size_bytes} / ${size} )) + remainder=$(( ${blockdev_size_bytes} - ${count} * ${size} )) + done + if [[ ${remainder} -gt 0 ]]; then + echo "Failed to find a suitable block size for wiping... skipping." + return + fi + + echo -n "Checking if block device '${target_disk}' is already wiped... " + if ! cmp --silent --bytes ${blockdev_size_bytes} /dev/zero ${target_disk}; then + echo "false." + echo "Wiping block device '${target_disk}' (${count} blocks of ${size} bytes)..." + dd if=/dev/zero of=${target_disk} bs=${size} count=${count} oflag=direct status=progress 2>&1 + else + echo "done." + fi +} +blockcheck + +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 'vgx'... " +yes | vgcreate vgx ${target_disk}3 >&2 +echo "done." + +echo -n "Creating root logical volume (${size_root_lv}GB)... " +yes | 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)... " +yes | mkfs.ext4 /dev/vgx/ceph >&2 +echo "done." + +echo -n "Creating zookeeper logical volume (${size_zookeeper_lv}GB)... " +yes | lvcreate -L ${size_zookeeper_lv}G -n zookeeper vgx >&2 +echo "done." +echo -n "Creating filesystem on zookeeper logical volume (ext4)... " +yes | mkfs.ext4 /dev/vgx/zookeeper >&2 +echo "done." + +echo -n "Creating swap logical volume (${size_swap_lv}GB)... " +yes | 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." + +vgchange -ay >&2 +target="/tmp/target" +mkdir -p ${target} >&2 +echo -n "Mounting disks on temporary target '${target}'... " +mount /dev/vgx/root ${target} >&2 +mkdir -p ${target}/boot >&2 +chattr +i ${target}/boot >&2 +mount ${target_disk}2 ${target}/boot >&2 +mkdir -p ${target}/boot/efi >&2 +chattr +i ${target}/boot/efi >&2 +mount ${target_disk}1 ${target}/boot/efi >&2 +mkdir -p ${target}/var/lib/ceph >&2 +chattr +i ${target}/var/lib/ceph >&2 +mount /dev/vgx/ceph ${target}/var/lib/ceph >&2 +mkdir -p ${target}/var/lib/zookeeper >&2 +chattr +i ${target}/var/lib/zookeeper >&2 +mount /dev/vgx/zookeeper ${target}/var/lib/zookeeper >&2 +mkdir -p ${target}/tmp >&2 +chattr +i ${target}/tmp >&2 +mount -t tmpfs tmpfs ${target}/tmp >&2 +echo "done." + +echo -n "Running debootstrap install... " +echo "Command: debootstrap --include=${inclpkglist} ${debrelease} ${target}/ ${debmirror}" >&2 +debootstrap --include=${inclpkglist} ${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="$( realpath ${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-zookeeper /var/lib/zookeeper ext4 errors=remount-ro 0 2" | tee -a ${target}/etc/fstab >&2 +echo "/dev/mapper/vgx-swap none 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 "tmpfs /tmp tmpfs defaults 0 0" | tee -a ${target}/etc/fstab >&2 +echo "done." + +seed_interfaces_segment() { + # A seed install is always "dhcp-raw" since the provisioner is always a dedicated, access port + target_interfaces_block="auto ${target_interface}\niface ${target_interface} inet dhcp\npost-up /etc/network/pvcprovisionerd.checkin.sh \$IFACE" +} + +interactive_interfaces_segment() { + case ${target_interfaces_is} in + static-vlan) + 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}" + ;; + static-raw) + target_interfaces_block="auto ${target_interface}\niface ${target_interface} inet ${target_netformat}\n\taddress ${formatted_ipaddr}\n\tnetmask ${formatted_netmask}\n\tgateway ${target_defgw}" + ;; + dhcp-vlan) + target_interfaces_block="auto ${vlan_interface}\niface ${vlan_interface} inet ${target_netformat}\n\tvlan_raw_device${target_interface}" + ;; + dhcp-raw) + target_interfaces_block="auto ${target_interface}\niface ${target_interface} inet ${target_netformat}" + ;; + esac +} + +echo -n "Creating bootstrap interface segment... " +case ${install_option} in + on) + seed_interfaces_segment + ;; + *) + interactive_interfaces_segment + ;; +esac +echo "done." + +echo -n "Adding bootstrap interface segment... " +echo -e "${target_interfaces_block}" | tee ${target}/etc/network/interfaces.d/${target_interface} >&2 +echo "done." + +case ${install_option} in + on) + echo -n "Creating bond interface segment... " + bond_interfaces="$( ip -br link show | grep -E -o '^e[a-z]+[0-9]+[a-z0-9]*' | grep -v "^${target_interface}$" | tr '\n' ' ' )" + bond_interfaces_block="auto bond0\niface bond0 inet manual\n\tbond-mode 802.3ad\n\tbond-slaves ${bond_interfaces}\n\tpost-up ip link set mtu 9000 dev \$IFACE" + echo "done." + + echo -n "Adding bond interface segment... " + echo -e "${bond_interfaces_block}" | tee ${target}/etc/network/interfaces.d/bond0 >&2 + echo "done." + + echo -n "Adding bootstrap interface post-up checkin script... " + cat <&2 +#!/usr/bin/env bash +target_interface=\${1} +pvcbootstrapd_checkin_uri="${pvcbootstrapd_checkin_uri}" +macaddr=\$( ip -br link show \${target_interface} | awk '{ print \$3 }' ) +ipaddr=\$( ip -br address show \${target_interface} | awk '{ print \$3 }' | awk -F '/' '{ print \$1 }' ) +bmc_macaddr=\$( ipmitool lan print | grep 'MAC Address ' | awk '{ print $NF }' ) +bmc_ipaddr=\$( ipmitool lan print | grep 'IP Address ' | awk '{ print $NF }' ) +if [[ -f /etc/pvc-install.pvcbootstrapd_completed ]]; then + # The third boot, when all pvcprovisionerd plugins have been run (this script will henceforth do nothing) + action="system-boot_bootstrap-completed" +elif [[ -f /etc/pvc-install.base && -f /etc/pvc-install.pvc ]]; then + # The second boot, when Ansible has been run and plugins running + action="system-boot_ansible-completed" + touch /etc/pvc-install.pvcbootstrapd_completed +else + # The first boot, when Ansible has not been run yet + action="system-boot_initial" +fi +curl -X POST \ + -H "Content-Type: application/json" \ + -d "{\"action\":\"\${action}\",\"macaddr\":\"\${macaddr}\",\"ipaddr\":\"\${ipaddr}\",\"hostname\":\"\$( hostname -s )\",\"bmc_macaddr\":\"\${bmc_macaddr}\",\"bmc_ipaddr\":\"\${bmc_ipaddr}\"}" \ + \${pvcbootstrapd_checkin_uri} + +if [[ -f /etc/pvc-install.pvcbootstrapd_completed ]]; then + # Clean up the bootstrap interface and this script + rm /etc/network/interfaces.d/\${target_interface} + rm \$0 + exit 0 +fi +EOF + chmod +x ${target}/etc/network/pvcprovisionerd.checkin.sh + echo "done." + ;; + *) + # noop + true + ;; +esac +echo "done." + +echo -n "Setting temporary 'root' password... " +echo "root:${root_password}" | chroot ${target} chpasswd >&2 +echo "done." + +echo -n "Adding deployment user... " +mv ${target}/home ${target}/var/home >&2 +chroot ${target} useradd -u 200 -d /var/home/${target_deploy_user} -m -s /bin/bash -g operator -G sudo ${target_deploy_user} >&2 +chroot ${target} mkdir -p /var/home/${target_deploy_user}/.ssh +if [[ -n ${target_keys_path} ]]; then + case ${target_keys_method} in + wget) + wget -O ${target}/var/home/${target_deploy_user}/.ssh/authorized_keys ${target_keys_path} + ;; + tftp) + tftp -m binary "${seed_host}" -c get "${target_keys_path}" ${target}/var/home/${target_deploy_user}/.ssh/authorized_keys + ;; + esac + chroot ${target} chmod 0600 /var/home/${target_deploy_user}/.ssh/authorized_keys + chroot ${target} chown -R ${target_deploy_user}:operator /var/home/${target_deploy_user} +else + echo "${target_deploy_user}:${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 ${target_interface} | grep inet | awk '{ print \$2 }' | head -1 )\" +cat </etc/issue +Debian GNU/Linux 10 \\\\n \\\\l + +Bootstrap 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 +cat <&2 +GRUB_DEFAULT=0 +GRUB_TIMEOUT=5 +GRUB_DISTRIBUTOR="Parallel Virtual Cluster (PVC) - Debian" +GRUB_CMDLINE_LINUX="console=hvc0 console=tty0 console=ttyS0,115200" +GRUB_TERMINAL_INPUT="console serial" +GRUB_TERMINAL_OUTPUT="gfxterm serial" +GRUB_SERIAL_COMMAND="serial --unit=0 --unit=1 --speed=115200" +EOF +chroot ${target} grub-install --force --target=${bios_target} ${target_disk} >&2 +chroot ${target} grub-mkconfig -o /boot/grub/grub.cfg >&2 +echo "done." + +seed_postinst() { + cleanup + echo "Temporary root password: ${root_password}" + seed_checkin end + + echo -n "Rebooting in 10 seconds..." + i=10 + while [[ ${i} -gt 0 ]]; do + sleep 1 + i=$(( ${1} - 1 )) + echo -n "." + done + echo + reboot +} + +interactive_postinst() { + set +o errexit + echo + echo -n "Edit the /etc/network/interfaces file in the target before completing setup? If you plan to use bonding, it is prudent to set this up in basic form now! [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 to reboot the system. |" + echo "| 2. Boot the system 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 +} + +case ${install_option} in + on) + seed_postinst + ;; + *) + interactive_postinst + ;; +esac diff --git a/templates/installer.list.chroot b/templates/installer.list.chroot new file mode 100644 index 0000000..0ee088c --- /dev/null +++ b/templates/installer.list.chroot @@ -0,0 +1 @@ +live-task-standard live-tools live-boot live-boot-initramfs-tools linux-image-amd64 psmisc mdadm lvm2 parted gdisk dosfstools debootstrap grub-pc-bin grub-efi-amd64 sipcalc vim ca-certificates vlan tftp-hpa curl ipmitool diff --git a/isolinux.cfg b/templates/isolinux.cfg similarity index 100% rename from isolinux.cfg rename to templates/isolinux.cfg diff --git a/templates/logind.conf b/templates/logind.conf new file mode 100644 index 0000000..8ba073c --- /dev/null +++ b/templates/logind.conf @@ -0,0 +1,2 @@ +[Login] +NAutoVTs=2 diff --git a/install.seed.example b/templates/pxe-install-seed.example similarity index 100% rename from install.seed.example rename to templates/pxe-install-seed.example diff --git a/templates/resolv.conf b/templates/resolv.conf new file mode 100644 index 0000000..cae093a --- /dev/null +++ b/templates/resolv.conf @@ -0,0 +1 @@ +nameserver 8.8.8.8 diff --git a/templates/root.bashrc b/templates/root.bashrc new file mode 100644 index 0000000..95f1618 --- /dev/null +++ b/templates/root.bashrc @@ -0,0 +1 @@ +/install.sh diff --git a/templates/serial-getty-override.conf b/templates/serial-getty-override.conf new file mode 100644 index 0000000..516d491 --- /dev/null +++ b/templates/serial-getty-override.conf @@ -0,0 +1,3 @@ +[Service] +ExecStart= +ExecStart=-/sbin/agetty -o '-p -- \\u' --autologin root --noclear --keep-baud 115200,38400,9600 %I $TERM diff --git a/splash.png b/templates/splash.png similarity index 100% rename from splash.png rename to templates/splash.png diff --git a/splash.xcf b/templates/splash.xcf similarity index 100% rename from splash.xcf rename to templates/splash.xcf diff --git a/theme.txt b/templates/theme.txt similarity index 100% rename from theme.txt rename to templates/theme.txt