#!/bin/bash
# vim: shiftwidth=4:tabstop=4:textwidth=100

# automatic Debian OS installer
# Clearcable SOE v4

V=0.50	#initial version
V=0.55	#Debian 5.0
V=0.60	#GPT/LVM2/RAID/MD
V=0.61	#amd64
V=0.62	#automatic hard drive detection
V=0.63	#partition layout specification
V=0.64	#Sun server HW detection
V=0.65	#Linux /boot typecode corrected
V=0.66	#obtain SECTOR_SIZE from disk
V=0.67	#SPARC support
V=0.68	#VIA EPIA M-II hardware support initial
V=0.69	#VIA EPIA M-II hardware support cont.
V=0.70	#SPARC tweaks: disk detect hardcode, /lib64
V=0.71	#HAL tweaks, mdadm only needed for SPARC
V=0.72	#boot-sync compatibility (fstab/BOOTP+S)
V=0.73	#HOSTNAME handling in hardware in VM
V=0.74	#Supermicro X7SPA-HF (Atom) support
V=0.75	#LVM metadatacopies=1
V=0.76	#SPARC MMODEL via banner-name
V=0.77	#SPARC disk geometry: 255/63 and 64/32
V=0.78	#x86: reverse typical disk ordering error
V=0.79	#x86: MegaCli copy for MegaRAID 
V=0.80	#filtering and sorting of HAL data
V=0.81	#replicate /boot after all...
V=0.82	#Cisco UCS C200 M2
V=0.83	#RAID auto stop and wipe before installing
V=0.84	#check if master block device exists before grub2-install
V=0.85	#another grub2-install workaround for M2s
V=0.86	#another grub2-install workaround (zsh)
V=0.87	#added /var/log to standard VM setup
V=0.88	#VM FS umount order fixed
V=0.89	#final grub2-install fix (cd /) [dward]
V=0.90	#added dev/pts to pre-stage2 bind mounts
V=0.91	#use "system" instead of "baseboard" from DMI
V=0.92	#added "ethtool" package to default hardware install
V=0.93	#better support for small disks
V=0.94	#preliminary Debian6/squeeze support
V=0.95	#BUG: argument parsing for Debian version
V=0.96	#increased root+var partition sizes for Nomad
V=0.97	#BUG: argument parsing for Debian version again
V=0.98	#BUG: argument parsing for Debian version again (2)
V=0.99	#stage2 URL update
V=1.00	#test stage2 URL reachability
V=1.01	#IPv6-only environment compatibility
V=1.02	#debootstrap version update (squeeze official)
V=1.03	#small disk size set to <30GiB (36GB SCSI drives are NORMAL)
V=1.04	#vlan package only for bare hardware
V=1.05	#SYSTEM_SERIAL_NUMBER under VirtualBox is 0 --- reset
V=1.06	#override HW_SERIAL via shell's environment
V=1.07	#*.deb archive caching only on hypervisor
V=1.08	#no updating of /etc/modules (handled in bcfg2)
V=1.09	#set 'errexit' and 'nounset' to catch more errors
V=1.10	#correct final umount processing for a VM
V=1.11	#integrated LV creation for both domB and domU systems
V=1.12	#BUG: removed extraneous comma from VM_DSK_LIST
V=1.13	#BUG: command line processing for bare HW installs
V=1.14	#BUG: unmount /proc and /sys for bare HW installs
V=1.15	#BUG: swap missing from VM install xen/auto config
V=1.16	#security: ensure that newly created LVs are blank
V=1.17	#don't try to copy MegaCli for a VM
V=1.18	#BUG: fix calculation of last usable rounded disk sector 
V=1.19	#copy selected up-to-date configuration files from Xen
V=1.20	#added OptiPlex_GX520 machine type
V=1.21	#BUG: don't create 5th partition on factory rounded disks
V=1.22	#automatic VM resource sizing based on name
V=1.23	#mail VM settings
V=1.24	#more VM settings
V=1.25	#added netmask to base PKG_INCLUDE (used in stage2)
V=1.26	#check that HOSTNAME is in DNS for VM creation
V=1.27	#add "xvdX1" devices to VM_DSK_LIST instead of "/dev/xvdX1"
V=1.28	#added Aspire_E500 to supported desktops
V=1.29	#fall back to baseboard* when system* are not defined in DMI
V=1.30	#allow numbered backup VMs
V=1.31	#synchronized with soe-install bootstrap script
V=1.32	#ionice -c3 for volume verification
V=1.33	#added /var/log in HW defaults
V=1.34	#unified method for downloading binary replacement files
V=1.35	#mount and unmount AUX filesystems unmounted by debootstrap
V=1.36	#recognize Xen via /dev/xen: can be created manually
V=1.37	#exclude /dev/pts from chattr +i
V=1.38	#profiles for HW-based systems
V=1.39	#more automatic filesystem options for various mountpoints
V=1.40	#removed 'acpid' from HW install: safe to install via bcfg2
V=1.41	#allow installation on uncertified hardware
V=1.42	#discard trailing space in SYSTEM_SERIAL_NUMBER
V=1.43	#disk detection based on dev; HAL deprecated
V=1.44	#small swap on small disks
V=1.45	#added warning about installing on a single drive
V=1.46	#reduced backup template /srv LV size (needs adjusting anyway)
V=1.47	#VM Nomad hostname detection heuristics
V=1.48	#when xm info fails, set NR_CPUS=0 for alt-image compatibility
V=1.49	#better logic for final HW cleanup and boot replication
V=1.50	#issue warning when trying to install on unsupported hardware
V=1.51	#VM Nomad detection heuristics additions: resi
V=1.52	#added support for selecting Debian wheezy for testing
V=1.53	#standardized grep invocation
V=1.54	#moved disk size rounding partition from 5 to 128
V=1.55	#RAID-X support: multiple partitions for RAID 1 and 0 PVs
V=1.56	#distinguish between public and private mirrors
V=1.57	#provide all six fstab options for tmpfs (wheezy mount)
V=1.58	#use tar instead of rsync to create BOOTS for sparc compatibility
V=1.59	#reduce swap size if it exceeds available VG space
V=1.60	#wait for all 4 system partitions to be detected
V=1.61	#increased inode density for /srv (backup) filesystem
V=1.62	#provide more space for trailing RAID superblock on larger partitions
V=1.63	#do not wait for ${HDX}2 partition (does not exit on SPARC)
V=1.64	#create /var/log with 'largefile' profile, not 'largefile4'
V=1.65	#BUG: pad the last component of the 2nd MAC address with 0 if needed
V=1.66	#enforce specific Debian version for various domB profiles
V=1.67	#detect hardware RAID virtual disks and accept lone HD0
V=1.68	#do not copy MegaCli binary anymore; package available in bcfg2
V=1.69	#increased the size of /var/log partition for prov VMs to 6GiB
V=1.70	#use 'getent' instead of 'host' to allow VM IP address in /etc/hosts
V=1.71	#Cisco UCS C210 storage server support (LSI HW RAID)
V=1.72	#unified block device verify/wipe procedure, mandatory for domB
V=1.73	#require Debian 6 for filer, synchronize Nomad spool size with syscheck
V=1.74	#stop LVs and MD arrays before messing with HW-RAID controller config
V=1.75	#added Supermicro X7DB8 (Xeon) support
V=1.76	#change debian-lenny URL to Debian archive
V=1.77	#SPARC systems do not have hardware RAID
V=1.78	#updated silo/mdadm direct download paths for SPARC
V=1.79	#added cmp command for SPARC
V=1.80	#cpufrequtils are not essential - removed
V=1.81	#use the main mirror for the download function
V=1.82	#reset another fake SYSTEM_SERIAL_NUMBER (Supermicro boards)
V=1.83	#simplified NIC MAC -> HW_SERIAL parsing via awk
V=1.84	#removed htop and strace packages from base install
V=1.85	#increased /var/log LV default size for Nomad to 4GiB
V=1.86	#new silo.conf location for lenny->squeeze upgrades
V=1.87	#added volume sizing for LTSP profile
V=1.88	#default /var/log size increased for nsX VM profile
V=1.89	#debug output before EFI_SYSTEM format 
V=1.90	#added VMware platform and associated serial number conversion
V=1.91	#list physical drive status on megacli systems to aid troubleshooting
V=1.92	#explicitly set ID_VENDOR to "unknown" when not defined as a UDEV device property
V=1.93	#added /home volume to noms VM
V=1.94	#added sizing profile for speedtest
V=1.95	#use autodetection procedure for maximum number of cores from syscheck/xen-config
V=1.96	#removed additional explicit exit statements handled by errexit
V=1.97	#force DIST_VER 6.0 for prov VMs, corrected VM vs. HW sizing selection groups
V=1.98	#add PowerEdge_M610 to supported platforms
V=1.99	#auto-select SOE4.1 for the Xen profile
V=2.00	#Cisco UCS M3 support
V=2.01	#clear foreign configuration from the LSI controller
V=2.02	#new filesystem config for NOMS: /var/lib and /var/cache
V=2.03	#added explanation about $MEGACLI_DEV presence
V=2.04	#allow override of MACHINE (force 32-bit install on a 64-bit platform)
V=2.05	#default backup /srv size reduced to 2GiB - prefer backup.hq for VM backups
V=2.06	#corrected test for wiping block device which is not under hardware RAID
V=2.07	#updated Nomad resources to match syscheck
V=2.08	#auto-select SOE4.1 for the home profile
V=2.09	#added workaround for megacli sometimes returning code 254 despite success
V=2.10	#updated jessie debootstrap to version 1.0.56
V=2.11	#fix parsing of output from "udevadm info" that includes spaces
V=2.12	#export URL_BASE for use in stage2
V=2.13	#updated jessie debootstrap to version 1.0.59
V=2.14	#support for jessie 686 PAE kernels and 486 kernels
V=2.15	#use getopts for processing command line arguments
V=2.16	#PyGrub configuration
V=2.17	#kernel selection redesign
V=2.18	#firmware update for C220/C240 RAID controller
V=2.19	#fixed logic when checking VM architecture restrictions
V=2.20	#added a kernel for lenny/i486 bare hardware combination
V=2.21	#removed Sangoma hardware detection / profile override
V=2.22	#unified profile specification
V=2.23	#increased default memory size for LVS profile
V=2.24	#increased default memory size for speedtest profile
V=2.25	#allow automatic selection of default CPU count for VM without a profile
V=2.26	#allow automatic selection of default Debian version for system without a profile
V=2.27	#updated jessie debootstrap to version 1.0.60
V=2.28	#alternate namespace support for VMs
V=2.29	#restrict first HOSTNAME component to SOE4 standard pattern: alpha+digit*
V=2.30	#write soe-install option header comment to generated /etc/xen/auto/ config files
V=2.31	#spelling corrections
V=2.32	#HW_SERIAL: do not assume that first NIC name is eth0 (could be "enp2s3f0"...)
V=2.33	#/var/lib/dpkg may be present on sysresccd; use /var/lib/dpkg/status/status instead
V=2.34	#recognize 9650SE-4LP_DISK as a HW_RAID controller
V=2.35	#work around bad return code in gdisk 0.8.10
V=2.36	#reverted back temporary Debian mirror location redirection
V=2.37	#explicitly install aptitude (not default on Debian 8)
V=2.38	#explicitly choose sysvinit over systemd for Debian 8
V=2.39	#explicitly set Debian version 5 for bsc
V=2.40	#corrected syntax for package exclusions/inclusions for jessie
V=2.41	#new FW_Package_Build for C220 under Linux 3.16
V=2.42	#updated jessie debootstrap to version 1.0.67
V=2.43	#specify explicit data-offset for mdadm when supported
V=2.44	#download and extract megacli binary
V=2.45	#allow specification of software RAID level (special purposes)
V=2.46	#system serial number detection unified with Bcfg2
V=2.47	#update Xen profile to Debian 8
V=2.48	#support skipping disk verification (e.g. recently processed via ATA Secure Erase)
V=2.49	#Xen toolstack selection for Xen 4.4
V=2.50	#automatically run xen-config-upgrade under Xen 4.4
V=2.51	#Cisco UCS C240 M4 support
V=2.52	#FS_LIST specification expanded to include filesystem usage type
V=2.53	#increased number of inodes for Nomad /var/log
V=2.54	#correct conditional for xen-config-upgrade
V=2.55	#treat Xen HVM environment as domB for nested virtualization
V=2.56	#Cisco UCS C220 M4 support
V=2.57	#automatically change drive status JBOD->Unconfigured-Good on newer LSI controllers
V=2.58	#changed DMIDECODE_FILTER to awk so it returns 0 exit code even with empty output
V=2.59	#OLC VM deployment support; vcmts/vcm DPoG profiles deleted
V=2.60	#automatic deployment profiles for flow+flowcaster
V=2.61	#swapped order of flow & flowcaster in HOST_TYPE case due to flow* matching flowcaster -JB
V=2.62	#change debian-squeeze URL to Debian archive (LTS support ended 2016-02-29)
V=2.63	#allow the use of lower "Max Strip Size" on controllers with BBU- SSD+
V=2.64	#added MMODEL 867661X (IBM eserver xSeries 335)
V=2.65	#new profile for Linux Test Project
V=2.66	#debootstrap version update (jessie official)
V=2.67	#take into account value suffix when evaluating suitability of Max_Strip_Size
V=2.68	#added pump profile to ensure full SOE4 compatibility of the CDN project VMs -JB
V=2.69	#use SKIP_HUU flag to bypass LSI firmware package version check
V=2.70	#update filer profile to Debian 8
V=2.71	#update backup profile to Debian 8
V=2.72	#recognize LSI SAS MegaRAID based on module name, not on platform model
V=2.73	#add Dell PowerEdge R730 as a supported platform
V=2.74	#bypass cache when wiping volumes
V=2.75	#select auto/noauto for /boot based on HW_RAID
V=2.76	#check local repository for package downloads as well
V=2.77	#download 'megacli' package instead of cpio file
V=2.78	#replace Max_Strip_Size with Max_Stripe_Size (spelling error in LSI utility?)
V=2.79	#set MakeGood even on drives excluded via PDNum_FORCE
V=2.80	#add MMODEL ProLiant_DL320_G2
V=2.81	#fix SSD detection when lsblk returns nothing
V=2.82	#added sizing profile for radius
V=2.83	#support Debian 10 target
V=2.84	#select ext4 for Debian 8+ installs
V=2.85	#remove SPARC support
V=2.86	#remove Intel 80486 support
V=2.87	#fix /boot volume remount during bare hardware install
V=2.88	#add new profile for LSN
V=2.89	#reset permissions on /tmp to 755 to satisfy checkroot-bootclean.sh (tmpfs overlay is used)
V=2.90	#add new profile for RPKI
V=2.91	#increase RPKI profile root filesystem size
V=2.92	#fix D10 compatiblity: don't pre-load /dev/null /dev/console /etc/hosts
V=2.93	#update prov profile to D8
V=2.94	#revise namespace selection logic to allow foreign namespace even for native VMs (SOE upgrade)
V=2.95	#wait until tmpfs bind is unmounted to avoid resetting /tmp permission on dom0
V=2.96	#export NAMESPACE to stage2
V=2.97	#update radius profile to D8
V=2.98	#export VERBOSE mode to stage2
V=2.99	#redo the fix for 2.89 (reference "tmp" instead of "/tmp")
V=3.00	#add VIA EPIA EN hardware profile
V=3.01	#use 'grub-install' in preference to 'grub2-install' (forward compatibility)
V=3.02	#update prefix profile to D8
V=3.03	#support RAID-6 selection on LSI HW-RAID
V=3.04	#remove previous LVM more completely to avoid leftover /dev/vg/ directory
V=3.05	#add NAS VM profile
V=3.06	#remove profiles: fax, smtp, filer
V=3.07	#update mail profile to Debian 8
V=3.08	#add MMODEL UCSC-C220-M5SX
V=3.09	#remove uas module (prevents removal of usb_storage)
V=3.10	#remove LSI firmware package version check (checked later by raid-hw)
V=3.11	#specify explicit i386-pc target for GRUB_INSTALL (defaults to x86_64-efi if booted via UEFI)
V=3.12	#upgrade default Debian 10 VM volumes to 2GiB size
V=3.13	#update private mirror URL to reflect transition to master.clearcable.net
V=3.14	#update private mirror auto-download URL with 'contrib' path instead of 'main'
V=3.15	#add kernel selection for Debian 10 on MACHINE=i686
V=3.16	#recognize SSDs based on "Rotation Rate" SMART attribute
V=3.17	#place PV directly on the block device (no md) on single-disk systems
V=3.18	#remove 'acpitool' from default package install list (not used in Bcfg2)
V=3.19	#remove 'libc6-i686' from default package install list (will be added by Bcfg2 if needed)
V=3.20	#enhance 3.04 with forther clear using dmsetup
V=3.21	#increase default volume sizes for SMALL disk layout (extra space for Debian 10)
V=3.22	#overlay Xen's package archive in the target VM's $APT_ARCHIVES (instead of copying files)
V=3.23	#support PDNum=6 for hardware RAID-10 setup
V=3.24	#set explicit options for ext4 filesystems for Debian 8 (e.g. "metadata_csum" is invalid)
V=3.25	#initialize mkfs FEATURES and EXTENDED_OPTIONS for cases where they are left at default
V=3.26	#exclude syslog-ng from install due to buster repository problem (will be done via Bcfg2)
V=3.27	#increase root volume sizes for monitor SOE4.3 profile
V=3.28	#add MMODEL PowerEdge_R630
V=3.29	#add pvgrub instead of pygrub on SOE4.3+
V=3.30	#upgrade speedtest to SOE4.3 profile
V=3.31	#upgrade unprov to SOE4.2 profile
V=3.32	#add MMODEL ProLiant_DL380_Gen9
V=3.33	#replate package telnet-ssl with telnet (temporarily? not available in D10)
V=3.34	#add maildb profile
V=3.35	#add CDN server hosttypes
V=3.36	#add IDP server hosttype
V=3.37	#add netconf server hosttype
V=3.38	#upgrade backup to SOE4.3 profile and set /srv to 20G default
V=3.39	#upgrade monitor to SOE4.3 profile
V=3.40	#add NOMS SOE4.3 profile (name noms43)
V=3.41	#numerous modifications to remove legacy cruft (e.g. SOE4.1 Xen support)
V=3.42	#increase root space for NOMS 4.3 to 4GB
V=3.43	#add stats/cartograph VM profile
V=3.44	#set monitor profile to always use pygrub
V=3.45	#update nsX to use SOE4.3
V=3.46	#increase filesystem sizes for monitor 4.3
V=3.47	#update default Debian version to 10 (installs without a profile)
V=3.48	#add Roundcube (SOE4.3) profile
V=3.49	#add webX framework VMs
V=3.50	#add /run/udev bind mount for stage2
V=3.51	#increase default LV sizing for installs onto SMALL_DISK
V=3.52	#unmount /run/udev bind mount
V=3.53	#skip setting immutable flag on /run/udev
V=3.54	#allow installation onto NVMe device
V=3.55	#promote mail profile to Debian 10
V=3.56	#set executable flag by default on new VMs
V=3.57	#update kernel image selection
V=3.58	#pygrub for all proxy/webdb/web host types
V=3.59	#add 8GB /var/www volume for flowX VMs
V=3.60	#update RPKI profile to D10
V=3.61	#properly spec nomsdev/plain-noms names just in case
V=3.62	#update LSN profile to D10
V=3.63	#reformat SOE4 host type spec table without semicolons
V=3.64	#update NAS VM profile to D10
V=3.65	#synchronize profile filesystem sizes with filesystems 1.0.0 syscheck
V=3.66	#allow dashes in namespace names for sub-namespaces (e.g. ryu-cpe-prov1)
V=3.67	#remove all "localnet" (eth1 Xen, xenbr1 VM, etc.) from new systems
V=3.68	#add pygrub and increased var for RPKI profile (match syschecks)
V=3.69	#update prefix profile to D10
V=3.70	#remove grub-pc from initial install (see stage2)
V=3.71	#add pgdb VM profile with /srv
V=3.72	#relax Max_Stripe_Size restrictions
V=3.73	#add supported model SNS-3495-K9 (UCSC-C220-M3S)
V=3.74	#enable pygrub for nsX and lvsX
V=3.75	#enable write cache for the test RAID-0 MegaRAID array
V=3.76	#remove stale /dev/vg after initial LVM cleanup
V=3.77	#move GRUB install completely to stage2
V=3.78	#add platform-specific console settings
V=3.79	#remove /opt from nomsdb a la pgdb
V=3.80	#update debootstrap to the official Debian buster version (1.0.114)
V=3.81	#add gawk to the initial package list
V=3.82	#cleanup whitespace and shebang in the default Xen VM template
V=3.83	#select required base memory size based on OS version
V=3.84	#add VPN profile
V=3.85	#add portal42 profile
V=3.86	#add /srv volume for storing NFCT logs on LSN
V=3.87	#reorganize cartograph/datascout/pgdb definitions for correctness, added /opt to pgdb
V=3.88	#add workaround for curl 404's returning 0 with invalid cert archives
V=3.89	#support Debian 12 / Bookworm install target
V=3.90	#increase default provX memory to 4G, add fwprov profile
V=3.91	#add support for MMC media install device
V=3.92	#make "noms" default to noms43 going forward (SOE4.0 NOMS must be "noms40")
V=3.93	#increase RPKI profile var filesystem size
V=3.94	#add xz-utils for extract-vmlinux (PYGRUB)
V=3.95	#add rns profile (identical to ns)
V=3.96	#add news for the /srv parition on SW VMs (more inodes)
V=3.97	#configure swap on zram for Debian 8+
V=3.98	#remove unused references to FSTAB_ARCH
V=3.99	#select /tmp or /proc fstab entry based on OS version
V=3.100	#update jessie APT URL to Debian archive
V=3.101	#allow force-no-Pygrub for flowcasterX domB deploys
V=3.102	#adjust default bridge name for SOE5 HV
V=3.103	#support installation of SOE5 VMs
V=3.104	#update debootstrap version (add "+deb12u1") -JB
V=3.105	#set xtrace by default: ensures we have logging when things fail -JB

