#!/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