#!/bin/bash
#< Fully featured script to provision a Solaris 10 zone

# Executables
AWK="/usr/bin/nawk"
BASENAME="/usr/bin/basename"
CAT="/usr/bin/cat"
CHMOD="/usr/bin/chmod"
CP="/usr/bin/cp"
ECHO="/bin/echo"
GREP="/usr/xpg4/bin/grep"
ID="/usr/xpg4/bin/id"
MKDIR="/usr/bin/mkdir"
MV="/usr/bin/mv"
POOLADM="/usr/sbin/pooladm"
RM="/usr/bin/rm"
RMDIR="/usr/bin/rmdir"
SED="/usr/bin/sed"
SVCS="/usr/bin/svcs"
TOUCH="/usr/bin/touch"
ZONEADM="/usr/sbin/zoneadm"
ZONECFG="/usr/sbin/zonecfg"

# Constants
THISPROG=$( ${BASENAME} $0 )
SYSADMINS="kevin someone jdoe"

# Paths
TMP_DIR="/tmp"
ZONE_ROOT="/var/zones"

# Defaults
AUTO_BOOT=0
BOOT_AFTER=0
CPU_SHARES=1
CONF_ZONE=1
HOME_DIRS=1
IP_ADDR=""   # mandatory
PHYS_INT=""  # mandatory
ZONE_NAME="" # mandatory
DEFAULT_ROUTE="192.168.0.1"
SUBNET_MASK="255.255.255.0"

VERBOSE=0

builtin umask 077

function print_error {
   ${ECHO} "Error: $@" >&2
}

function printv {
   (( VERBOSE )) && {
      ${ECHO} "--> $@"
   }
}

function print_msg {
   ${ECHO} "--> $@"
}

function print_usage {
   {
      ${ECHO} "Usage: ${THISPROG} [-abChHv] [-c <num>] [-r <route>] [-s <netmask>] [-t <tmpdir>] -i <ip> -p <phys> -z <zone>"
      ${ECHO} "       -a   Set zone to auto-boot"
      ${ECHO} "       -b   Boot zone after configuration"
      ${ECHO} "       -c   Assign zone <num> CPU shares"
      ${ECHO} "       -C   Disable post-installation zone configuration"
      ${ECHO} "       -h   Display this usage message"
      ${ECHO} "       -H   Do not import sysadmin home directories"
      ${ECHO} "       -i   Zone IP address"
      ${ECHO} "       -p   Zone physical interface"
      ${ECHO} "       -r   Override zone default route (${DEFAULT_ROUTE})"
      ${ECHO} "       -s   Override zone subnet mask (${SUBNET_MASK})"
      ${ECHO} "       -t   Specify temporary directory"
      ${ECHO} "       -v   Verbose mode"
      ${ECHO} "       -z   Specify zone name"
   } >&2
}

function get_id {
   MY_UID=$( ${ID} -un )
   if [ "${MY_UID}" != "root" ]; then
      print_error "This script must be run as root"
      exit 1
   fi
}

function check_pools_enabled {
   POOL_STATE=$( ${SVCS} svc:/system/pools:default | ${AWK} 'NR==2 {print $1}' )
   if [ "${POOL_STATE}" != "online" ]; then
      print_error "svc:/system/pools:default not online"
   fi
}

function check_resource_configuration {
   ERROR_COUNT=0
   ${POOLADM} | ${GREP} -q "zone_pset"
   if [ "$?" -ne "0" ]; then
      print_error "zone_pset does not exist in resource configuration"
      (( ERROR_COUNT = ERROR_COUNT + 1 ))
   fi
   ${POOLADM} | ${GREP} -q "zone_pool"
   if [ "$?" -ne "0" ]; then
      print_error "zone_pool does not exist in resource configuration"
      (( ERROR_COUNT = ERROR_COUNT + 1 ))
   fi
   if [ "${ERROR_COUNT}" -gt "0" ]; then
      exit 1
   fi
}