set -o errexit
set -o nounset
set -o xtrace
#set -o noexec #syntax testing

TMP=$(mktemp)
UUID=$(uuidgen)

# constants
declare -r -i KiB=$(( 2**10 ))
declare -r -i MiB=$(( 2**20 ))
declare -r -i GiB=$(( 2**30 ))
declare -r -i TiB=$(( 2**40 ))

APT_ARCHIVES='/var/cache/apt/archives'

# PACKAGES
PKG_INCLUDE="
debian-archive-keyring,
sudo,
locales,
debsums,deborphan,
bzip2,p7zip-full,sharutils,rsync,
less,tree,mmv,patch,file,bc,vim,pwgen,gawk,
lsof,psmisc,udev,
ssh,telnet,
tcpdump,netmask,bind9-host,
"
PKG_EXCLUDE="
tasksel,tasksel-data,
rsyslog,
vim-tiny,
libsasl2-2,libgnutls26,libconsole,
libgcrypt11,libtasn1-3,
libgpg-error0
"

function purge {
	rm --force --recursive "$1"
	mkdir "$1"
}

# SRC: /usr/local/share/sysfix/memory
function memory_round {
	local reported=$1

	actual=$(( $MiB ))
	i=0
	while [[ $actual -lt $reported ]]; do
		if [[ $i -lt 10 ]]; then
			actual=$((actual*2))
		else
			actual=$((actual+GiB))
		fi
		i=$((i+1))
	done
	echo $actual
}

