#!/bin/ksh93 # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License") # (see http://www.opensource.org/licenses/CDDL-1.0). # # Copyright (c) 2011, 2012 Jens Elkner. All rights reserved. # Use is subject to license terms. ############################################################################ # "bootstrap" stuff ############################################################################ typeset -r LIBNAME="${.sh.file}" . ${LIBNAME%/*}/log.kshlib . ${LIBNAME%/*}/man.kshlib Man.addVar LIBNAME 'The full path of this zinstall helper script. (read-only)' Man.addVar SCRIPT 'The full path of the running script. (read-only)' typeset -r SCRIPT=$( cd ${ dirname $0 }; print -n "$PWD/${ basename $0; }"; ) Man.addVar SDIR 'Base directory which contains all relevant files (manifests, profiles, etc.) required to setup zones. Default: the directory which contains this running script (see \a$SCRIPT\a).' [[ -z $SDIR ]] && SDIR=${SCRIPT%/*} cd $SDIR . ${LIBNAME%/*}/utils.kshlib ############################################################################ # main environment ############################################################################ Man.addVar ZNAME 'The name of the zone to be [un]]installed. Default: \a$SCRIPT\a with suffix \b-zonesetup.ksh\b and trailing dot components removed.' if [[ -z ${ZNAME} ]]; then typeset ZNAME=${SCRIPT%-zonesetup.ksh} if [[ ${ZNAME} == ${SCRIPT} ]]; then ZNAME="" else ZNAME=${ZNAME##*/} ZNAME=${ZNAME%%.*} fi else [[ $ZNAME == $SCRIPT ]] && ZNAME="" || ZNAME=${ZNAME##*/} fi Man.addVar ZBASE 'Absolute path of the directory where zones get mounted. Convention: \b/zones\b. It should be a ZFS.' [[ -z $ZBASE ]] && ZBASE='/zones' . ${LIBNAME%/*}/datadir.kshlib ############################################################################ # steps ############################################################################ Man.addVar ZVNIC 'Primary VNIC to use for the zone. Convention: \a$ZNAME\a\b0\b.' Man.addVar ADD_ZVNICS 'An associative array of additional vnics to add to the zone. The key is the addrobj (e.g. "${ZNAME}1/v4") and the value is the addr/prefixlen (static address) or an empty string (dhcp) to pass to \bipadm\b(1M). Per default this array is empty. NOTE: To take effect one needs to set the values after '"${LIBNAME##*/}"' got source and before \bcheckNIC()\b and \bprepareProfiles()\b are called.' typeset -A ADD_ZVNICS=( ) Man.addVar ZHOSTID 'HostId of the zone. Default: Last 4 bytes of the MAC address of $ZVNIC. (optional)' Man.addFunc checkNIC '' '[+NAME?checkNIC - check primary VNIC settings.] [+DESCRIPTION?Checks the Virtual Network Interface Card (VNIC) settings of the primary VNIC (\a$ZVNIC\a) to use for the non-global zone. If \aZVNIC\a is not yet set, it gets set to \a${ZNAME}0\a or \a${ZNAME}_0\a if $ZNAME ends with a digit (convention). If \a$ZVNIC\a does not yet exist, this function logs an appropriate info about how to create it and calls \bexit()\b. Finally, if \a$ZHOSTID\a is not yet set, it gets set to the sum of the last 4 bytes of the MAC address of \a$ZVNIC\a. If an error occurs, this function calls \bexit()\b.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage ZVNIC ADD_ZVNICS ZNAME ZHOSTID MAC DEFAULTIF; }" ' [+SEE ALSO?\bgetDefaultIfAndMac()\b, \bdladm\b(1M)] ' function checkNIC { typeset X if [[ -z ${ZVNIC} ]]; then if [[ ${ZNAME%+([[:digit:]])} != ${ZNAME} ]]; then ZVNIC="${ZNAME}_0" else ZVNIC="${ZNAME}0" fi fi X=${ dladm show-vnic -p -o macaddress $ZVNIC 2>/dev/null; } if [[ -z $X ]]; then getDefaultIfAndMac [[ -n ${MAC} ]] && MAC="-m ${MAC}" Log.fatal "The virtual NIC $ZVNIC does not exist yet." \ 'You may create it using the following command:\n\t' \ "+ dladm create-vnic -l ${DEFAULTIF:-bge0} ${MAC} ${ZVNIC}" exit 2 fi if [[ -z ${ZHOSTID} ]]; then typeset DIGIT=( ${X//:/ } ) ZHOSTID="" for ((i=2; i <= 5; i++)); do [[ ${#DIGIT[$i]} == 1 ]] && ZHOSTID+="0" ZHOSTID+=${DIGIT[$i]} done fi (( ! ${#ADD_ZVNICS[@]} )) && return typeset Y OBJ ADDR NIC typeset -i16 H integer OFFSET=0 for OBJ in "${!ADD_ZVNICS[@]}" ; do [[ -z ${OBJ} ]] && continue NIC=${OBJ%/*} X=${ dladm show-vnic -p -o macaddress "${NIC}" 2>/dev/null; } if [[ -z $X ]]; then X=${ dladm show-vnic -p -o macaddress "${ZVNIC}" 2>/dev/null; } H=16#${X##*:} (( OFFSET++ )) (( H+=${OFFSET} )) Y=${ dladm show-phys -p -o link | sort | tail -1 ; } Log.fatal "The virtual NIC ${ZVNIC%0}1 for management does not exist yet." \ '\n You may create it using the following command:\n\t' \ "+ dladm create-vnic -l ${Y:-bge0} -m ${X%:*}:${H#*#} ${NIC}" exit 2 fi done } Man.addVar ZHNAME 'Hostname of the new zone. Default: \a$ZNAME\a' Man.addVar ZDOMAIN 'DNS domain to use within the zone. Default: If \a$ZHNAME\a contains a dot (.), everything after the first dot. Otherwise the domain name of the global zone.' Man.addVar ZSEARCH 'The arguments for the search option to use for the zone its \b/etc/resolv.conf\b. Default: Same as in the global zone, if not available \a$ZDOMAIN\a.' Man.addVar ZDNSRVR 'A comma separated list of DNS servers to use. Default: Same as in the global zone. If the can not be determined \b127.0.0.1\b.' Man.addVar ZIP 'Primary IP address of the zone. Default: AUTO. See also "getent hosts $ZNAME.$ZDOMAIN" or "getent hosts $ZNAME".' Man.addVar ZNMASK 'Netmask of the primary IP address of the zone. Format: "a.b.c.d" or "/N" (CIDR notation). Default: AUTO. See also "getent netmasks $ZIP".' Man.addVar ZDEFROUT 'If set, the IP of the default router to use. Default: Same as the one for the global zone. If empty, no default router will be set.' Man.addFunc checkIP '' '[+NAME?checkIP - check and set missing IP/DNS related settings for the zone.] [+DESCRIPTION?Function to check network parameters of the primary VNIC and DNS settings of the zone to install. If a required environment variable is not yet set, the corresponding settings of the current zone will be used to populate them.] [+?If the default router for the zone is different from the default router in this zone, \bZDEFROUT\b should be set explicitly. This is important because if it is empty or has an invalid value (network unreachable), SMF switches immediately and permanent over to dynamic routing (see \bin.routed\b(1M)) when the system gets initialized. One may use \brouteadm\b(1M) to determine the current routing typ ("IPv4 routing: disabled" means static routing).] [+?Note that dynamic routing on the next [re]]boot gets also enabled permanently, if there is no valid \b/etc/defaultrouter\b or network strategy is set to \bdhcp\b.] [+?If an error occurs, this function calls \bexit()\b.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage ZNAME ZHNAME ZDOMAIN ZIP ZNMASK ZSEARCH ZDNSRVR ZDEFROUT; }" '} [+SEE ALSO?\bgetprop()\b, \bgetent\b(1M), \bgetNetmaskBits()\b, \bgetNetmaskBitsByIP()\b, \broute\b(1M), \bmatchIP()\b] ' function checkIP { ZHNAME=${ZHNAME:-$ZNAME} typeset X=${ZHNAME#*.} BITS="" if [[ $X != $ZHNAME ]]; then [[ -n $ZDOMAIN && $ZDOMAIN != $X ]] && \ Log.warn "Replacing ZDOMAIN=$ZDOMAIN by $X because ZHNAME=$ZHNAME" ZDOMAIN="$X" ZHNAME=${ZHNAME%%.*} elif [[ -z $ZDOMAIN ]]; then ZDOMAIN=${ getprop config/domainname nis/domain; } ZDOMAIN=${ZDOMAIN##*+(\s)} if [[ -z $ZDOMAIN ]]; then ZDOMAIN=${ getprop config/search dns/client:default; } # the first entry should be the preferred one ZDOMAIN=${ZDOMAIN%%+(\s)*} fi # [[ -z $ZDOMAIN ]] && ZDOMAIN="$FBZDOMAIN" fi ZDOMAIN=${ZDOMAIN%%.} if [[ -z $ZIP ]]; then ZIP=${ getent hosts ${ZHNAME}.$ZDOMAIN; } [[ -z $ZIP ]] && ZIP=${ getent hosts $ZHNAME; } if [[ -n $ZIP ]]; then # last try X=${ZIP#*[[:space:]]} X=${X%%[[:space:]]*} [[ -n $X ]] && ZDOMAIN=${X#*.} fi ZIP=${ZIP%%[[:space:]]*} fi if [[ -z $ZIP ]]; then Log.fatal 'Unable to determine the primary IP address for the zone.' \ 'Either set ZIP explicitly to the desired IP or add an' \ "appropriate host entry to /etc/inet/hosts for ${ZHNAME}.$ZDOMAIN ." exit 2 fi [[ $ZIP == "127.0.0.1" ]] && \ Log.warn "VNIC $ZVNIC will not get configured because the IP address" \ '127.0.0.1 is reserved for the loopback interface lo0.' if [[ -z $ZNMASK ]]; then ZNMASK=$(getent netmasks $ZIP) if [[ -z $ZNMASK ]]; then BITS=$(getNetmaskBitsByIP $ZIP) [[ -n $BITS ]] && ZNMASK="/$BITS" else ZNMASK=${ZNMASK##*[[:space:]]} fi fi if [[ -z $ZNMASK ]]; then Log.fatal "Unable to determine the netmask for ${ZIP}. Either set" \ 'ZNMASK explicitly or add an entry to /etc/inet/netmasks or your' \ 'name service.' exit 2 fi BITS=${ getNetmaskBits $ZNMASK; } if [[ -z $BITS ]]; then Log.fatal "Invalid netmask (ZNMASK=$ZNMASK). Format /N or N.N.N.N" exit 2 fi ZNMASK="/$BITS" if [[ -z $ZSEARCH ]]; then ZSEARCH=${ getprop config/search dns/client:default; } if [[ -z $ZSEARCH ]]; then ZSEARCH="$ZDOMAIN" else ZSEARCH=${ZSEARCH//\"} # if zone domain is not included in ZSEARCH it is probably wrong [[ -n $ZDOMAIN && ${ZSEARCH//$ZDOMAIN} == $ZSEARCH ]] && \ ZSEARCH=$ZDOMAIN fi fi if [[ -z $ZDNSRVR ]]; then ZDNSRVR=${ getprop config/nameserver dns/client:default; } [[ -z $ZDNSRVR ]] && ZDNSRVR="127.0.0.1" fi if [[ -z $ZDEFROUT ]]; then ZDEFROUT=$( route -n get default 2>/dev/null | \ awk '/gateway:/ { print $2 }' ) # it would be assigned to the $VNIC IF and thus might be wrong ZDEFROUT=${ matchIP "$ZIP$ZNMASK" "$ZDEFROUT"; } fi } Man.addVar ZPATH 'Absolute path wrt. global zone, where the new zone should be mounted. Default and Convention: \a$ZBASE/$ZNAME\a.' # Just defined here, to get hte default listed when called with -l. Gets # overwritten in checkZonePathes() typeset ZPATH="$ZBASE/$ZNAME" Man.addVar ZFSNAME 'Name of the root ZFS of the new zone (which gets mounted on \a$ZPATH\a). Gets determined automatically, if not explicitly set.' [[ -z $ZFSNAME ]] && typeset ZFSNAME='' # make sure it exists Man.addFunc checkZonePathes '' '[+NAME?checkZonePathes - check zone path settings] [+DESCRIPTION?Checks, whether \bZBASE\b is set and points to a directory. Also sets \bZPATH\b to its default value \a$ZBASE/$ZNAME\a. If \bZFSNAME\b is set, it gets checked, whether it already exists. If not it gets created with the mountpoint set to \a$PATH\a, otherwise the function verifies, that the mountpoint is set to \a$PATH\a. If \bZFSNAME\b is not already set (the usual case), it gets deduced from \a$ZPATH\a if it already exists, otherwise from \a$ZBASE\a. On error, this function logs a message and calls \bexit()\b.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage ZBASE ZNAME ZPATH ZFSNAME; }" '} [+SEE ALSO?\bdf\b(1)] ' function checkZonePathes { if [[ -z $ZBASE ]]; then Log.fatal 'zone base path ZBASE is not set.' exit 3 fi if [[ ! -d $ZBASE ]]; then Log.fatal "zone base path $ZBASE does not exist." \ 'The following command might be used to create it:\n\t' \ '+ zfs create -o mountpoint=/zones -o compression=on rpool/zones' exit 3 fi # Need absolute cononical path ZBASE=$( cd $ZBASE && print "$PWD" ) [[ -z $ZBASE ]] && Log.fatal 'Unable to determine the absolute path of' \ "'$ZBASE' (ZBASE)" && exit 3 ZPATH=${ZBASE}/$ZNAME typeset -a LIST if [[ -n $ZFSNAME ]]; then if [[ ${ZFSNAME:0:1} == '/' ]]; then Log.fatal "Invalid ZFSNAME '$ZFSNAME'" exit 3 fi LIST=( ${ zfs list -H -o name,mountpoint $ZFSNAME 2>/dev/null ; } ) if [[ -z ${LIST[0]} ]]; then zfs create -o mountpoint=$ZPATH $ZFSNAME || exit 3 Log.info "Using ZFS $ZFSNAME ..." return fi if [[ ${LIST[1]} != ${ZPATH} ]]; then # too hot to just reset it Log.fatal "ZFS $ZFSNAME exists but its mountpoint is != $ZPATH" exit 3 fi Log.info "Using ZFS $ZFSNAME ..." return fi LIST=( ${ zfs list -H -o name,mountpoint $ZPATH 2>/dev/null ; } ) ZFSNAME=${LIST[0]} if [[ -n $ZFSNAME ]]; then if [[ ${LIST[1]} == ${ZPATH} ]]; then Log.info "Using existing ZFS $ZFSNAME ..." return fi # may be a relict (mountpoint) from a previous zfs rmdir ${ZPATH} || \ { Log.fatal "$ZPATH is not a separate ZFS." ; exit 3 ; } fi # created on demand, but than $ZBASE should be a separate ZFS LIST=( ${ zfs list -H -o name,mountpoint $ZBASE 2>/dev/null ; } ) ZFSNAME=${LIST[0]} if [[ -z $ZFSNAME ]]; then Log.fatal "Unable to determine the name of the ZFS ${ZBASE}." exit 3 fi if [[ ${LIST[1]} != $ZBASE ]]; then Log.fatal "$ZBASE (ZBASE) should be a separate ZFS." \ 'The following command might be used to create it:\n\t' \ "+ zfs create -o mountpoint=$ZBASE -o compression=on rpool/zones" exit 3 fi ZFSNAME+="/$ZNAME" Log.info "Using ZFS $ZFSNAME ..." } Man.addVar TMPDIR 'Directory where temporary files like manifests and profiles should be stored. See also \bcreateTempdir()\b.' Man.addVar USEFIXTMP 'If != 0 \a/tmp/$ZNAME\a will be used as directory for storing temporary files and files used to finally create the zone. Also the directory will not be removed when the script terminates. Otherwise the result of \bmktemp\b(1) will be used instead. Default: \b0\b.' integer USEFIXTMP=0 Man.addFunc createTempdir '' '[+NAME?createTempdir - prepare a directory for temporary files] [+DESCRIPTION?Creates a directory to store temporary files like manifests and profiles and misc. snippets. \bTMPDIR\b gets set to this directory. If the directory already exists it gets removed completely first. If the directory can not be created or removed, this function logs a message and calls \bexit()\b.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage USEFIXTMP TMPDIR; }" '} [+SEE ALSO?\brm\b(1), \bmktemp\b(1), \bcleanup()\b] ' function createTempdir { if (( $USEFIXTMP )); then TMPDIR=/tmp/${ZNAME} ; rm -rf $TMPDIR || exit 1 ; mkdir $TMPDIR if (( $? != 0 )); then Log.fatal 'Unable to create temp directory.' exit 2 fi return fi TMPDIR=$(mktemp -d /tmp/$ZNAME.XXXXXX) if [ -z "$TMPDIR" ]; then Log.fatal 'Unable to create temp directory.' exit 2 fi } Man.addFunc cleanup '' '[+NAME?cleanup - cleanup temporary files and directories] [+DESCRIPTION?Removes the directory \a$TMPDIR\a unless \aUSEFIXTMP\a != 0 or \a$TMPDIR\a is not a sub directory of \b/tmp/\b. Per default it gets automatically called on script termination.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage USEFIXTMP TMPDIR; }" '} [+SEE ALSO?\brm\b(1), \bcreateTempdir()\b] ' function cleanup { # we do that for /tmp sub dirs, only [[ -z $TMPDIR || ! -d $TMPDIR ]] && return (( $USEFIXTMP == 0 )) && [[ -n $TMPDIR && ${TMPDIR#/tmp/} != $TMPDIR ]] && \ rm -rf "$TMPDIR" && return Log.warn "The directory $TMPDIR still exists. Please delete it manually!" } Man.addFunc normalizePkgName '' '[+NAME?normalizePkgName - normalize a package name] [+DESCRIPTION?If the given package name \apkgName\a does not start with does not start with \bpkg:\b, it gets prefixed with \bpkg://solaris/\b, if it neither starts with / nor with //, with \bpkg:\b otherwise and finally printed out.] \n\n\apkgName\a\n\n [+SEE ALSO?\baddPkg()\b, \brmPkg()\b] \n\n\avname\a \apkgName\a' function normalizePkgName { typeset X=$1 if [[ ${X:0:4} != 'pkg:' ]]; then if [[ ${X:0:1} != '/' ]]; then X="pkg://solaris/$X" # e.g. network/telnet elif [[ ${X:0:2} != '//' ]]; then X="pkg://solaris$X" # e.g. /network/telnet else X="pkg:$X" # e.g. //lnf/primary-admin fi fi print $X } Man.addVar FBMANIFEST 'Basename of the AI manifest to use. (read-only)' [[ ${ uname -v ; } == '11.0' ]] && \ FBMANIFEST='0_manifest-11.0.xml' || FBMANIFEST='0_manifest.xml' typeset -r FBMANIFEST Man.addVar MANIFEST 'AI manifest, which should be used for the zone setup. Default: \a$SDIR/$ZNAME\a\b-manifest.xml\b if readable, otherwise \a$SDIR/$FBMANIFEST\a.' Man.addVar AIM_DTD 'Gets unset to avoid side-effects to \baimanifest\b(1M).' unset AIM_DTD Man.addVar AIM_SW 'XPATH to the element in the AI manifest, which contains the names of the packages to [un]]install. See also \baimanifest\b(1M). (read-only)' typeset -r AIM_SW=/auto_install/ai_instance/software/software_data # no API: vname vname fname function checkPkgAction { typeset -n ACTION=$1 TAG=$2 if [[ ! ${ACTION} =~ ^(install|uninstall|avoid|unavoid|reject)$ ]]; then Log.fatal "$3() expects as first argument either 'install'," \ "'uninstall', 'avoid', 'unavoid' or 'reject'" exit 2 fi if [[ ${ACTION} == 'reject' ]]; then TAG=${ uname -v ; } if [[ ${TAG} == '11.0' ]]; then # 11.0 AI does not support pkg install --reject ACTION='uninstall' TAG='name' else # since 11.1 ACTION='install' TAG='reject' fi else TAG='name' fi } Man.addFunc addPkg '' '[+NAME?addPkg - add the named package to the corresponding software_data section of the AI zone manifest.] [+DESCRIPTION?This function should be used to modify the generated AI manifest (\a$MANIFEST\a) used to setup the zone. It adds the given \apkg_frmi\a to the software_data[action="\acommand\a"]] element, which actually corresponds to the \bpkg\b(1) \acommand\a used when installing the zone. Since AI before Solaris 11.1 does not support the pkg install --reject option, this function sets the \acommand\a to \buninstall\b on Solaris 11.0 boxes, to \binstall\b and corresponding children to instead of otherwise, if the given \acommand\a is \breject\b. The \apkg_frmi\a is required to be absolute and must not have a version suffix (e.g. /shell/tcsh). The prefix \bpkg:\b is optional. If an error occurs, this function calls \bexit()\b.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage MANIFEST AIM_SW; }" '} [+SEE ALSO?\bnormalizePkgName()\b, \baimanifest\b(1M), \bprepareManifest()\b, \bpkg\b(1)] \n\n{\binstall\b|\buninstall\b|\bavoid\b|\bunavoid\b|\breject\b} \apkg_frmi\a ... ' function addPkg { typeset SACTION=$1 PKG TAG checkPkgAction SACTION TAG 'addPkg' typeset -x AIM_MANIFEST=${MANIFEST} integer ERRORS=0 shift 1 while [[ -n $1 ]]; do PKG=${ normalizePkgName $1 ; } aimanifest add "${AIM_SW}[@action='${SACTION}']/${TAG}" "${PKG}" (( $? != 0 )) && $(( ERRORS++ )) shift 1 done if (( ERRORS != 0 )); then Log.fatal 'Please fix package names!' exit 3 fi } Man.addFunc rmPkg '' '[+NAME?rmPkg - remove the named package from the corresponding software_data section of the AI zone manifest.] [+DESCRIPTION?This function should be used to modify the generated AI manifest (\a$MANIFEST\a) used to setup the zone. It removes the given \apkg_frmi\a from the software_data[action="\acommand\a"]] element, which actually corresponds to the \bpkg\b(1) \acommand\a used when installing the zone. Since AI before Solaris 11.1 does not support the pkg install --reject option, this function sets the \acommand\a to \buninstall\b on Solaris 11.0 boxes, to \binstall\b and corresponding children to instead of otherwise, if the given \acommand\a is \breject\b. The \apkg_frmi\a is required to be absolute and must not have a version suffix (e.g. /shell/tcsh). The prefix \bpkg:\b is optional. If an error occurs, this function calls \bexit()\b.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage MANIFEST AIM_SW; }"'} [+SEE ALSO?\bnormalizePkgName()\b, \bprepareManifest()\b, \bpkg\b(1)] \n\n{\binstall\b|\buninstall\b|\bavoid\b|\bunavoid\b|\breject\b} \apkg_frmi\a ... ' function rmPkg { typeset SACTION="$1" PKG MSG TAG checkPkgAction SACTION TAG 'rmPkg' typeset -x AIM_MANIFEST="${MANIFEST}" shift 1 while [[ -n $1 ]]; do PKG=${ normalizePkgName "$1" ; } MSG=${ aimanifest set \ "${AIM_SW}[@action='${SACTION}']/${TAG}='${PKG}'" "" 2>&1; } [[ -n ${MSG} ]] && Log.warn "${PKG} not found in the " \ "software_data[@action=${SACTION}] element - not removed!" shift 1 done } Man.addFunc prepareManifest '' '[+NAME?prepareManifest - copy the AI manifest to use to a temp directory and apply common modifications to it.] [+DESCRIPTION?Copies \a$MANIFEST\a to $TMPDIR/$FBMANIFEST, verifies it and adds common packages like tcsh, gnu-sed and gnu-patch to its install section. If \a$MANIFEST\a is not yet set, a default alternative will be used. If an appropriate manifest cannot be found, this function logs a message and calls \bexit()\b. Otherwise, on success, \a$MANIFEST\a gets set to the path of the modified copy.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage MANIFEST TMPDIR FBMANIFEST SDIR ZNAME; }" '} [+SEE ALSO?\baimanifest\b(1M), \baddPkg()\b] ' function prepareManifest { if [[ -n $MANIFEST ]]; then if [[ ! -r $MANIFEST ]]; then Log.fatal "Manifest $MANIFEST not found." exit 1 fi [[ $MANIFEST == $TMPDIR/$FBMANIFEST ]] && return elif [[ -r $SDIR/${ZNAME}-manifest.xml ]]; then MANIFEST="$SDIR/${ZNAME}-manifest.xml" elif [[ -r $SDIR/$FBMANIFEST ]]; then MANIFEST="$SDIR/$FBMANIFEST" else Log.fatal "Default manifest $SDIR/$FBMANIFEST not found." exit 1 fi Log.info "Using manifest $MANIFEST ..." typeset -x AIM_MANIFEST="$TMPDIR/$FBMANIFEST" aimanifest load "$MANIFEST" (( $? == 0 )) || exit 2 MANIFEST=$AIM_MANIFEST addPkg install '/shell/tcsh' addPkg install '/text/gnu-sed' addPkg install '/text/gnu-patch' } Man.addFunc startBundle '' '[+NAME?startBundle - print out the prolog of a service bundle] [+DESCRIPTION?Just emits the xml declaration, DOCTYPE and start tag.] ' function startBundle { cat < EOF } Man.addFunc endBundle '' '+NAME?endBundle - print out the epilog for a service bundle] [+DESCRIPTION?Just emits the end tag.] ' function endBundle { cat < EOF } Man.addFunc createService_SysID '' '[+NAME?createService_SysID - print out a service element for system/identity] [+DESCRIPTION?Just emits a \bsystem/identity\b service element for a service config bundle for the given node (arg1). Does nothing if the argument is empty or omitted.] \n\n[\anodename\a]\n\n [+SEE ALSO?\bservice_bundle\b(4)] ' function createService_SysID { if [[ -z $1 ]]; then Log.warn 'Missing arg1 (nodename). system/identity service element' \ 'not created.' return fi cat < EOF } Man.addFunc createService_IPv4IF '' '[+NAME?createService_IPv4IF - print out a service element for network/install] [+DESCRIPTION?Just emits a \bnetwork/install\b service element for a service config bundle for the given VNIC (arg2), IP/netmask (arg3) and defaultrouter (arg3). Does nothing if no arguments are given or arg2 is empty. If the zone should get its IP address via DHCP, set arg3 to \bdhcp\b. If arg4 is missing, no default_route entry gets emitted. The arg1 is the name of the instance to set.] \n\n\aVNIC_Name\a { \aIPv4_address/netmask\a | \bdhcp\b } [\adefaultrouter_IPv4\a]\n\n [+SEE ALSO?\bservice_bundle\b(4)] ' function createService_IPv4IF { typeset INSTNAME="$1" NIC="$2" ADDR="$3" TYPE='static' DEFROUT="$4" VERS='' if [[ -z ${NIC} ]]; then Log.warn 'Missing arg2 (VNIC). network/install service element' \ 'not created.' return fi [[ -z ${ADDR} || ${ADDR} == 'dhcp' ]] && TYPE=dhcp [[ -z ${INSTNAME} ]] && INSTNAME='default' [[ ${TYPE} == 'static' && -z ${ADDR} ]] && \ Log.warn "An IP address is required for VNIC ${NIC}." VER=${NIC#*/} [[ -z ${.sh.match} ]] && VER='v4' cat< EOF if [[ ${TYPE} == 'static' && -n ${ADDR} ]]; then cat< EOF fi [[ -n ${DEFROUT} ]] && cat< EOF cat< EOF } Man.addVar RTIMEOUT 'Timeout for DNS lookups which finally goes an option into the \b/etc/resolv.conf\b of the new zone. Default: \b3\b (max. 30) seconds. See also \bresolv.conf\b(4), http://iws.cs.uni-magdeburg.de/~elkner/s11/dns.html .' typeset -i RTIMEOUT=3 Man.addVar RATTEMPTS 'Number of attempts to resolve a hostname before giving up. Finally goes an option into \b/etc/resolv.conf\b. Default: \b1\b (max. 5). See also \bresolv.conf\b(4), http://iws.cs.uni-magdeburg.de/~elkner/s11/dns.html .' typeset -i RATTEMPTS=1 Man.addFunc createService_DNSclient '' '[+NAME?createService_DNSclient - print out service elements for system/name-service/switch and network/dns/client] [+DESCRIPTION?Just emits a \bsystem/name-service/switch\b and \bnetwork/dns/client\b service element for a service config bundle for the given DNS search string (arg1: domains) and DNS servers (arg2: IPs). Both strings are expected to be a whitespace separated list with one or more entries.] \n\n\aDNS_search_param_list\a \aDNS_server_list\a\n\n [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage RTIMEOUT RATTEMPTS; }" '} [+SEE ALSO?\bservice_bundle\b(4), \bresolv.conf\b(4), http://iws.cs.uni-magdeburg.de/~elkner/s11/dns.html] ' function createService_DNSclient { typeset X XDF='' XDN='' typeset -a XD=( $1 ) if (( ${#XD[@]} == 0 )); then Log.warn 'Missing arg1 (DNS search parameter).' \ 'system/name-service/switch and network/dns/client service' \ 'element not created.' return fi typeset -a XN=( $2 ) if (( ${#XN[@]} == 0 )); then Log.warn 'Missing arg2 (DNS servers). system/name-service/switch and' \ 'network/dns/client service element not created.' return fi integer MAX=${#XD[@]} i for (( i=0; i < MAX; i++ )); do X=${XD[$i]} XDF+="" done (( MAX > 6 )) && Log.warn 'libresolv will use the first 6 domains from' \ 'the search parameter (arg1), only!' MAX=${#XN[@]} for (( i=0; i < MAX; i++ )); do XDN+="" done (( MAX > 3 )) && Log.warn 'libresolv will use the first 3 nameserver' \ '(arg2), only!' cat< $XDF $XDN EOF } Man.addVar BASEPROF 'Basename of the common service configuration profile to use. (read-only)' typeset -r BASEPROF='0_sc-profile.xml' Man.addVar SYSIDPROF 'Basename of the system/identity service configuration profile to use. (read-only)' typeset -r SYSIDPROF='10_SYSID_profile.xml' Man.addVar VNICPROF 'Basename of the service configuration profile to use for the primary VNIC incl. IPv4 settings. (read-only)' typeset -r VNICPROF='20_VNIC_profile.xml' Man.addVar DNSCPROF 'Basename of the DNS service configuration profile to use. (read-only)' typeset -r DNSCPROF='30_DNSC_profile.xml' Man.addFunc prepareProfiles '' '[+NAME?prepareProfiles - prepare service configuration profiles for zone setup] [+DESCRIPTION?Similar to prepareManifest this function copies all relevant profiles (\a$BASEPROF\a, \a$ZNAME-profile*.xml\a) from \a$SDIR\a to \a$PROFILE_DIR\a and creates the profiles for the nodename (\a$SYSIDPROF\a), the primary VNIC (\a$VNICPROF\a) and DNS client (\a$DNSCPROF\a) within the same folder. After zone setup all those files can be found within \b/etc/svc/profile/site/\b of the zone. \aPROFILE_DIR\a gets set/overwritten by this function.] [+?\bNOTE\b: AI is pretty dumb: It supports the autom. configuration of a single VNIC, only! All others need to be configured manually or using a post install script.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage TMPDIR PROFILE_DIR SDIR BASEPROF ZNAME SYSIDPROF ZHNAME VNICPROF ZVNIC ZIP ZNMASK ZDEFROUT DNSCPROF ZSEARCH ZDNSRVR ZDOMAIN; }" '} [+SEE ALSO?\bmkdir\b(1), \bcp\b(1), \bstartBundle()\b, \bendBundle()\b, \bcreateService_SysID()\b, \bcreateService_IPv4IF()\b, \bcreateService_DNSclient()\b] ' function prepareProfiles { Log.info 'Preparing service configuration profiles ...' [[ ! -d ${TMPDIR} ]] && createTempdir PROFILE_DIR="${TMPDIR}/profiles" mkdir ${PROFILE_DIR} cp ${SDIR}/${BASEPROF} ${ZNAME}-profile*.xml ${PROFILE_DIR}/ 2>/dev/null typeset PROF=${PROFILE_DIR}/${SYSIDPROF} OBJ X startBundle >${PROF} createService_SysID ${ZHNAME} >>${PROF} endBundle >>${PROF} typeset PROF=${PROFILE_DIR}/${VNICPROF} startBundle >${PROF} print ''>>${PROF} createService_IPv4IF '' "${ZVNIC}" "${ZIP}${ZNMASK}" "${ZDEFROUT}" >>${PROF} for OBJ in "${!ADD_ZVNICS[@]}"; do [[ -z ${OBJ} ]] && continue X=${ADD_ZVNICS["${OBJ}"]} [[ -z $X ]] && X='dhcp' createService_IPv4IF "${OBJ%/*}" "${OBJ}" $X done >> ${PROF} print ''>>${PROF} endBundle >>${PROF} PROF=${PROFILE_DIR}/${DNSCPROF} startBundle >${PROF} createService_DNSclient "${ZSEARCH}" "${ZDNSRVR}" >>${PROF} endBundle >>${PROF} } Man.addVar ZCMD 'Basename of the zone command file, which gets finally used to setup the zone. (read-only)' typeset -r ZCMD='zone.cmd' Man.addVar ZPRIVS 'A comma separated list of additional privileges, which the zone should have (e.g. "default,sys_time"). Default: ""' unset ZPRIVS Man.addFunc createZoneConfig '' '[+NAME?createZoneConfig - Create a common zone config command file and configures the new zone] [+DESCRIPTION?Create the zone config command file \a$ZCMD\a in \a$TMPDIR\a and executes it to configure the new zone. Additional adjustments to the zone configuration can still be made using "zonecfg -z \a$ZNAME\a". If \a$ZHOSTID\a is not set, the corresponding zonecfg command to explicitly set the hostid will not be used. Same thing for \aZPRIVS\a - if unset no \blimitpriv\b command will be used.. If zonecfg fails, this function calls \bexit()\b.] \n\n[\ashort_zone_description\a]\n\n [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage ZHOSTID ZPRIVS ZNAME ZPATH ZVNIC DATADIR TMPDIR ZCMD; }" '} [+SEE ALSO?\bzonecfg\b(1M), \baddDataDirs2zone()\b] ' function createZoneConfig { typeset X='Template zone' HCMD PCMD OBJ [[ -n $1 ]] && X="$1" [[ -n ${ZHOSTID} ]] && HCMD="set hostid=\"${ZHOSTID}\"" [[ -n ${ZPRIVS} ]] && PCMD="set limitpriv=\"${ZPRIVS}\"" cat >${TMPDIR}/${ZCMD}<>${TMPDIR}/${ZCMD} done fi addDataDirs2zone >>${TMPDIR}/${ZCMD} print 'verify\ncommit\n' >>${TMPDIR}/${ZCMD} Log.info "Configure zone using ${TMPDIR}/${ZCMD} ..." zonecfg -z ${ZNAME} -f ${TMPDIR}/${ZCMD} if (( $? != 0 )); then [[ -f /etc/zones/${ZNAME}.xml ]] && \ Log.fatal 'To remove the zone configration in the system, you' \ 'may use this script or the command:\n\t' \ "+ zonecfg -z ${ZNAME} delete" exit 6 fi } Man.addFunc installZone '' '[+NAME?installZone - apply final manifest fixes and install the zone] [+DESCRIPTION?Applies final fixes to the AI manifest (so that the buggy AI stuff/python xml parser swollows it), and finally calls \bzoneadm\b(1M) to install the zone using the service configuration files in \a$PROFILE_DIR\a as well as the mangled \a$MANIFEST.$$\a. If install fails, this function calls \bexit()\b.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage TMPDIR MANIFEST PROFILE_DIR ZNAME; }" '} [+SEE ALSO?\bxsltproc\b(1), \bzoneadm\b(1M), \bcreateZoneConfig()\b, \bprepareProfiles()\b, \bprepareManifest()\b] ' function installZone { Log.info 'Preprocessing manifest for AI ...' # since "smart" AI developers do not use a real XML parser (but other # bogus stuff)/ is unable to handle valid XML manifests, we need to make # sure to feed something, what it can handle... cat >$TMPDIR/manifest.xslt< EOF xsltproc -o $MANIFEST.$$ $TMPDIR/manifest.xslt $MANIFEST Log.info "Installing zone $ZNAME ..." # install [-m manifest] [-c profile.xml | dir] # install {-a archive|-d path} {-p|-u} [-s|-v] [-c profile.xml | dir] zoneadm -z $ZNAME install -c ${PROFILE_DIR}/ -m $MANIFEST.$$ if (( $? != 0 )); then Log.fatal 'To remove the zone incl. its configuration you may use' \ 'this script or do it manually using the following commands:\n\t' \ "+ zoneadm -z $ZNAME uninstall\n\t + zonecfg -z $ZNAME delete" exit 8 fi # if sudo package is not installed, svc:/system/config-user:default (i.e. # useradd) will fail because of the missing /etc/sudoers.d/ and # thus zone will not boot successfully. So we fix it before reboot # (fixed in 11.1 SRU 6.4): typeset DIR=$ZPATH/root//etc/sudoers.d [[ ! -d $DIR ]] && mkdir -p $DIR && chmod 0750 $DIR && \ Log.info 'useradd bug workaround applied (requires /etc/sudoers.d/).' } Man.addFunc addPrimaryAdministrator '' '[+NAME?addPrimaryAdministrator - add the "Primary Administrator" role to the zone] [+DESCRIPTION?Get back the "Primary Administrator" role to the zone, which was removed from Solaris 11 for questionable reasons. It also adds a symlink from \bpfexec\b(1) to "\b+\b" for admin convinience. This is a file based alternative wrt. to the \b//lnf/primary-admin\b package, e.g. if one has no access to the related repository or does not wanna have the corresponding publisher in the zone.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage ZPATH; }" '} ' function addPrimaryAdministrator { Log.info 'Adding "Primary Administrator" role ...' print 'Primary Administrator:solaris:cmd:::*:uid=0;gid=0' \ >$ZPATH/root/etc/security/exec_attr.d/primary-administrator print 'Primary Administrator:::Can perform all administrative tasks:auths=solaris.*,solaris.grant;help=RtPriAdmin.html' \ >$ZPATH/root/etc/security/prof_attr.d/primary-administrator ln -s ./pfexec $ZPATH/root/usr/bin/+ } Man.addFunc bootZone '' '[+NAME?bootZone - boot the zone and wait until it is fully configured] [+DESCRIPTION?Boot the zone \a$ZNAME\a and wait until it is fully configured. This step is important, because the per profiles configured environment incl. config/legacy files gets populated (see "svccfg apply"). Certain modifications of the zone should not be made from outside (even if commands like "pkg -R $ZPATH/root ..." allow that). However, one may trigger them from the global zone using zlogin when the zone is running.] [+?The optional \asvcs\a can be used to wait for additional service(s) to come online. Per default this function waits for self-assembly-complete, filesystem/local and config-user.] [+?If booting the zone fails, this function exits with 1.] [t:timeout?Number of seconds to wait for the zone to come up (Default: 60). If required services are not online after the timeout, this function exits with 2 and usually causes the script to terminate.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage ZNAME; }" '} [+SEE ALSO?\bzoneadm\b(1M), \bzlogin\b(1)] \n\n[svcs]...\n ' function bootZone { Log.info "Booting zone $ZNAME ..." Log.info 'To monitor boot progress you may use the following command:\n\t' \ "+ zlogin -e^ -C $ZNAME" zoneadm -z $ZNAME boot -W if (( $? != 0 )); then Log.fatal 'failed.' exit 1 fi integer TIMEOUT=60 IDX typeset STATE='' X while getopts -a 'bootZone' "${ print ${Man.FUNC['bootZone']}; }" X ; do case $X in t) TIMEOUT=$OPTARG ; (( $TIMEOUT < 1 )) && $TIMEOUT=60 ;; esac done X=$(( OPTIND - 1 )) shift $X typeset -a SVCS=( 'svc:/milestone/self-assembly-complete:default' 'svc:/system/filesystem/local:default' 'svc:/system/config-user:default' ) while [[ -n $1 ]]; do SVCS+="$1" shift done Log.info 'Waiting for zone assembly completion ...' SECONDS=0 while (( $SECONDS <= $TIMEOUT )); do print -n '.' sleep 1 for (( IDX=${#SVCS[@]}-1 ; IDX >= 0; IDX--)) ; do X=${SVCS[$IDX]} [[ -z $X ]] && continue STATE=${ zlogin $ZNAME svcs -Ho state "$X" 2>/dev/null; } [[ $STATE != 'online' ]] && break || SVCS[$IDX]='' done [[ $STATE == 'online' ]] && { IDX=1 ; break ; } || IDX=0 done print "${SECONDS}s" if (( ! $IDX )); then X=" ${SVCS[*]}" X=${X// /$'\n\t'} Log.fatal 'One or more of the following services not online' \ 'after '$TIMEOUT's:'"$X" Log.fatal "Use 'zlogin ${ZNAME}' to login into the zone and than" 'execute "svcs -xv" and check the log file to see, what'"'s going" \ 'wrong!' exit 2 fi } Man.addFunc rebootZone '' '[+NAME?rebootZone - reboot the zone and wait until ssh service has been started] [+DESCRIPTION?Reboots the zone and waits until it is up again, i.e. the ssh service of the zone is running.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage ZNAME; }" '} [+SEE ALSO?\bzoneadm\b(1M), \bzlogin\b(1)] ' function rebootZone { Log.info 'Rebooting zone ...' zoneadm -z $ZNAME reboot typeset STATE= integer TIMEOUT=120 while [[ $STATE != "online" ]]; do print -n '.' sleep 1 #STATE=$(zlogin $ZNAME svcs -Ho state \ # svc:/system/manifest-import:default 2>/dev/null) STATE=${ zlogin $ZNAME svcs -Ho state \ svc:/network/ssh:default 2>/dev/null ; } (( TIMEOUT-- )) if (( TIMEOUT < 0 )); then Log.warn 'Reboot failed.' return fi done print Log.info 'Done.' } Man.addFunc haltZone '' '[+NAME?haltZone - halt the zone when the ENTER key gets pressed.] [+DESCRIPTION?Once called it waits until the ENTER key gets pressed. After that it halts the zone. Usually (if not testing zone setup scripts) one should avoid this big hammer and use "+ zlogin \a$ZNAME\a init 5" instead.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage ZNAME; }" '} [+SEE ALSO?\bzoneadm\b(1M), \bread\b(1), \brebootZone()\b] ' function haltZone { typeset LINE Log.info 'Press to halt the zone ...' read LINE Log.info 'Halting zone ...' zoneadm -z $ZNAME halt if (( $? != 0 )); then Log.fatal 'failed.' fi } Man.addFunc destroyZone '' '[+NAME?destroyZone - halt, uninstall the zone and delete its configuration.] [+DESCRIPTION?This function halts a zone (if running), uninstalls it and finally deletes its zone configuration.] [+ENVIRONMENT VARIABLES]{' "${ Man.varUsage ZNAME LIBNAME SCRIPT; }" '} [+SEE ALSO?\bzoneadm\b(1M), \bzonecfg\b(1M)] ' function destroyZone { [[ -z ${ZNAME} || ${LIBNAME} == ${SCRIPT} ]] && exit 0 typeset X=${ zoneadm -z $ZNAME list -p 2>/dev/null ; } typeset -a ZINFO=( ${X//:/ } ) typeset STATE=${ZINFO[2]} integer TIMEOUT=0 DOWN=1 if [[ -z ${STATE} ]]; then Log.fatal "There is no zone $ZNAME on this machine!" return fi if [[ ! ${STATE} == @(installed|unavailable|incomplete|configured) ]]; then # ready|running DOWN=0 Log.info "Halting zone $ZNAME ..." zoneadm -z $ZNAME halt for (( TIMEOUT=0; TIMEOUT < 10; TIMEOUT++ )); do print -n '.' sleep 1 X=${ zoneadm -z $ZNAME list -p 2>/dev/null | cut -f3 -d: ; } [[ $X == @(installed|unavailable|incomplete|configured) ]] \ && DOWN=1 && break done print if (( $DOWN == 0 )); then Log.fatal "Unable to halt zone $ZNAME." exit 2 fi else X=${STATE} fi if [[ $X != configured ]]; then # interactiv is desired ! # installed|unavailable|incomplete zoneadm -z ${ZNAME} uninstall || exit $? fi zonecfg -z $ZNAME delete X=${ zfs list -H -o name ${ZINFO[3]} 2>/dev/null ; } [[ -n $X ]] && zfs destroy -r "$X" } Man.addFunc doPackageTest '' '[+NAME?doPackageTest - testet, ob die Paketzusammenstellung stimmt.] [+DESCRIPTION?Zieht sich aus dem laufenden Script alle \baddPkg\b und \brmPkg\b Befehle zeilenweise heraus und erstellt unter Zuhilfenahme vom StandardManifest \b0_manifest.xml\b eine Liste der zu [de]]installierende Pakete. Dieser Set wird dem pkg resolver übergeben, der ausrechnet, ob die Paketzusammenstellung für eine erfolgreiche Zonen-Installation benutzt werden kann. Das ist manchmal hilfreich, da zoneinstall aka AI oftmals nicht angibt, woran nun eine Zonen-Installation gescheitert ist und ist bedeutend schneller als eine echte Zoneninstallation.] ' function doPackageTest { [[ $SCRIPT == $LIBNAME ]] && return 0 createTempdir prepareManifest typeset SFUNC SACTION PKGS /usr/xpg4/bin/egrep \ '^[[:space:]]*(add|rm)Pkg[[:space:]]+(un)?install[[:space:]]+' $SCRIPT|\ while read SFUNC SACTION PKGS ; do $SFUNC $SACTION $PKGS done typeset XSLT=$TMPDIR/extract.xslt cat >$XSLT< facet: EOF typeset PKGS="" REJECT="" A B C TAIL typeset -a FACETS=( ) xsltproc $XSLT $MANIFEST | while read A B C TAIL ; do case $A in facet:) FACETS+=( "$C=$B" );; add:) PKGS+=" $B" ;; del:) REJECT+=" --reject $B" ;; esac done typeset INSTLOG="$TMPDIR/install.log" RES CDIR=$TMPDIR/root \ ARCH=${ uname -p ; } REPO='' \ PUBLISHER STICKY SYSPUB ENABLED TYPE STATUS URI PROXY typeset -A ALL=( ) mkdir $CDIR || exit 1 pkg publisher -H -F tsv | \ while read PUBLISHER STICKY SYSPUB ENABLED TYPE STATUS URI PROXY ; do if [[ $PUBLISHER == 'solaris' ]]; then REPO="$URI" else ALL[$PUBLISHER]="$URI" fi done [[ -z $REPO ]] && Log.warn 'No solaris publisher found' && exit 2 pkg image-create --full --variant variant.opensolaris.zone=nonglobal \ --variant variant.arch=$ARCH --origin=$REPO --publisher solaris=$REPO \ $CDIR >&2 || exit 4 for PUBLISHER in "${!ALL[@]}"; do pkg -R $CDIR set-publisher -g ${ALL[$PUBLISHER]} $PUBLISHER done Log.info "Install log goes to $INSTLOG" print "pkg -R $CDIR change-facet ${FACETS[@]}\n" >$INSTLOG pkg -R $CDIR change-facet "${FACETS[@]}" >>$INSTLOG 2>&1 || exit 1 print "\n\npkg -R $CDIR install -nv --accept ${REJECT} ${PKGS}\n" \ >>$INSTLOG pkg -R $CDIR install -nv --accept ${REJECT} ${PKGS} >>$INSTLOG 2>&1 integer RES=$? cp $INSTLOG /tmp/${ZNAME}.install.log if (( $RES )); then Log.fatal " - see /tmp/${ZNAME}.install.log" exit 2 else Log.info "Should be OK - see /tmp/${ZNAME}.install.log ($CDIR)" fi } . ${LIBNAME%/*}/zcustomize.kshlib . ${LIBNAME%/*}/zskeleton.kshlib ############################################################################## # main ############################################################################## function showUsage { if [[ $2 != LIBX ]]; then getopts -a "$1" "${ print ${Man.FUNC[$2]}; }" OPT --man else typeset XX=' [+?The purpose of this library is to be able to setup adhoc and in a flexible manner non-global zones following common policies/guide lines (schema "F").] [+?Here, schema "F" means basically "reproducable at any time" as well as that per default:]{ [+-?The zone setup script name is always \a$ZNAME-zonesetup.ksh\a, whereby \a$ZNAME\a denotes the name of the zone and its hostname as well.] [+-?The virtual network interfaces (VNIC) used by the zone have always the name of the zone suffixed by an instance number (0..N).] [+-?An exclusive-IP stack is used.] [+-?The admin home of the zone is always \b/local/home/admin\b and accessed directly instead of using an autofs mounted /home/admin to avoid NFS problems early (machine appears to hang).] [+?-One gets the same working and fully functional zone by executing the corresponding script and mounting of relevant ZFS on another machine within minutes.] } [+?Adhoc and flexible means here:]{ [+-?Reduce the boilerplate code in the scripts to a minimum , so that the peculiarities of a zone can be determined fast and easy by just reading the script.] [+-?Reduce the need for adjustments of the scripts to a minimum. Usually all required information for a zone setup are already available in the system or DNS - this library tries to determine/deduce those and make them available by initializing related variables (see ${LIBNAME##*/} -v).] [+-?Having the opportunity to allow required modifications of the standard script by inserting appropriate code on every zone setup/install relevant locations.] [+-?Avoid clone dependencies/complexity/problems. That is why we use even for identical zone not the zone clone features (after a certain amount of updates, the win of HDD space will turn into zero/a loss anyway).]} [+?Steps to create a standard zone, e.g. named "template":]{ [+a)?Add the name of the zone (hostname) to your DNS or \b/etc/inet/hosts\b e.g.: print "10.0.0.1 template" >>/etc/inet/hosts] [+b)?Create the VNIC template0. see also step 3]}{ [+0)?cd $SDIR] [+1)?.$LIBNAME -s > template-zonesetup.ksh] [+2)?chmod 755 template-zonesetup.ksh] [+3)?optional: If there is no VNIC named template0, create it: "./template-zonesetup.ksh -i". It should show a more or less meaningful suggestion to create the VNIC.] [+4)?optional: If the default route of the global zone should not be used, define in ./template-zonesetup.ksh ZDEFROUT und ZNMASK. E.g.:]{ [+?ZDEFROUT=10.0.0.200] [+?ZNMASK=/24 # or] [+?ZNMASK=255.255.255.0]} [+5)?./template-zonesetup.ksh -i] [+6)?zlogin -l root template0] [+7)?optional (can be done within the script as well):]{ [+?passwd -r files root] [+?passwd -r files admin]} } [+?To create an identical zone named "test" but with a different IP address:]{ [+a)?print "10.0.0.2 test" >>/etc/inet/hosts] [+0)?ln -s ./template-zonesetup.ksh test-zonesetup.ksh] [+1)?./test-zonesetup.ksh -i] [+2)?VNIC anlegen] [+3)?./test-zonesetup.ksh -i]} }' typeset X="${ print ${Man.FUNC[LIB]}; }" OPT X=${X%\[h:*\]*} # remove OPTIONS and other trailing stuff getopts -a "$1" "${ print ${X}${XX}; }" OPT --man fi } # shared options X='[h:help?Print this help and exit.] [l:list?Print a list of script relevant environment variables, their current value and exit.] [f:functions?Print the source of all functions currently defined and exit. Just invokes \btypeset -f\b builtin.] [F:fnames?Print a list of all functions currently defined and exit. Just invokes \btypeset +f\b builtin.] [H:usage]:[functionName?Show the usage info for the given function, if available and exit. See also option \a-F\a.] [L:libusage?Print the library usage information and exit.] [D:libdesc?Print an extended description of the '"${LIBNAME##*/}"' library and exit.] [s:script?Print a script for the setup of a standard zone and exit.] [S:Script?Print a script with comments for the setup of a standard zone and exit.] [T:trace]:[fname_list?a comma or whitespace separated list of names of functions to trace. Should be specified as the first switch to be able to trace anything. See also option \b-F\b .] ' Man.addFunc MAIN '' '[+NAME?'"${SCRIPT##*/}"' - setup/destroy the zone \b${ZNAME}\b .] [+DESCRIPTION?Script to setup or destroy the zone \b${ZNAME}\b .] [i:install?Install the non-global zone ${ZNAME}.] [u:uninstall?Destroy and uninstall the non-global zone ${ZNAME}.] [t:test?Test whether the solaris package selection is ok. See also \b'"${SCRIPT##*/}"' -H doPackageTest\b.] '"$X" Man.addFunc LIB '' '[+NAME?'"${LIBNAME##*/}"' - library which makes it easy to create scripts for zone setup/destroy easily.] [+DESCRIPTION?This ksh93 script contains several functions, which makes it much easier to create scripts for zone setup and destruction. When it get sourced in, it tries to determine the full path of the running script (see \a$SCRIPT\a), sets the environment variable \bSDIR\b (if not already set) to the directory, which contains the running script, sets the environment variable \aLIBNAME\a to the full path of this library script, changes automatically the current working directory (CWD) to \a$SDIR\a and finally initializes all required variables. So before this script gets sourced from another script, one should not change the CWD or cd back to the original CWD before the library gets sourced. Otherwise the described mechanismen may not work as expected.] '"$X"'[+ENVIRONMENT VARIABLES?The following variables are used within the library and might be used in the '"${SCRIPT##*/}"':]{' "${Man.VAR[*]}" '} ' ACTION="" X=MAIN [[ $SCRIPT == $LIBNAME ]] && X=LIB while getopts "${ print ${Man.FUNC[$X]}; }" option ; do case "$option" in h) showUsage "${SCRIPT##*/}" $X ; exit 0 ;; L) showUsage "${LIBNAME##*/}" LIB ; exit 0 ;; s) skeleton ; exit 0 ;; S) skeleton -x ; exit 0 ;; H) if [[ ${OPTARG%_t} != $OPTARG ]]; then $OPTARG --man # self-defined types else showUsage "$OPTARG" "$OPTARG" # functions fi exit 0 ;; T) typeset -ft ${OPTARG//,/ } ;; f) typeset -f ; exit 1 ;; F) typeset +f ; exit 1 ;; D) showUsage "${LIBNAME##*/}" LIBX; exit 0 ;; i) ACTION="INSTALL" ;; u) destroyZone ; exit $? ;; l) Man.listVars ; exit ;; t) ACTION="PKGTEST" ;; esac done X=$(( OPTIND - 1 )) shift $X if ! whence gsed >/dev/null ; then Log.fatal 'GNU sed is required.' \ 'Please install this package first! E.g.:\n\t' \ 'pkg install text/gnu-sed\n' exit 1 fi # nothing to do ? Show help if [[ -z $ACTION || $ACTION == HELP ]]; then [[ $LIBNAME == $ZNAME ]] && showUsage "${LIBNAME##*/}" LIB && exit 0 if [[ -n $ZNAME ]]; then showUsage "${SCRIPT##*/}" MAIN exit 0 fi fi [[ $LIBNAME == $SCRIPT ]] && exit 0 if [[ $ACTION == 'PKGTEST' ]]; then doPackageTest exit $? fi trap cleanup EXIT # vim:ts=4 filetype=sh