function check_arguments {
   # We could validate IPs with valip.sh - but the zoneadm install step will
   # perform this validation anyway, same goes for physical interface, etc.
   ERROR_COUNT=0
   if [ "${IP_ADDR}" = "" ]; then
      print_error "IP Address (-i) mandatory"
      (( ERROR_COUNT = ERROR_COUNT + 1 ))
   fi
   if [ "${PHYS_INT}" = "" ]; then
      print_error "Physical Interface (-p) mandatory"
      (( ERROR_COUNT = ERROR_COUNT + 1 ))
   fi
   if [ "${ZONE_NAME}" = "" ]; then
      print_error "Zone Name (-z) mandatory"
      (( ERROR_COUNT = ERROR_COUNT + 1 ))
   fi
   if [ "${CONF_ZONE}" -eq "1" -a "${HOME_DIRS}" -eq "0" ]; then
      print_error "Zone configuration requires home directory import"
      (( ERROR_COUNT = ERROR_COUNT + 1 ))
   fi
   ${ECHO} "${CPU_SHARES}" | ${GREP} -E -q '^[0-9]+$'
   if [ "$?" -ne "0" ]; then
      print_error "CPU shares (-c) should be an integer value"
      (( ERROR_COUNT = ERROR_COUNT + 1 ))
   fi
   if [ ! -d "${TMP_DIR}" ]; then
      print_error "${TMP_DIR} not a directory"
      (( ERROR_COUNT = ERROR_COUNT + 1 ))
   elif [ ! -w "${TMP_DIR}" ]; then
      print_error "Cannot write to ${TMP_DIR}"
      (( ERROR_COUNT = ERROR_COUNT + 1 ))
   fi
   if [ "${ERROR_COUNT}" -gt "0" ]; then
      exit 1
   fi
}

function check_zone_exists {
   ZONE_PATH="${ZONE_ROOT}/${ZONE_NAME}"
   if [ -d "${ZONE_PATH}" ]; then
      print_error "Zone ${ZONE_NAME} already exists"
      exit 1
   fi
}

function create_zone_command_file {
   ZONE_CMD_FILE="${TMP_DIR}/${ZONE_NAME}.$$"
   printv "Generating zone command file - will be saved as ${ZONE_CMD_FILE}"
   {
      ${ECHO} "create -F"
      ${ECHO} "set zonepath=${ZONE_PATH}"
      if [ "${AUTO_BOOT}" -eq "1" ]; then
         ${ECHO} "set autoboot=true"
      fi
      ${ECHO} "add net"
      ${ECHO} "set address=${IP_ADDR}"
      ${ECHO} "set physical=${PHYS_INT}"
      ${ECHO} "end"
      ${ECHO} "set pool=zone_pool"
      ${ECHO} "add rctl"
      ${ECHO} "set name=zone.cpu-shares"
      ${ECHO} "add value ( priv=privileged,limit=${CPU_SHARES},action=none )"
      ${ECHO} "end"
      ${ECHO} "add fs"
      ${ECHO} "set dir=/etc/local"
      ${ECHO} "set special=${ZONE_PATH}/conf"
      ${ECHO} "set type=lofs"
      ${ECHO} "set options=[rw,nodevices]"
      ${ECHO} "end"
      if [ "${HOME_DIRS}" -eq "1" ]; then
         for SYSADMIN in ${SYSADMINS}; do
            SYSADMIN_HOME="/home/${SYSADMIN}"
            if [ ! -d "${SYSADMIN_HOME}" ]; then
               print_msg "Cannot find home directory for ${SYSADMIN}"
            else
               ${ECHO} "add fs"
               ${ECHO} "set dir=${SYSADMIN_HOME}"
               ${ECHO} "set special=${SYSADMIN_HOME}"
               ${ECHO} "set type=lofs"
               ${ECHO} "set options=[rw,nodevices]"
               ${ECHO} "end"
            fi
         done
         ${ECHO} "add fs"
         ${ECHO} "set dir=/root"
         ${ECHO} "set special=/root"
         ${ECHO} "set type=lofs"
         ${ECHO} "set options=[rw,nodevices]"
         ${ECHO} "end"
      fi
      ${ECHO} "verify"
      ${ECHO} "commit"
      ${ECHO} "exit"
   } > ${ZONE_CMD_FILE}
   printv "Zone command file created:"
   (( VERBOSE )) && {
      ${CAT} ${ZONE_CMD_FILE}
   }
}