VALID_OPTION_HELP='[-h] [-d 5|6|8|10|12] [-r 0|1|6] [-m machine_type] [-n namespace] [-v] [-g] hostname'
function usage_help() {
	echo "Usage: $0 $VALID_OPTION_HELP" 1>&2
	echo "Try the {-h} flag for more information." 1>&2
	exit 1
}

function usage_help_long() {
cat << EOF 1>&2
Usage: $0 $VALID_OPTION_HELP
Install a Clearcable SOE4 base Debian system.
The target may be a VM, dom0 (hypervisor) or domB (bare hardware).
Example: soe-install -d5 nomad2.example.com

General behaviour modifiers:
	-h		Display this help message
	-v		Enable verbose mode (executed commands are show)

Target system type selection:
	-d		Debian version (5=lenny, 6=squeeze, 8=jessie, 10=buster), default=6
	-g		PyGRUB mode for a VM (will have its own kernel)
	-G		Force no-PyGRUB mode for a domB with a PyGRUB domU profile
	-m		Machine type override: i586 i686 x86_64
	-r		RAID type override: 0 1 6
	-p		LVM PV override: name of PV in VG vg

Only certain machine type downgrades make sense, e.g. specifying "i586" on a "i686" virtual
environment to obtain a generic kernel that will work with non-PAE/non-CMOV hardware.

Namespace selection:
	-n		Use the specified keyword as an alternate namespace for a VM

This accommodates cases where a single dom0 hosts VMs that belong to a domain name different from
that of the dom0. For example, installing {dev.rafal.ca} on {xen1.clearcable.ca} may create a
conflict with {dev.clearcable.ca}. Pick a short prefix such as {ra} to create an alternate
namespace. The VM name and its storage resource names will be prefixed with the namespace
identifier, e.g. "ra-dev_root"

The FQDN "hostname" parameter is always required and needs to be pre-configured in DNS before
installation starts. For a VM, its short name will be derived from the first component of the FQDN
hostname, e.g. ns1.example.com -> ns1
EOF
	exit 1
}

SOE_INSTALL_API_REQ=3
if test ! -z "${SOE_INSTALL_API+xxx}"
then
	if test $SOE_INSTALL_API != $SOE_INSTALL_API_REQ
	then
		echo "ERROR: soe-install API version mismatch: expected $SOE_INSTALL_API_REQ, got $SOE_INSTALL_API" 1>&2
		exit 1
	fi
else
	echo "ERROR: unable to determine soe-install API version" 1>&2
	exit 1
fi

PYGRUB='yes'
FORCE_NO_PYGRUB='no'
NAMESPACE=''
VERBOSE=0
DEBIAN_VERSION_OVERRIDE=''
PV_ALLOC=''
while getopts "d:r:m:n:p:vgGh" opt; do
	case "${opt}" in
		d)
			d=${OPTARG}
			((d == 5 || d == 6 || d == 8 || d == 10 || d == 12)) || usage_help
			DEBIAN_VERSION_OVERRIDE=${OPTARG}
			;;
		r)
			r=${OPTARG}
			((r == 0 || r == 1 || r == 6)) || usage_help
			RAID_LEVEL=${OPTARG}
			;;
		v)
			VERBOSE=1
			set -o xtrace
			;;
		g)
			PYGRUB='yes'
			;;
		G)
			FORCE_NO_PYGRUB='yes'
			;;
		m)
			MACHINE=${OPTARG}	
			;;
		n)
			NAMESPACE=${OPTARG}	
			;;
		p)
			PV_ALLOC=${OPTARG}	
			;;
		h)
			usage_help_long
			;;
		*)
			usage_help
			;;
	esac
done
shift $((OPTIND-1))

if test ! -z "${1+xxx}"
then
	HOSTNAME=$1
else
	usage_help
fi

HOST_TYPE=${HOSTNAME%%.*}
if ! [[ "$HOST_TYPE" =~ ^[a-z]+[0-9]*$ ]]
then
	echo "ERROR: invalid HOST_TYPE {$HOST_TYPE} syntax (required alpha base with optional digits)" 1>&2
	exit 1
fi
# automatic resource sizing and OS selection
DEBIAN_VERSION=10
MEM_SIZE=''
VCPUS=''
FS_LIST=''
FS_LIST_ADD=''
case $HOST_TYPE in
	xen*)		DEBIAN_VERSION=12	MEM_SIZE=1024	VCPUS=1	FS_LIST='/:3 /var:2 /var/log:2:big /var/lib/xen:6:largefile4';;
	monitor)	DEBIAN_VERSION=10	MEM_SIZE=4096	VCPUS=8	FS_LIST='/:6 /var:12 /var/log:24:largefile' PYGRUB='yes';;
	backup*)	DEBIAN_VERSION=12	MEM_SIZE=2048	VCPUS=4	FS_LIST_ADD='/srv:20:news' PYGRUB='yes';;
	noms43|nomsdev|noms) \
				DEBIAN_VERSION=10	MEM_SIZE=8192	VCPUS=8	FS_LIST='/:4 /var:4	/var/log:6:largefile /opt:4 /srv:8:news' PYGRUB='yes';;
	nomsdb)		DEBIAN_VERSION=10	MEM_SIZE=16384	VCPUS=8	FS_LIST='/:4 /var:4	/var/log:6:largefile /opt:4 /srv:16' PYGRUB='yes';;
	noms40)		DEBIAN_VERSION=5	MEM_SIZE=4096	VCPUS=4	FS_LIST='/:2 /var:2 /var/log:6:largefile /var/lib:4 /var/cache:4';;
	stats|cartograph|datascout*) \
				DEBIAN_VERSION=10	MEM_SIZE=8192	VCPUS=8	FS_LIST='/:4 /var:4	/var/log:8:largefile /opt:4 /srv:8:news' PYGRUB='yes';;
	pgdb*)		DEBIAN_VERSION=10	MEM_SIZE=8192	VCPUS=8	FS_LIST='/:4 /var:4 /var/log:6:largefile /opt:4 /srv:16' PYGRUB='yes';;
	portal)	    DEBIAN_VERSION=10	MEM_SIZE=4096	VCPUS=4	FS_LIST='/:4 /var:4	/var/log:8:largefile /opt:4 /srv:8:news' PYGRUB='yes';;
	mail*)		DEBIAN_VERSION=10	MEM_SIZE=2048	VCPUS=2	FS_LIST='/:2 /var:8 /var/log:6:largefile';;
	prov*)		DEBIAN_VERSION=8	MEM_SIZE=4096	VCPUS=2	FS_LIST='/:2 /var:2 /var/log:6:largefile /srv:6';;
	fwprov)		DEBIAN_VERSION=8	MEM_SIZE=1024	VCPUS=2	FS_LIST='/:2 /var:2 /var/log:6:largefile /srv:6';;
	ns*)		DEBIAN_VERSION=10	MEM_SIZE=512	VCPUS=1	FS_LIST='/:2 /var:2 /var/log:6:largefile' PYGRUB='yes';;
	rns*)		DEBIAN_VERSION=10	MEM_SIZE=512	VCPUS=1	FS_LIST='/:2 /var:2 /var/log:6:largefile' PYGRUB='yes';;
	prefix*)	DEBIAN_VERSION=10	MEM_SIZE=4096	VCPUS=4	FS_LIST='/:2 /var:4 /var/log:6:largefile' PYGRUB='yes';;
	speedtest*)	DEBIAN_VERSION=10	MEM_SIZE=256	VCPUS=1;;
	unprov*)	DEBIAN_VERSION=8	MEM_SIZE=256	VCPUS=1;;
	#nomad*)		DEBIAN_VERSION=5	MEM_SIZE=1024	VCPUS=2	FS_LIST='/:2 /var:2 /var/log:4 /var/spool:4';;
	nomad*)		DEBIAN_VERSION=10	MEM_SIZE=4096	VCPUS=4	FS_LIST='/:4 /var:4 /var/log:6:largefile /srv:8:news' PYGRUB='yes';;
	home*)		DEBIAN_VERSION=6	MEM_SIZE=256	VCPUS=1;;
	OLC*)		DEBIAN_VERSION=6	MEM_SIZE=2048	VCPUS=2;;
	lvs*)		DEBIAN_VERSION=10	MEM_SIZE=512	VCPUS=1 PYGRUB='yes';;
	vpn)		DEBIAN_VERSION=10	MEM_SIZE=512	VCPUs=1 PYGRUB='yes';;
	radius*)	DEBIAN_VERSION=8	MEM_SIZE=256	VCPUS=1;;
	lsn*)		DEBIAN_VERSION=10	MEM_SIZE=512	VCPUS=2	FS_LIST='/:2 /var:2 /var/log:2:largefile /srv:10:largefile' PYGRUB='yes';;
	rpki)		DEBIAN_VERSION=12	MEM_SIZE=4096	VCPUS=2	FS_LIST='/:2 /var:8:news /var/log:2:big' PYGRUB='yes';;
	nas*)		DEBIAN_VERSION=10	MEM_SIZE=2048	VCPUS=2	PYGRUB='yes';;
	idp*)		DEBIAN_VERSION=10	MEM_SIZE=1024	VCPUS=2	FS_LIST='/:2 /var:2 /var/log:2:largefile /opt:4 /srv:4';;
	netconf*)	DEBIAN_VERSION=10	MEM_SIZE=1024	VCPUs=2	FS_LIST='/:2 /var:2 /var/log:2:largefile /opt:4 /srv:4';;
	roundcube)	DEBIAN_VERSION=10	MEM_SIZE=1024	VCPUs=2;;
	rtbh*)		DEBIAN_VERSION=10	MEM_SIZE=24576	VCPUs=8 FS_LIST='/:6 /var:4 /var/log:6:largefile /srv:32' PYGRUB='yes';;
	# CDN hosttypes
	pump*)		DEBIAN_VERSION=10	MEM_SIZE=32786	VCPUS=12	FS_LIST='/:8 /var:4 /var/log:8:largefile' PYGRUB='yes';;
	flowcaster*)DEBIAN_VERSION=10	MEM_SIZE=32786	VCPUS=12	FS_LIST='/:8 /var:4 /var/log:8:largefile' PYGRUB='yes';;
	flow*)		DEBIAN_VERSION=10	MEM_SIZE=4096	VCPUS=2	FS_LIST='/:8 /var:4 /var/www:8 /var/log:8:largefile' PYGRUB='yes';;
	# Load-balanced/redundant website hosting hosttypes
	proxy*)		DEBIAN_VERSION=10	MEM_SIZE=512	VCPUS=2	FS_LIST='/:2 /var:2	/var/log:4:largefile' PYGRUB='yes';;
	webdb*)		DEBIAN_VERSION=10	MEM_SIZE=512	VCPUS=2	FS_LIST='/:2 /var:2	/var/log:4:largefile /srv:4' PYGRUB='yes';;
	web*)		DEBIAN_VERSION=10	MEM_SIZE=512	VCPUS=2	FS_LIST='/:2 /var:2	/var/log:4:largefile /srv:8' PYGRUB='yes';;
esac
if [[ -n "$DEBIAN_VERSION_OVERRIDE" ]]; then
	DEBIAN_VERSION=$DEBIAN_VERSION_OVERRIDE
fi

case $FORCE_NO_PYGRUB in
	yes)
		PYGRUB='no'
		;;
	*)
		PYGRUB=$PYGRUB
		;;
esac

# OS distribution settings
MIRROR_ARCHIVE='http://archive.debian.org/debian'
MIRROR_MAIN='http://debian.mirror.rafal.ca/debian'
MIRROR_PRIVATE='http://debian.clearcable.net/debian'
DEBOOTSTRAP='debootstrap'
MDADM='mdadm'

case $DEBIAN_VERSION in
	5)
		DIST_VER=5
		DIST_NAME='lenny'
		MIRROR_PUBLIC=$MIRROR_ARCHIVE
		;;
	6)
		DIST_VER=6
		DIST_NAME='squeeze'
		MIRROR_PUBLIC=$MIRROR_ARCHIVE
		;;
	8)
		DIST_VER=8
		DIST_NAME='jessie'
		MIRROR_PUBLIC=$MIRROR_ARCHIVE
		PKG_INCLUDE="$PKG_INCLUDE,sysvinit-core,dnsutils"
		PKG_EXCLUDE="$PKG_EXCLUDE,systemd-sysv"
		;;
	10)
		DIST_VER=10
		DIST_NAME='buster'
		MIRROR_PUBLIC=$MIRROR_MAIN
		PKG_INCLUDE="$PKG_INCLUDE,sysvinit-core,gnupg,dnsutils"
		PKG_EXCLUDE="$PKG_EXCLUDE,systemd-sysv"
		;;
	12)
		DIST_VER=12
		DIST_NAME='bookworm'
		MIRROR_PUBLIC=$MIRROR_MAIN
		PKG_INCLUDE="$PKG_INCLUDE,gnupg,dbus,systemd,systemd-resolved,systemd-sysv,systemd-zram-generator,zstd,zsh,bind9-dnsutils"
		PKG_EXCLUDE="$PKG_EXCLUDE,ifupdown,apt-utils,cron,exim4-base,fdisk,isc-dhcp-client"
		;;
	*)
		echo ERROR: unknown Debian version 1>&2
		exit
		;;
esac
if [[ $DIST_VER -ge 12 ]]; then
	declare -r -i DIST_SOE_VER=5
else
	declare -r -i DIST_SOE_VER=4
fi

OS_DESCRIPTION="Debian GNU/Linux $DIST_VER ($DIST_NAME), SOE$DIST_SOE_VER"

if test $(id --user) -ne 0
then
	echo ERROR: must be root to run $0 1>&2
	exit
fi

# CPU architecture selection
if test ! -z "${MACHINE+xxx}"
then
	: #overridden from command line
else
	MACHINE=$(uname --machine)
fi

test -z "${VERIFY_DISK+xxx}" && VERIFY_DISK=1

case "$MACHINE" in
	i586)
		ARCH='i386'
		PKG_ARCH=''
		case $DEBIAN_VERSION in
			5|6) KERNEL='linux-image-486';;
			8)	KERNEL='linux-image-586';;
			10)	KERNEL='linux-image-686';;
		esac
		;;
	i686)
		ARCH='i386'
		PKG_ARCH='ipmitool'
		case $DEBIAN_VERSION in
			5)	KERNEL='linux-image-686';;
			6)	KERNEL='linux-image-xen-686';;
			8|10)	KERNEL='linux-image-686-pae';;
		esac
		;;
	x86_64)
		ARCH='amd64'
		PKG_ARCH='ipmitool'
		case $DEBIAN_VERSION in
			5)	KERNEL='linux-image-2.6.32-bpo.5-xen-amd64';;
			6)	KERNEL='linux-image-xen-amd64';;
			8)	KERNEL='linux-image-amd64';;
			10|12)
				case $PYGRUB in
					yes)
						KERNEL='linux-image-cloud-amd64'
						PKG_INCLUDE="$PKG_INCLUDE,binutils,xz-utils,lz4"
						;;
					*)	KERNEL='linux-image-amd64';;
				esac
				;;
		esac
		;;
	*)
		echo "unsupported machine architecture {$MACHINE}" 1>&2
		echo 'supported architectures are: i586 i686 x86_64' 1>&2
		exit 1
		;;
esac

SYSTEM_SERIAL_NUMBER=$(. $INSTALLER_DIR/serial_number)

# get the machine model
DMIDECODE_FILTER='awk {if($0!~/^[#]/){print}}'
SYSTEM_MANUFACTURER=$(dmidecode --string system-manufacturer|$DMIDECODE_FILTER)
SYSTEM_PRODUCT_NAME=$(dmidecode --string system-product-name|$DMIDECODE_FILTER)
SYSTEM_PRODUCT_NAME=$(echo $SYSTEM_PRODUCT_NAME|tr ' ' '_') # discarded trailing space
SYSTEM_VERSION=$(dmidecode --string system-version|$DMIDECODE_FILTER)
if test -z "$SYSTEM_PRODUCT_NAME" -o "$SYSTEM_PRODUCT_NAME" = 'Super_Server'
then
	SYSTEM_MANUFACTURER=$(dmidecode --string baseboard-manufacturer|$DMIDECODE_FILTER)
	SYSTEM_PRODUCT_NAME=$(dmidecode --string baseboard-product-name|$DMIDECODE_FILTER)
	SYSTEM_PRODUCT_NAME=$(echo $SYSTEM_PRODUCT_NAME|tr ' ' '_') # discarded trailing space
	SYSTEM_VERSION=$(dmidecode --string baseboard-version|$DMIDECODE_FILTER)
fi

MMODEL="$SYSTEM_PRODUCT_NAME"
if test ! -z "${HW_SERIAL+xxx}"
then
	: #overridden from environment
else
	HW_SERIAL="$SYSTEM_SERIAL_NUMBER"
fi
test -n "$HW_SERIAL"

if test -d '/etc/xen'
then
	# Hypervisor
	VM='yes'
	TARGET="/tmp/target-$UUID"
	MMODEL='Xen'

	source '/etc/os-release'
	if [[ "$VERSION_ID" -ge 12 ]]; then
		declare -r -i HV_SOE_VERSION=5
		declare -r HV_BRIDGE_DEFAULT='br0'
	elif [[ "$VERSION_ID" -eq 10 ]]; then
		declare -r -i HV_SOE_VERSION=4
		declare -r HV_BRIDGE_DEFAULT='xenbr0'
	else
		echo "WARNING: host OS version too old; unsupported."
		echo "This will likely fail."
		echo "Press ENTER to continue"
		read JUNK
	fi

	# verify architecture restrictions
	if ! test $ARCH = 'i386' -o $ARCH = 'amd64'
	then
		echo "VM architecture can only be i386 or amd64, not {$ARCH}" 2>&1
		exit 1
	fi

	if test $PYGRUB = 'no' -a $ARCH != 'amd64'
	then
		echo "paravirtualized VM architecture can only be amd64, not {$ARCH}" 2>&1
		exit 1
	fi
else
	# bare hardware
	VM='no'
	PYGRUB='no'
	TARGET='/target'

	# double check that this is not a live system!!!
	test ! -s '/etc/debian_version'
	test ! -s '/var/lib/dpkg/status'
	test ! -s '/etc/bcfg2.crt'

	if test -n "$NAMESPACE"
	then
		echo 'namespace selection is only applicable to virtual machines; unsetting' 2>&1
		exit 1
	fi
fi

# choose model-specific settings
CONSOLE_PORT='ttyS1'
CONSOLE_SPEED='115200'
case "$MMODEL" in
	R200-1120402|R200-1120402W|R210-2121605W|UCSC-C220-M3S|SNS-3495-K9|UCSC-C240-M3S|UCSC-C240-M4SX|UCSC-C220-M4S|UCSC-C220-M5SX)
	# Cisco UCS
		:;;

	PowerEdge_2950|PowerEdge_M610|PowerEdge_R730|PowerEdge_R630)
	# Dell
		:;;

	SUN_FIRE_X4170_SERVER|SUN_FIRE_X4170_M2_SERVER)
	# Sun x86
		:;;

	CLE266-8235)
	# VIA EPIA M-II
		:;;

	CN700-8237R)
	# VIA EPIA EN
		:;;

	X7SPA-HF|X7DB8|X7DWU|X10DRL-CT|H8DGU)
	# Super Micro SuperServer
		:;;

	DH57DD|DG43GT|NUC8i7BEH)
	# Intel desktop
		:;;

	Aspire_E500)
	# ACER desktop
		:;;
	
	OptiPlex_GX520)
	# Dell desktop
		:;;
	
	81042EU)
	# IBM desktop
		:;;

	*867661X*)
	# IBM server
		:;;

	APU2|apu2|apu4)
	# PC Engines
		CONSOLE_PORT='ttyS0'
		CONSOLE_SPEED='115200'
		:;;

	ProLiant_DL320_G2|ProLiant_DL380_Gen9)
	# HP server
		:;;
	
	Virtual_Machine|VirtualBox|HVM_domU|VMware_Virtual_Platform)
	# Microsoft Windows Virtual PC
	# Sun Microsystem VirtualBox
	# Xen Hypervisor HVM
	# VMware Virtualization Software
		:;;

	Xen)
		:;;

	*)
		MMODEL="$MMODEL[unknown]"
		cat << EOF
WARNING: unknown hardware type

This MMODEL $MMODEL is not in the database.

Hardware-specific features of this platform may be unsupported by Bcfg2.

Press ENTER to continue setup or Ctrl-C to abort.
EOF
		read JUNK
		;;
esac

echo "auto-detected ARCH:$ARCH MACHINE:$MACHINE MMODEL:$MMODEL HW_SERIAL:$HW_SERIAL KERNEL:$KERNEL"

# mandatory Bcfg2 certificate
BCFG2_CERT_URL="$URL_BASE/installer/bcfg2_cert"
if ! $CURL --output $INSTALLER_DIR/bcfg2_cert.tar.bz2 $BCFG2_CERT_URL/$HOSTNAME.tar.bz2; then
	echo "unable to download Bcfg2 certificate for $HOSTNAME from $BCFG2_CERT_URL" 1>&2
	exit 1
fi
if ! tar -tjf $INSTALLER_DIR/bcfg2_cert.tar.bz2 &>/dev/null; then
	echo "downloaded Bcfg2 certificate archive is corrupt/invalid (cert not generated?)" 1>&2
	exit 1
fi

# further package selection (bare hardware/VM specific)
if test $VM = 'yes'
then
	PKG_INCLUDE="$PKG_INCLUDE"
	PKG_EXCLUDE="$PKG_EXCLUDE,
dhcp3-client,dhcp3-common,
dmidecode,module-init-tools,
iptables,
"
else
	PKG_INCLUDE="$PKG_INCLUDE,
$PKG_ARCH,
ntp,watchdog,
ethtool,vlan,bridge-utils,
mdadm,lvm2,
busybox,dosfstools,squashfs-tools,
picocom,pciutils,usbutils,smartmontools,sg3-utils,
"
fi

# common definitions
VG=vg
FSTAB='# /etc/fstab: static file system information.
#
#<file system>	<mount point>	<type>		<options>	<dump>	<pass>
#'

if [[ $DIST_VER -le 6 ]]; then
	FSTAB="$FSTAB
tmpfs		/tmp		tmpfs		defaults"
elif [[ $DIST_VER -ge 8 ]]; then
	FSTAB="$FSTAB
proc		/proc		proc		hidepid=2,gid=4"
fi