function create_sysidcfg_file {
   ROOT_PASSWORD=$( ${AWK} 'BEGIN {FS=":"} $1 ~ /^root/ {print $2}' /etc/shadow )
   SYSIDCFG="${TMP_DIR}/${ZONE_NAME}-sysidcfg.$$"
   printv "Generating sysidcfg file - will be saved as ${SYSIDCFG}"
   {
      ${ECHO} "system_locale=C"
      ${ECHO} "timezone=Australia/Melbourne"
      ${ECHO} "terminal=ansi"
      ${ECHO} "security_policy=NONE"
      ${ECHO} "root_password=${ROOT_PASSWORD}"
      ${ECHO} "timeserver=localhost"
      ${ECHO} "name_service=NONE"
      ${ECHO} "network_interface=primary { hostname=${ZONE_NAME}"
      ${ECHO} "        netmask=${SUBNET_MASK}"
      ${ECHO} "        default_route=${DEFAULT_ROUTE}"
      ${ECHO} "        protocol_ipv6=no }"
      ${ECHO} "nfs4_domain=dynamic"
   } > ${SYSIDCFG}
   printv "sysidcfg file created:"
   (( VERBOSE )) && {
      ${CAT} ${SYSIDCFG}
   }
}

function install_zone {
   print_msg "Configuring zone from command file"
   ${ZONECFG} -z ${ZONE_NAME} -f ${ZONE_CMD_FILE}
   if [ "$?" -ne "0" ]; then
      print_error "Issues configuring zone ${ZONE_NAME}"
      exit 1
   fi
   ${MKDIR} -p ${ZONE_PATH}/conf
   print_msg "Installing new zone ${ZONE_NAME}"
   ${ZONEADM} -z ${ZONE_NAME} install
   if [ "$?" -ne "0" ]; then
      print_error "Could not create ${ZONE_NAME}"
      # let's avoid rm -rf...
      ${RMDIR} ${ZONE_PATH}/conf
      ${RMDIR} ${ZONE_PATH}
      ${ZONECFG} -z ${ZONE_NAME} delete -F
      exit 1
   fi
   if [ "${HOME_DIRS}" -eq "1" ]; then
      print_msg "Disabling /home automount"
      AUTO_MASTER="${ZONE_PATH}/root/etc/auto_master"
      ${GREP} -v 'home' ${AUTO_MASTER} > ${AUTO_MASTER}.new
      ${MV} ${AUTO_MASTER}.new ${AUTO_MASTER}
   fi
   print_msg "Installing sysidcfg for unattended first boot"
   ${CP} ${SYSIDCFG} ${ZONE_PATH}/root/etc/sysidcfg
   ${TOUCH} ${ZONE_PATH}/root/etc/.NFS4inst_state.domain
}

function generate_firstboot_script {
   FIRSTBOOT="${ZONE_PATH}/root/etc/rc2.d/S50firstboot"
   ${CAT} <<EOF >${FIRSTBOOT}
#!/usr/bin/bash

exec 1> /dev/console
echo "--> Error log"
echo "Errors will be saved in /var/log/firstboot.log"
exec 2> /var/log/firstboot.log

echo "--> Building user environments"
usermod -d /root -s /usr/bin/bash root
# Users home directories will already be present during zone configuration
useradd -d /home/kevin -u 111 -g sysadmin -s /usr/bin/bash -c "Kevin Waldron" kevin
useradd -d /home/someone -u 222 -g sysadmin -s /usr/bin/bash -c "Someone" someone
useradd -d /home/jdoes -u 333 -g sysadmin -s /usr/bin/bash -c "Jane Doe" jdoe

echo "--> Setting default user passwords"
cp -p /etc/shadow /etc/shadow.orig
for U in kevin someone jdoe; do
   perl -spi -e 's/^'\$U':\*LK\*:/'\$U':EnCrYpTeD:/' /etc/shadow
   # rest of user environment will already be inherited from global zone
done

echo "--> Adding standard warnings"
echo "Authorized uses only. All activity may be monitored and reported" > /etc/issue
echo "Authorized uses only. All activity may be monitored and reported" > /etc/motd
chmod 644 /etc/issue /etc/motd

echo "--> Enable DNS name resolution"
echo "domain xyz.com" > /etc/resolv.conf
echo "search xyz.com" >> /etc/resolv.conf
echo "nameserver 192.168.0.1" >> /etc/resolv.conf
echo "nameserver 192.168.0.2" >> /etc/resolv.conf
cp -p /etc/nsswitch.dns /etc/nsswitch.conf
svcadm enable dns/client
/etc/init.d/nscd stop
/etc/init.d/nscd start

echo "--> Disabling routing on the zone"
touch /etc/notrouter

echo "--> Configuring ntp"
echo "server ntp.xyz.com" > /etc/inet/ntp.conf
svcadm restart svc:/network/ntp:default

echo "--> Running newaliases"
newaliases

echo "--> Replacing syslogd with syslog-ng"
# assumes syslog-ng is installed on global zone
ln -s /etc/local/syslog-ng.conf /etc/syslog-ng.conf
svccfg import /var/svc/manifest/system/syslog-ng.xml

echo "--> Disabling services"
yes | netservices limited
svcadm disable rpc/bind

echo "--> Restarting syslog"
svcadm disable system-log
svcadm enable syslog-ng

echo "--> Fixing FQDN"
for file in /etc/inet/hosts /etc/inet/ipnodes; do
  sed "s/^\(.*${ZONE_NAME}.*\)$/\1 ${ZONE_NAME}.xyz.com/" \${file} > \${file}.new
  mv \${file}.new \${file}
done

echo "--> Restarting Sendmail"
svcadm refresh sendmail

echo "--> Self destruct mechanism activated ;-)"
rm -f /etc/rc2.d/S50firstboot
EOF
   ${CHMOD} 755 ${FIRSTBOOT}
}