function blockdev_verify_or_wipe {
DEV=$1
DEV_SIZE=$(blockdev --getsize64 $DEV)

# calculate the highest possible block size
# to use with this device
REMAINDER=1
i=16
while test $REMAINDER -gt 0
do
	i=$[ $i - 1 ]
	test $i -ge 0
	BLOCK_SIZE=$[ 2**9 * 2 ** $i ]
	BLOCK_COUNT=$[ $DEV_SIZE / $BLOCK_SIZE ]
	REMAINDER=$[ $DEV_SIZE - $BLOCK_COUNT * $BLOCK_SIZE ]
done

# make sure the device is filled with NULLs
# VM: security precaution for reusing empty space
# HW: media test
test $[ $BLOCK_COUNT * $BLOCK_SIZE ] -eq $DEV_SIZE
test -n "${SKIP_BLOCKTEST+xxx}" && return
echo "checking if $DEV ($DEV_SIZE bytes) is NULL..."
if ! ionice -c3 cmp --silent --bytes $DEV_SIZE /dev/zero $DEV
then
	echo -n "$DEV is not NULL, wiping $BLOCK_COUNT blocks of $BLOCK_SIZE bytes..."
	ionice -c3 dd if=/dev/zero of=$DEV bs=$BLOCK_SIZE count=$BLOCK_COUNT oflag=direct 2>/dev/null
	echo 'done.'
fi
}

if test $VM = 'no'
then

# disable swap
# (swap locks the disk's partition table)
swapoff --all --verbose

# unmount target filesystems
umount /target/*/* 2>/dev/null || true
umount /target/*   2>/dev/null || true
umount /target     2>/dev/null || true
umount /boot       2>/dev/null || true

# disable previous LVM (if any)
vgchange --available n
for VG in $(vgs --noheadings --options vg_name); do
	vgremove --force $VG
done
for PV in $(pvs --noheadings --options pv_name); do
	pvremove $PV
done
dmsetup remove_all
if [[ -d /dev/vg ]]; then
	rm --one-file-system --recursive /dev/vg
fi

# shutdown all active software RAID arrays
SYS_BLOCK='/sys/block'
cd $SYS_BLOCK
for MD in md*; do
	if [[ -b /dev/$MD ]]; then
		cd "$SYS_BLOCK/$MD/slaves"
		SLAVES=$(ls)

		$MDADM --stop /dev/$MD

		for SLAVE in $SLAVES; do
			$MDADM --zero-superblock --force /dev/$SLAVE
			dd if=/dev/zero of=/dev/$SLAVE bs=1M count=8 2>/dev/null
		done
	fi
done
cd /

# enumerate fixed hard drives
SMALL_DISK=0	# assume big disks by default
HDD_SIZE_SMALL=30	# GiB

function megacli_waitfor_device {
	local getsize64
	local i=1
	while test ! -b $MEGACLI_DEV
	do
		sleep $i
		i=$[$i*2]
	done
	getsize64=$(blockdev --getsize64 $MEGACLI_DEV)
	test $getsize64 -gt 0
	Vendor=$(sginfo -X $MEGACLI_DEV|awk '{if(NR==2){print}}')
	test $Vendor = 'LSI' -o $Vendor = 'Cisco' -o $Vendor = 'DELL'
}

function download {
	PACKAGE=$1
	VERSION=$2
	if test -z ${3+xxx}
	then
		ARCH_PKG=$ARCH
	else
		ARCH_PKG=$3
	fi

	BASE=${PACKAGE%-udeb}
	if test $BASE = $PACKAGE
	then
		EXT='deb'
	else
		EXT='udeb'
	fi

	cd '/usr/local'
	purge 'download'
	cd 'download'
	DOWNLOAD_PATH="${PACKAGE:0:1}/${BASE}/${PACKAGE}_${VERSION}_${ARCH_PKG}.${EXT}"
	if ! wget "$MIRROR_MAIN/pool/main/$DOWNLOAD_PATH"
	then
		wget "$MIRROR_PRIVATE/pool/contrib/$DOWNLOAD_PATH"
	fi

	ar x *.${EXT}
	cd ..
	if test -s 'download/data.tar.gz'
	then
		DATA_ARCHIVE='download/data.tar.gz'
	elif test -s 'download/data.tar.xz'
	then
		DATA_ARCHIVE='download/data.tar.xz'
	else
		echo DATA_ARCHIVE not found
		exit 1
	fi
	tar --auto-compress --strip-components=0 --extract --file $DATA_ARCHIVE
	tar --auto-compress --strip-components=2 --extract --file $DATA_ARCHIVE './usr/' || true
	echo "downloaded and extracted $PACKAGE $VERSION $ARCH_PKG"
}

# configure/create hardware RAID if applicable
if [[ -d /sys/module/megaraid_sas ]]; then
	RAID_TYPE=1 #'hardware'

	download 'megacli' '5.00.12-1'
	#download 'megacli' '8.07.14-1'

	# hardcoded settings
	ADAPTER=0	# single controller
	MEGACLI_DEV='/dev/sda'	# single virtual drive
	if [[ -d /sys/module/uas ]]; then
		modprobe --verbose --remove uas
	fi
	if [[ -d /sys/module/usb_storage ]]; then
		modprobe --verbose --remove usb_storage
	fi

	# report current firmware version
	FW_Package_Build=$(megacli -AdpAllInfo -a$ADAPTER|awk -F' *: *' '{if ($1=="FW Package Build"){print $2}}')
	echo "LSI FW_Package_Build $FW_Package_Build"

	# set Factory Default for all controllers
	megacli -AdpFacDefSet -aALL
	megacli -AdpAllinfo -aALL

	# Clear Configuration and delete all virtual drives
	megacli -CfgClr -aALL
	megacli -CfgForeign -Clear -aALL
	megacli -CfgFreeSpaceInfo -aALL

	PDNum_megacli=$(megacli -PDGetNum -a$ADAPTER | grep "Number of Physical Drives" | sed "s/Number of Physical Drives on Adapter $ADAPTER: //" | tr -d ' ')
	test -n "$PDNum_megacli"

	Max_Spans_Per_VD=$(megacli -AdpAllinfo -aALL|awk -F' +: ' '{if ($1=="Max Spans Per VD"){sub(" +$","",$2);print $2}}')
	Max_Stripe_Size=$(megacli -AdpAllinfo -aALL|awk -F' +: ' '{
if ($1!="Max Stripe Size")
	next
split($2,MaxStripeSize,"[[:space:]]")
value=MaxStripeSize[1]
suffix=MaxStripeSize[2]
multiplier=\
	(suffix=="KB")?2**00:
	(suffix=="MB")?2**10:
	(suffix=="GB")?2**20:
	0
print value*multiplier
exit
}')
	Solid_State_Device_ALL=$(megacli -PDList -aALL|awk -F': ' 'BEGIN{ssd=1}{if ($1=="Media Type"){if ($2!="Solid State Device"){ssd=0}}}END{print ssd}')

	# display and collect physical drive list
	megacli -PDList -aALL | grep --extended-regexp "^Firmware state:"
	declare -a PDList
	PDList=($(megacli -PDList -a0|grep --extended-regex "(Enclosure Device ID|Slot Number):"|sed 's/.*: //'|awk '{ if (NR%2) {Enclosure=$1} else { print Enclosure ":" $1 } }'))
	test $PDNum_megacli -eq ${#PDList[*]}

	# this will include all drives and may be longer than PDList_RAID0 if constrained by PDNum_FORCE
	PDList_MakeGood=(${PDList[@]})

	if [[ -n "${PDNum_FORCE+xxx}" ]]; then
		for i in $(seq $PDNum_FORCE ${#PDList[*]}); do
			unset PDList[$i]
		done
	fi

	PDNum=${#PDList[*]}
	echo PDList: ${PDList[*]}
	echo PDNum: $PDNum
	echo PDList_MakeGood: ${PDList_MakeGood[*]}

	# do NOT enable DirectPdMapping on the C210:
	# megacli -DirectPdMapping -Dsply -aALL
	# the controllers firmware will not initialize:
	# >Attached Enclosure doesn't support in controller's Direct mapping mode
	# >Please contact your system support.
	# >System has halted due to unsupported configuration.
	# a tested fix involves:
	# * disconnecting SAS cables from the controller
	# * booting System Rescue CD and issuing the -Dsbl command

	PDList_RAID0=$(printf ",%s" "${PDList[@]}")
	PDList_RAID0="[${PDList_RAID0:1}]"

	i=0
	PDList_RAID1=''
	PDList_RAID6=''
	PDList_SPARE=''
	MIRROR_COUNT=$[ ${#PDList[*]} / 2 - 1 ]
	while [[ $i -le $MIRROR_COUNT ]]; do
		c1=$[2*$i]
		c2=$[2*$i+1]
		if [[ $PDNum -ge 8 && $i -eq $MIRROR_COUNT ]]; then
			PDList_SPARE="[${PDList[$c1]},${PDList[$c2]}]"
		else
			PDList_RAID1="$PDList_RAID1 -Array$i[${PDList[$c1]},${PDList[$c2]}]"
			PDList_RAID6="$PDList_RAID6,${PDList[$c1]},${PDList[$c2]}"
		fi
		i=$[$i+1]
	done
	PDList_RAID6="[${PDList_RAID6:1}]"
	echo "0->$PDList_RAID0"
	echo "1->$PDList_RAID1"
	echo "6->$PDList_RAID6"

	# ensure the configuration is supported
	case $PDNum in
		2)
			HW_RAID_LEVEL=1
			HW_RAID_TYPE="Ld"
			PDList_FINAL=$PDList_RAID0
			;;
		4|6|8|12|16|24)
			HW_RAID_LEVEL=10
			HW_RAID_TYPE="Span"
			PDList_FINAL=$PDList_RAID1
			if [[ -n "${RAID_LEVEL+xxx}" ]];then
				if [[ $RAID_LEVEL -eq 6 ]]; then
					HW_RAID_LEVEL=6
					HW_RAID_TYPE="Ld"
					PDList_FINAL=$PDList_RAID6
				fi
			fi
			;;
		*) echo unsupported PDNum $PDNum; exit 1;;
	esac

	# ensure that the controller capacities are not exceeded
	Max_Spans_Per_VD_REQ=$[ $PDNum / 2 ]
	if [[ $HW_RAID_TYPE = "Span" ]]; then
		if [[ $Max_Spans_Per_VD -lt $Max_Spans_Per_VD_REQ ]]; then
			echo "controller supports only $Max_Spans_Per_VD Spans_Per_VD but $Max_Spans_Per_VD_REQ are required for PDNum $PDNum in RAID-10" 1>&2
			echo "remove enough drives for the initial install and add them to a second VD later" 1>&2
			echo "or use PDNum_FORCE=xx to restrict number of drives (typically 12)" 1>&2
			exit 1
		else
			echo "Max_Spans_Per_VD: $Max_Spans_Per_VD"
		fi
	fi

	# validate Max_Stripe_Size
	if [[ $Max_Stripe_Size -eq 1024 ]]; then
		echo "Max_Stripe_Size: $Max_Stripe_Size (OK for HDD/SSD)"
	elif [[ $Max_Stripe_Size -eq 64 ]]; then
		if [[ $Solid_State_Device_ALL -gt 0 ]]; then
			echo "Max_Stripe_Size: $Max_Stripe_Size (OK for SSD)"
		else
			echo "Max_Stripe_Size: $Max_Stripe_Size (sub-optimal for HDDs)"
		fi
	else
		echo "unrecognized Max_Stripe_Size=$Max_Stripe_Size" 1>&2
		exit 1
	fi

	# set the firmware state on ALL available physical drives (even excluded ones)
	# to {Unconfigured-Good} since they may default to {JBOD}
	# the command will fail if any of the drives are already set to the target state
	# therefore set each drive individually and ignore errors
	echo -n "PDMakeGood: "
	for PDMakeGood in ${PDList_MakeGood[*]}; do
		if megacli -PDMakeGood -PhysDrv"[$PDMakeGood]" -Force -a$ADAPTER >/dev/null; then
			echo -n "."
		else
			echo -n "x"
		fi
	done
	echo

	# ensure that the LV device path is unassigned
	sleep 1
	if [[ -b $MEGACLI_DEV ]]; then
		sginfo $MEGACLI_DEV
		echo "$MEGACLI_DEV must not exist: reserved for hardware RAID (disconnect FlexFlash)"
		exit 2
	fi

	function megacli254 {
	if megacli $@; then
		echo array created OK
	else
		megacli_status=$?
		if [[ $megacli_status -eq 254 ]]; then
			# as per code comment in enterprise-storage-os tui/megaraid.c:
			# /* MegaCLI gives a exit status of 254 when using CfgLdAdd
			# * and it successfully created the volume; it gives
			# * an error about 'proc_add_new_ld: scandir failed' */
			echo array created successfully with status $megacli_status
			return 0
		else
			return $megacli_status
		fi
	fi	
	}

	# TESTING: create a RAID-0 array
	megacli254 -CfgLdAdd -r0 $PDList_RAID0 -strpsz$Max_Stripe_Size -a$ADAPTER
	megacli -LDSetProp -EnDskCache -L0 -a$ADAPTER
	megacli -LDInfo -Lall -aALL
	megacli_waitfor_device
	test $VERIFY_DISK -gt 0 && blockdev_verify_or_wipe $MEGACLI_DEV
	# remove the temporary configuration
	megacli -CfgClr -aALL

	# PRODUCTION: create a production array
	megacli254 -Cfg${HW_RAID_TYPE}Add -r$HW_RAID_LEVEL $PDList_FINAL -strpsz$Max_Stripe_Size -a$ADAPTER
	megacli -LDInfo -Lall -aALL
	megacli_waitfor_device

	# set the boot drive
	megacli -AdpBootDrive -Set -L0 -aALL
	megacli -AdpBootDrive -Get -aALL

	# add global hot spares
	if [[ -n "$PDList_SPARE" ]]; then
		megacli -PDHSP -Set -PhysDrv"$PDList_SPARE" -a$ADAPTER
	fi
	
	megacli -PDList -aALL | grep --extended-regexp "^Firmware state:"
else
	RAID_TYPE=0 #'none'
fi # megaraid_sas hardware RAID setup

HD0=''
HD1=''
DEV_HDD=$(echo /dev/sd? /dev/xvd? /dev/nvme0n1 /dev/mmcblk0)
for DEV in $DEV_HDD; do
	test -b "$DEV" || continue

	#https://bugzilla.redhat.com/show_bug.cgi?id=644330
	eval $(udevadm info --name=$DEV --query=property --export|
	tr ' ' '_'|
	grep --extended-regexp --invert-match '^DEVLINKS=')
	test $DEVNAME = ${DEVNAME#/dev/xvd} || ID_VENDOR='Xen'

	# defaults; some virtual drivers do not provide all fields
	test -z "${ID_VENDOR+xxx}" && ID_VENDOR='unknown'
	test -z "${ID_BUS+xxx}"    && ID_BUS='unknown'
	test -z "${ID_MODEL+xxx}"  && ID_MODEL='unknown'

	if [[ $ID_BUS = 'usb' ]]; then
		echo "INFO: skipped removable disk $DEVNAME on $ID_BUS"
		continue
	fi

	# recognize additional HW RAID controllers
	case $ID_MODEL in
		VBOX_HARDDISK) RAID_TYPE=1;;
	esac
	case $ID_VENDOR in
		#virtual disk backed by hardware RAID
		VMware|Xen) RAID_TYPE=1;;
	esac

	SIZE=$(blockdev --getsize64 $DEV)
	SIZE_GiB=$(( $SIZE / $GiB ))

	if [[ $SIZE_GiB -lt $HDD_SIZE_SMALL ]]; then
		SMALL_DISK=1
	fi

	ROTATION_RATE=$(smartctl --info $DEV|awk -F':[[:space:]]*' '{if ($1=="Rotation Rate"){print $2}}')
	if [[ "$ROTATION_RATE" = 'Solid State Device' ]]; then
		SSD_DISK=1
		DISC_GRAN=$(lsblk --discard --raw --scsi --noheadings $DEV|awk '{print $3}')
		if [[ "$DISC_GRAN" = '0B' || -z "$DISC_GRAN" ]]; then
			SSD_DISK_TRIM=0
		else
			SSD_DISK_TRIM=1
		fi
	else
		SSD_DISK=0
		SSD_DISK_TRIM=0
	fi

	echo "found a fixed disk $DEVNAME on bus $ID_BUS of size $SIZE_GiB GiB, isSMALL=$SMALL_DISK, RAID_TYPE=$RAID_TYPE, isSSD=$SSD_DISK"
	if [[ $SSD_DISK -gt 0 && $SSD_DISK_TRIM -gt 0 ]]; then
		echo 'ATA Secure Erase'
		echo 'https://ata.wiki.kernel.org/index.php/ATA_Secure_Erase'
		hdparm --user-master u --security-set-pass Eins $DEV
		hdparm --user-master u --security-erase Eins $DEV
	fi
	if [[ $RAID_TYPE -eq 0 && $VERIFY_DISK -gt 0 ]]; then
		blockdev_verify_or_wipe $DEVNAME
	fi

	if [[ -z "$HD0" ]]; then
		echo "HD0 registered: $DEVNAME ${SIZE_GiB}GiB"
		HD0=$DEVNAME
		HD0_SIZE=$SIZE_GiB
	else
		if [[ -z "$HD1" ]]; then
			echo "HD1 registered: $DEVNAME ${SIZE_GiB}GiB"
			HD1=$DEVNAME
			HD1_SIZE=$SIZE_GiB

			if [[ $HD0_SIZE -gt $HD1_SIZE ]]; then
				echo 'ERROR: (sysconfig) smallest drive is not the first drive (HD0)' 2>&1
				exit 2
			fi
		else
			echo 'Unused disk.'
		fi
	fi
done

# exit if no disks detected
test -n "$HD0"

if [[ $RAID_TYPE -eq 0 && -n "$HD1" ]]; then
	RAID_TYPE=2 #'software'
fi

# binary executable updates
umount /usr/local 2>/dev/null || true
if ! command -v ar; then
	ln --symbolic /bin/busybox /usr/bin/ar
fi