function configure_zone {
   if [ "${CONF_ZONE}" -eq "0" ]; then
      print_msg "Not performing zone post-install at user request"
      return
   fi
   # assumes PATH/SUPATH is correctly configured in global zone
   print_msg "Copying /etc/default files to zone"
   ${CP} -p /etc/default/login /etc/default/su ${ZONE_PATH}/root/etc/default
   # assumes sendmail is correctly configured in global zone - /usr/lib/mail/cf/* will
   # already be inherited
   print_msg "Copying /etc/mail/sendmail.cf to zone"
   ${CP} -p /etc/mail/sendmail.cf ${ZONE_PATH}/root/etc
   print_msg "Configuring /etc/skel in zone"
   ${RM} -f ${ZONE_PATH}/root/etc/skel/local.* ${ZONE_PATH}/root/etc/skel/.profile
   ${CAT} <<EOP >${ZONE_PATH}/root/etc/skel/.bash_profile
PS1='\[\033[01;32m\]\u@\h \[\033[01;34m\]\w \\$ \[\033[00m\]'

EDITOR=/usr/local/bin/vim
PAGER=/usr/bin/less

export PS1 EDITOR PAGER

export \`/usr/bin/grep ^PATH /etc/default/login\`

umask 022
EOP
   print_msg "Configuring mail aliases"
   ${CAT} <<EOA >${ZONE_PATH}/root/etc/mail/aliases
root:           kevin
kevin:          kevin@xyz.com
EOA
   print_msg "Copying syslog-ng configuration to zone"
   ${MKDIR} -p ${ZONE_PATH}/conf/lib/svc/method
   ${CP} -p /etc/local/syslog-ng.conf ${ZONE_PATH}/conf
   ${CP} -p /etc/local/lib/svc/method/svc-syslog-ng ${ZONE_PATH}/conf/lib/svc/method
   ${CP} -p /var/svc/manifest/system/syslog-ng.xml ${ZONE_PATH}/root/var/svc/manifest/system/syslog-ng.xml
   ${CP} -p /etc/logadm.conf ${ZONE_PATH}/root/etc/
   generate_firstboot_script
}

function boot_zone {
   if [ "${BOOT_AFTER}" -eq "1" ]; then
      print_msg "Booting ${ZONE_NAME}..."
      ${ZONEADM} -z ${ZONE_NAME} boot
   fi
}

#
# main()
#
while getopts ":abc:ChHi:p:t:vz:" OPTION; do
  case ${OPTION} in
     "a")  AUTO_BOOT=1               ;;
     "b")  BOOT_AFTER=1              ;;
     "c")  CPU_SHARES="${OPTARG}"    ;;
     "C")  CONF_ZONE=0               ;;
     "h")  print_usage && exit 0     ;;
     "H")  HOME_DIRS=0               ;;
     "i")  IP_ADDR="${OPTARG}"       ;;
     "p")  PHYS_INT="${OPTARG}"      ;;
     "r")  DEFAULT_ROUTE="${OPTARG}" ;;
     "s")  SUBNET_MASK="${OPTARG}"   ;;
     "t")  TMP_DIR="${OPTARG}"       ;;
     "v")  VERBOSE=1                 ;;
     "z")  ZONE_NAME="${OPTARG}"     ;;
     *  )  print_usage && exit 1     ;;
  esac
done

shift $(( ${OPTIND} - 1 ))

if [ "$#" -ne "0" ]; then
   print_usage && exit 1
fi

get_id
check_pools_enabled
check_resource_configuration
check_arguments
check_zone_exists
create_zone_command_file
create_sysidcfg_file
install_zone
configure_zone
boot_zone

exit 0