# debootstrap
DEBOOTSTRAP_VERSION='1.0.128+nmu2+deb12u1'	# bookworm
download 'debootstrap-udeb' $DEBOOTSTRAP_VERSION 'all'
purge /debootstrap
mv sbin/debootstrap /debootstrap
mv share/debootstrap/* /debootstrap
/debootstrap/debootstrap --version
DEBOOTSTRAP="/debootstrap/debootstrap"

# obtain date/time and write it to hardware
NTP_SERVERS="0.ntp.clearcable.net 0.ntp.rafal.ca"
ntpdate -b -v $NTP_SERVERS
hwclock --utc --systohc

for HDD in $HD0 ${HD1:-}
do

SECTOR_SIZE=$(blockdev --getss $HDD)
MiB2SECT=$(( $MiB / $SECTOR_SIZE ))
ALIGN=$(( 1 * $MiB2SECT ))
if [[ $SMALL_DISK -gt 0 ]]; then
	echo INFO: small disk GAP
	GAP=$((   8 * $MiB2SECT ))
else
	GAP=$(( 128 * $MiB2SECT ))
fi
echo "using inter-partition gap of $GAP MiB"

HDD_SECTORS=$(blockdev --getsz $HDD)
HDD_SECTOR_NUMBER_MAX=$[ $HDD_SECTORS - 1 ] #first sector is 0
HDD_SIZE=$[ $SECTOR_SIZE * $HDD_SECTORS ]

# based only on the first drive
# the 2nd drive, even if larger, will have the same layout
if test -z "${SIZE_ROUNDED+xxx}"
then
	SIZE_ROUNDED=$[ $HDD_SIZE / 1000**3 * 1000**3 ]
fi

# clear the disk partition tables (font+end)
dd if=/dev/zero of=$HDD bs=$SECTOR_SIZE count=1K 2>/dev/null
## gdisk sector count doesn't match /proc/partition reported disk size ##
dd if=/dev/zero of=$HDD bs=$SECTOR_SIZE seek=$[ $HDD_SIZE / $SECTOR_SIZE - $KiB ] count=2K 2>/dev/null || true #WILL fail

# partitions
GPT=1

# define GPT partition sizes
if [[ $SMALL_DISK -gt 0 ]]; then
	P1_SIZE_MB=10	#GRUB stage1.5
	P2_SIZE_MB=20	#EFI system
	P3_SIZE_MB=128	#GRUB stage2 / kernel / initrd / memtest
	PHYSICALEXTENTSIZE_M=32
else
	P1_SIZE_MB=10	#GRUB stage1.5
	P2_SIZE_MB=200	#EFI system
	P3_SIZE_MB=1024	#GRUB stage2 / kernel / initrd / memtest / rescueCD
	PHYSICALEXTENTSIZE_M=256
fi
echo "disk layout: $P1_SIZE_MB/$P2_SIZE_MB/$P3_SIZE_MB/remainder with ${PHYSICALEXTENTSIZE_M}MiB extents"

P1_START=$ALIGN
P1_SIZE=$[ $P1_SIZE_MB * $MiB2SECT ]

P2_START=$[ $P1_START + $P1_SIZE + $GAP ]
P2_SIZE=$[ $P2_SIZE_MB * $MiB2SECT ]

P3_START=$[ $P2_START + $P2_SIZE + $GAP ]
P3_SIZE=$[ $P3_SIZE_MB * $MiB2SECT ]

#OS LVM
P4_START=$[ $P3_START + $P3_SIZE + $GAP ]

#http://en.wikipedia.org/wiki/GUID_Partition_Table
#LBA0:Protective MBR
#LBA1:GTP header
#LBA2-33:4 partition entries per 512-byte sector
#->GPT structure takes 33 512-byte sectors (+1 sector for MBR)
#->secondary GPT is the same size, but there is no protective MBR

# create GPT partitions
sgdisk --zap-all $HDD
sgdisk --set-alignment=$ALIGN --new=1:$P1_START:$[ $P1_START + $P1_SIZE - 1 ] --typecode=1:ef02 --change-name=1:"BIOS boot" $HDD

# disk size rounding
PL_NUM=128	#partition-last number
PL_END=$(sgdisk --end-of-largest $HDD|tail --lines 1)
GPT_END_SECTORS=$[ $HDD_SECTOR_NUMBER_MAX - $PL_END ]
test $GPT_END_SECTORS -eq 33	# expected value
PL_START=$[ $SIZE_ROUNDED / $SECTOR_SIZE - $GPT_END_SECTORS ]

# create a reserved partition only if there is extra space
# i.e. NOT for disks which are factory rounded to metric GBs
if test $SIZE_ROUNDED -lt $HDD_SIZE
then
sgdisk --set-alignment=1 --new=$PL_NUM:$PL_START:$[ $PL_END ] --typecode=$PL_NUM:8301 --change-name=$PL_NUM:"rounding reserved" $HDD
fi

sgdisk --set-alignment=$ALIGN --new=2:$P2_START:$[ $P2_START + $P2_SIZE - 1 ] --typecode=2:ef00 --change-name=2:"EFI system" $HDD
sgdisk --set-alignment=$ALIGN --new=3:$P3_START:$[ $P3_START + $P3_SIZE - 1 ] --typecode=3:EA00 --change-name=3:"XBOOTLDR" $HDD

# setup RAID-X partition layout
test -z "${RAIDX+xxx}" && RAIDX=0	#disabled by default
test $RAIDX -ge 0 -a $RAIDX -le 6
RAIDX_COUNT=$[ 2**$RAIDX ]

SGDISK_FIL=$(sgdisk --first-in-largest $HDD)
SGDISK_EOL=$(sgdisk --end-of-largest   $HDD)

RAIDX_START_S=$[ $SGDISK_FIL + $GAP ]
RAIDX_END_S=$[ $SGDISK_EOL ]

LARGESTBLOCK_SIZE_S=$[ $RAIDX_END_S - $RAIDX_START_S + 1 ]

SIZE_RAIDXPART_NOMINAL_B=$[ $LARGESTBLOCK_SIZE_S / $RAIDX_COUNT * $SECTOR_SIZE ]

PX_END=$[ $RAIDX_START_S - 1 ]

# LVM data offset (1MiB), RAID data offset (1MiB), unknown (72KiB)
# $[ 1 * $MiB + 1 * $MiB + 72 * $KiB ]
# 72KiB is the RAID overhead for drives ~160GB (possibly the secondary header)
# for 300GB drives the overhead seems to be 136KiB, so it grows with array size
LVM_RAID_OVERHEAD=$[ 1 * $MiB + 1 * $MiB + 1 * $MiB ] #should allow for about 2TiB

#must preserve alignment; unused for now if LVM_RAID_OVERHEAD is aligned
LVM_RAID_OVERHEAD_GAP_S=$[ ( 3 * $MiB - $LVM_RAID_OVERHEAD ) / $SECTOR_SIZE ]

i=1
SIZE_RAIDXPART_USED_B=0
while test $i -le $RAIDX_COUNT
do
	SIZE_RAIDXPARTS_CUMULATIVE_B=$[ $i * $SIZE_RAIDXPART_NOMINAL_B ]
	SIZE_RAIDXPART_AVAILABLE_NOW_B=$[ $SIZE_RAIDXPARTS_CUMULATIVE_B - $SIZE_RAIDXPART_USED_B ]
	EXTENTS_POSSIBLE=$[ ( $SIZE_RAIDXPART_AVAILABLE_NOW_B - $LVM_RAID_OVERHEAD ) / ( $PHYSICALEXTENTSIZE_M * $MiB ) ]
	test -z "${PV_PE_COUNT+xxx}" && PV_PE_COUNT=$EXTENTS_POSSIBLE
	SIZE_RAIDXPART_ACTUAL_S=$[ ( $LVM_RAID_OVERHEAD + $EXTENTS_POSSIBLE * $PHYSICALEXTENTSIZE_M * $MiB ) / $SECTOR_SIZE ]

	PX_NUM=$[ 3 + $i ]
	PX_START=$[ $PX_END + 1 ]
	PX_END=$[ $PX_START + $SIZE_RAIDXPART_ACTUAL_S - 1 ]

	sgdisk --set-alignment=$ALIGN --new=$PX_NUM:$PX_START:$[ $PX_END ] --typecode=$PX_NUM:fd00 $HDD
	test $PX_START -eq $(sgdisk --info=$PX_NUM $HDD | grep --extended-regexp "^First sector:" | awk '{print $3}')

	#name selection
	if test $i -eq 1
	then
		PX_NAME='Linux RAID'
	else
		PX_NAME='RAID-X empty'
	fi
	sgdisk --change-name=$PX_NUM:"$PX_NAME" $HDD

	i=$[ $i + 1 ]
	SIZE_RAIDXPART_USED_B=$[ $SIZE_RAIDXPART_USED_B + $SECTOR_SIZE * ( $SIZE_RAIDXPART_ACTUAL_S + $LVM_RAID_OVERHEAD_GAP_S ) ]
	PX_END=$[ $PX_END + $LVM_RAID_OVERHEAD_GAP_S ]
done
gdisk -l $HDD || true	# BUG: returns 1 in version 0.8.10

case $HDD in
	/dev/sd*|/dev/xvd*) P='';;
	/dev/nvme*|/dev/mmcblk*) P='p';;
esac

HDD_P1=${HDD}${P}1
HDD_P2=${HDD}${P}2
HDD_P3=${HDD}${P}3
HDD_P4=${HDD}${P}4
if [[ -z "${BOOT_PART+xxx}" ]]; then
	BOOT_PART=$HDD_P3
fi

# wait for new partitions to be recognized by the kernel
until test -b $HDD -a -b $HDD_P1 -a -b $HDD_P2 -a -b $HDD_P3 -a -b $HDD_P4
do
	echo -n .
	sleep 1
done

# BOOTP/BOOTS volume labels
if test -z "${BOOT_INSTANCE+xxx}"
then
	BOOT_INSTANCE="P"
else
	BOOT_INSTANCE="S"
fi

# format the system and boot partitions
dd if=/dev/zero of=$HDD_P1 bs=64k 2>/dev/null || true #WILL fail
mkfs.vfat -n EFI_SYSTEM $HDD_P2
mkfs.ext2 -q -m 0 -L BOOT${BOOT_INSTANCE} $HDD_P3
dd if=/dev/zero of=$HDD_P4 bs=1M count=10 2>/dev/null

done #per disk processing
mount $BOOT_PART '/boot'

# always create RAID
PV='/dev/md1'
if [[ -z "$HD1" ]]; then
	# no HD1; running on hardware RAID or single disk
	RAID_DEVICE_COUNT=1	#fake RAID-1
	COMPONENT1=${HD0}${P}4
	COMPONENT2=""
else
	RAID_DEVICE_COUNT=2	#standard RAID-1
	COMPONENT1=${HD0}${P}4
	COMPONENT2=${HD1}${P}4
fi
MD_ALIGN_REQ=$[ 2**11 ]
if $MDADM --create --help|grep --quiet data-offset
then
	DATA_OFFSET_SPEC="--data-offset=${MD_ALIGN_REQ}s"
else
	DATA_OFFSET_SPEC=""
fi

if [[ $RAID_TYPE -lt 2 ]]; then
	PV=$COMPONENT1
else
SW_RAID_LEVEL=1
test -n "${RAID_LEVEL+xxx}" && SW_RAID_LEVEL=$RAID_LEVEL
$MDADM --create\
	--homehost=$HW_SERIAL\
	--metadata=1.2 --verbose --force\
	--level=$SW_RAID_LEVEL --raid-device=$RAID_DEVICE_COUNT\
	$DATA_OFFSET_SPEC\
	$PV $COMPONENT1 $COMPONENT2

# check MD alignment
# http://www.thomas-krenn.com/en/wiki/Linux_Software_RAID
# Depending on the version of mdadm the size of the data offset varies: ...
MD_ALIGN_CUR=$(mdadm --examine ${HD0}4|awk '{if ($1$2$3$5=="DataOffset:sectors"){print $4}}')
test -n "$MD_ALIGN_CUR"
test $MD_ALIGN_CUR -eq $MD_ALIGN_REQ
fi

# LVM setup
METADATACOPIES=1	#"too many metadata areas for pvresize" when >1
dd if=/dev/zero of=$PV bs=1M count=1 2>/dev/null
pvcreate --verbose --dataalignment 1M --metadatacopies $METADATACOPIES $PV

# check LVM alignment
test $(pvs --units b --noheadings --nosuffix --options pe_start $PV) -eq 1048576

vgcreate --physicalextentsize ${PHYSICALEXTENTSIZE_M}M $VG $PV

# exact PV sizing is supported only on GPT
if [[ $GPT -gt 0 && $RAID_TYPE -eq 2 ]]; then
	# check that we have the right number of Physical Extents
	[ $SW_RAID_LEVEL -eq 0 ] && PV_PE_COUNT=$[ 2 * $PV_PE_COUNT ]
	test $(pvs --units b --noheadings --nosuffix --options pv_pe_count $PV) -eq $PV_PE_COUNT

	# check that there is no leftover space
	# (not going to be exact due to RAID secondary metadata block sizing variability)
	###pvdisplay $PV|grep --extended-regexp 'PV Size.+/ not usable 0[[:space:]]*$'
fi

# SRC: /usr/local/share/sysfix/memory
MemTotal=$(awk '{if ($1=="MemTotal:"){print $2}}' '/proc/meminfo') #KiB
MemTotal_bytes=$(memory_round $(( MemTotal * $KiB )) )

LV_PREFIX=""	# only for VMs

# HW-specific sizing
FS_LIST_DEFAULT="/:2 /var:2 /var/log:2:largefile"

if [[ $SMALL_DISK -gt 0 ]]; then
	FS_LIST_DEFAULT='/:1.5 /var:0.5 /var/log:0.5:largefile'
fi

test -z "$FS_LIST"		&& FS_LIST="$FS_LIST_DEFAULT"
test -n "$FS_LIST_ADD"	&& FS_LIST="$FS_LIST $FS_LIST_ADD"
test -z "$MEM_SIZE"		&& MEM_SIZE=$(( MemTotal_bytes / MiB ))


if [[ $RAID_TYPE -eq 2 ]]; then
	BOOT_AUTO='noauto'
else
	BOOT_AUTO='auto'
fi


FSTAB="$FSTAB
LABEL=BOOTP	/boot		ext2	$BOOT_AUTO,nodev,nosuid,noexec"

hostname $HOSTNAME

elif test $VM = 'yes'
then
	if ! getent hosts $HOSTNAME >/dev/null
	then
		echo "hostname $HOSTNAME must resolve" 1>&2
		exit 1
	fi

	#authoritative source: syscheck/xen-config
	test -x '/usr/sbin/xl' && XEN_TOOLSTACK='xl' || XEN_TOOLSTACK='xm'
	if $XEN_TOOLSTACK info > $TMP
	then
	eval $(grep --extended-regexp '^(nr_cpus|nr_nodes|cores_per_socket|threads_per_core|cpu_mhz)' $TMP |
	sed 's/ *: */=/g')
	nr_cores=$[ $nr_cpus / $threads_per_core ]
	else
	nr_cores=0
	fi

	XEN_DOMAINNAME=$(hostname --domain)
	VM_NAME=$(echo $HOSTNAME|cut --delimiter=. --fields=1)
	if [[ -n "$NAMESPACE" ]]; then
		if ! echo "$NAMESPACE" | grep --quiet --extended-regexp '^[a-z][a-z0-9-]+$'; then
			echo "namespace name '$NAMESPACE' is not valid (only alpha-leading, alphanumeric, or dashes allowed)."
			exit 1
		fi
		VM_NAME="$NAMESPACE-$VM_NAME"
		echo "namespace-augmented VM name will be {$VM_NAME}"
	else
		if [[ "$HOSTNAME" = "${VM_NAME}.${XEN_DOMAINNAME}" ]]; then
			echo "VM $HOSTNAME is within the native dom0 namespace of $XEN_DOMAINNAME"
		else
			echo "requested VM name $HOSTNAME is outside the native dom0 namespace of $XEN_DOMAINNAME"
			echo "alternate namespace specification is required: use the {-n} switch"
			exit 1
		fi
	fi

	LV_PREFIX="${VM_NAME}_"

	# defaults (minimum)
	FS_LIST_DEFAULT="/:1 /var:1 /var/log:1:largefile"
	VCPUS_DEFAULT=1

	# memory allocation (MiB)
	if [[ $DEBIAN_VERSION -ge 10 ]]; then
		MEM_SIZE_DEFAULT=512
	elif [[ $DEBIAN_VERSION -ge 8 ]]; then
		MEM_SIZE_DEFAULT=256
	else
		MEM_SIZE_DEFAULT=128
	fi

	if [[ $DEBIAN_VERSION -ge 10 ]]; then
		# base OS install fills up 1GiB volumes completely:
		# /dev/mapper/vg-iot_root    999320  828396    102112  90% /tmp/target-a18fd39f-82a9-45e8-a3ca-780e693154f9
		# /dev/mapper/vg-iot_var     999320  926856     56080  95% /tmp/target-a18fd39f-82a9-45e8-a3ca-780e693154f9/var
		# so move them up to 2GiB
		FS_LIST_DEFAULT="/:2 /var:2 /var/log:2:largefile"
	fi

	test -z "$FS_LIST"		&& FS_LIST="$FS_LIST_DEFAULT"
	test -n "$FS_LIST_ADD"	&& FS_LIST="$FS_LIST $FS_LIST_ADD"
	test -z "$MEM_SIZE"		&& MEM_SIZE="$MEM_SIZE_DEFAULT"
	test -z "$VCPUS"		&& VCPUS="$VCPUS_DEFAULT"

	[ $VCPUS -gt $nr_cores ] && VCPUS=$nr_cores
fi

function set_fs_spec () {
if [[ $VM = 'yes' ]]; then
	letter=$(printf "\x$(printf %x $[96+$FS_ID])\n")
	case $DIST_SOE_VER in
		4) FS_SPEC="/dev/xvd${letter}1";;
		5) FS_SPEC="/dev/xvd${letter}";;
	esac
else
	FS_SPEC="/dev/vg/$LV_NAME"
fi
}

# create LVs for all filesystems, format them and mount them
# (procedure common for VM and VM-non systems)
FS_ID=1	#for VMs
FS_MNT_LIST=""
VM_DSK_LIST=""
test -n "$FS_LIST"
for FS in $FS_LIST; do
	IFS=':' read FS_MNT FS_SIZE FS_USAGE_TYPE <<< "$FS"
	FS_MNT_LIST="$FS_MNT $FS_MNT_LIST"

	# filesystem specific options
	if [[ $FS_MNT = '/' ]]; then
		FS_PASSNO=1
		FS_NAME='root'
		RESERVED_BLOCKS=''
	else
		FS_PASSNO=2
		FS_NAME=${FS_MNT:1}
		FS_NAME=${FS_NAME//\//_}
		RESERVED_BLOCKS='-m 0'
	fi
	if [[ -n "$FS_USAGE_TYPE" ]]; then
		FS_USAGE_TYPE="-T $FS_USAGE_TYPE"
	fi

	LV_NAME=${LV_PREFIX}${FS_NAME##*_}
	LV_DEV="/dev/$VG/$LV_NAME"

	if [[ $FS_SIZE -eq 0 ]]; then
		SIZE_SPEC='--extents 100%FREE'
	else
		SIZE_SPEC="--size ${FS_SIZE}G"
	fi

	lvcreate --quiet --zero n --name $LV_NAME $SIZE_SPEC $VG $PV_ALLOC
	blockdev_verify_or_wipe $LV_DEV

	FEATURES=''
	EXTENDED_OPTIONS=''
	if [[ $DEBIAN_VERSION -le 6 ]]; then
		FS_TYPE='ext3'
	elif [[ $DEBIAN_VERSION -eq 8 ]]; then
		FS_TYPE='ext4'
		EXTENDED_OPTIONS='-E lazy_itable_init=0,lazy_journal_init=0'
		FEATURES='-O none,has_journal,ext_attr,resize_inode,dir_index,filetype,extent,flex_bg,sparse_super,large_file,dir_nlink,extra_isize'
	else 
		FS_TYPE='ext4'
		EXTENDED_OPTIONS='-E lazy_itable_init=0,lazy_journal_init=0'
	fi
	mkfs $RESERVED_BLOCKS $FEATURES $EXTENDED_OPTIONS -v -L $FS_NAME -t $FS_TYPE $FS_USAGE_TYPE $LV_DEV

	mkdir --parents $TARGET/$FS_MNT
	mount $LV_DEV $TARGET/$FS_MNT

	set_fs_spec
	FSTAB="$FSTAB
$FS_SPEC	$FS_MNT	$FS_TYPE	defaults	0	$FS_PASSNO"
	FS_ID=$(( FS_ID + 1 ))

	if [[ -n "$VM_DSK_LIST" ]]; then
		VM_DSK_LIST+=',
	'
	fi
	VM_DEV_SPEC=${FS_SPEC##*/}
	VM_DSK_LIST="$VM_DSK_LIST'phy:$LV_DEV,$VM_DEV_SPEC,w'"
done

# create swap space appropriate to the memory size
SWAP_SIZE=$(( MEM_SIZE / 2 ))

if [[ $DEBIAN_VERSION -le 6 ]]; then
	LV_NAME=${LV_PREFIX}"swap"
	LV_DEV="/dev/$VG/$LV_NAME"
	lvcreate --quiet --zero n --name $LV_NAME --size ${SWAP_SIZE}M $VG $PV_ALLOC
	mkswap -f $LV_DEV
	FS_NAME='swap'
	set_fs_spec
	FSTAB="$FSTAB
$FS_SPEC	none		swap"
	VM_DEV_SPEC=${FS_SPEC##*/}
	VM_DSK_LIST="$VM_DSK_LIST,
	'phy:$LV_DEV,$VM_DEV_SPEC,w'"
else
	declare -r ZRAM_DEV='/dev/zram0'
	FSTAB="$FSTAB
$ZRAM_DEV	none		swap	pri=0"
fi

if [[ $VM = 'yes' ]]; then
	# prepare the XEN configuration file

	# http://standards.ieee.org/develop/regauth/oui/public.html
	# 00-16-3E	(hex)	Xensource, Inc.
	MAC_BASE="00:16:3e"
	MAC1_4=$(echo $UUID|cut --bytes=1-2)
	MAC1_5=$(echo $UUID|cut --bytes=3-4)
	MAC1_6=$(echo $UUID|cut --bytes=5-6)
	MAC2_6=$(printf "%02x" $[ ( 0x$MAC1_6 + 1 ) % 256 ])
	MAC1="$MAC_BASE:$MAC1_4:$MAC1_5:$MAC1_6"
	MAC2="$MAC_BASE:$MAC1_4:$MAC1_5:$MAC2_6"

	XEN_CONF_DIR="/etc/xen/auto"
	test -d $XEN_CONF_DIR

	# adjust memory visible inside the VM
	case $DIST_SOE_VER in
		5)
			# measured empirically on Xen 4.17.2-pre
			LR_A=-30.822
			LR_B=0.98301
			LR_y=$MEM_SIZE # inside - desired
			outside_mem=$(bc <<< "($LR_y - $LR_A)/$LR_B+1")
			MEM_SIZE=$outside_mem
			;;
	esac

	TMP_VM_CONFIG=$(mktemp)
cat > $TMP_VM_CONFIG << EOF
#!/usr/sbin/xl create
#$ soe-install/stage1 V=$V HOSTNAME=$HOSTNAME DEBIAN_VERSION=$DEBIAN_VERSION MEM_SIZE=$MEM_SIZE VCPUS=$VCPUS
name	= "$VM_NAME"
uuid	= "$UUID"
vcpus	= $VCPUS
memory	= $MEM_SIZE
disk	= [
	$VM_DSK_LIST,
]
vif	= [
	'bridge=$HV_BRIDGE_DEFAULT,mac=$MAC1',
]
EOF

if [[ $PYGRUB = 'yes' ]]; then
	case $DIST_SOE_VER in
		4) vm_boot_spec='kernel	= "/etc/xen/boot/pvgrub"
extra	= "(xen/xvda1)/boot/grub/grub.cfg"';;
		5) vm_boot_spec='type	= "pvh"
kernel	= "/usr/lib/grub-xen/grub-i386-xen_pvh.bin"
extra	= "(xen/xvda)/boot/grub/grub.cfg"
on_watchdog	= "restart"';;
	esac
	echo "$vm_boot_spec" >> $TMP_VM_CONFIG
else
	echo 'kernel	= "/etc/xen/boot/vmlinuz"' >> $TMP_VM_CONFIG
	echo 'ramdisk	= "/etc/xen/boot/initrd.img"' >> $TMP_VM_CONFIG
	echo 'root	= "/dev/xvda1"' >> $TMP_VM_CONFIG
	echo 'extra	= "printk.time=0 net.ifnames=0 vsyscall=emulate elevator=noop"' >> $TMP_VM_CONFIG
fi
mv $TMP_VM_CONFIG $XEN_CONF_DIR/$VM_NAME
chmod 755 $XEN_CONF_DIR/$VM_NAME

# We don't need to run this any more, we make xl-compatible configs from scratch
#test $XEN_TOOLSTACK = 'xl' && xen-config-upgrade run 1>/dev/null
fi #VM

# pre-cached packages
if [[ -d $APT_ARCHIVES ]]; then
	mkdir --parents $TARGET/$APT_ARCHIVES
	mount --bind $APT_ARCHIVES $TARGET/$APT_ARCHIVES
fi

# mount AUX filesystems
BOOT_VOLUME=
if test $VM = 'no'
then
	BOOT_VOLUME='boot'
	mkdir $TARGET/$BOOT_VOLUME
	mount --bind /$BOOT_VOLUME $TARGET/$BOOT_VOLUME
fi

# OS installation
$DEBOOTSTRAP\
	--no-check-gpg\
	--arch=$ARCH\
	--include="$PKG_INCLUDE"\
	--exclude="$PKG_EXCLUDE"\
	$DIST_NAME $TARGET $MIRROR_PUBLIC

# mount AUX filesystems
for FS in sys proc tmp dev dev/pts run run/udev; do
	[ -d $TARGET/$FS ] || mkdir $TARGET/$FS
	mount --bind /$FS $TARGET/$FS
done

# export working nameserver
NAMESERVER=$(awk '{if ($1=="nameserver"){print $2;exit}}' '/etc/resolv.conf')

# run tweaks inside the new OS ("stage2")
STAGE2_SH=stage2.sh
cp --archive $INSTALLER_DIR/$NEXT_STAGE $TARGET/$STAGE2_SH
chmod 555 $TARGET/$STAGE2_SH
export ARCH UUID HOSTNAME NAMESPACE FSTAB VM PYGRUB KERNEL VERBOSE SWAP_SIZE
export MIRROR_PUBLIC MIRROR_PRIVATE INSTALLER_DIR URL_BASE OS_DESCRIPTION DIST_VER DIST_NAME DIST_SOE_VER
export CONSOLE_PORT CONSOLE_SPEED
export NAMESERVER
chroot $TARGET /bin/bash /$STAGE2_SH
rm $TARGET/$STAGE2_SH

if test $VM = 'yes'
then
	# replicate up-to-date config files from the Xen system
	cp --archive --target-directory="$TARGET/etc" /etc/hosts.allow /etc/hosts.deny
fi

if [[ -d $APT_ARCHIVES ]]; then
	umount $TARGET/$APT_ARCHIVES
	mkdir  $TARGET/$APT_ARCHIVES/partial
fi

chattr +i $TARGET	#root
for FS in run/udev run dev/pts dev sys proc $BOOT_VOLUME tmp $FS_MNT_LIST; do
	umount $TARGET/$FS
	if [[ $FS = 'tmp' ]]; then
		chmod 755 $TARGET/$FS # 1777 will be set by tmpfs overlay
	fi
	if [[ $FS = 'run' ]]; then
		empty_dir=$(mktemp --directory)
		rsync --delete --dirs --verbose $empty_dir/ $TARGET/$FS/
		rmdir $empty_dir
	fi

	case $FS in
		'/'|'dev/pts'|'run/udev');;
		*) chattr +i $TARGET/$FS;;
	esac
done
rmdir $TARGET

if [[ $VM = 'no' ]]; then
	umount /boot
	lvchange --available n $VG
fi

rm $TMP

echo "SOE$DIST_SOE_VER installation complete."
