src/brand/pkgcreatezone
author Tim Foster <tim.s.foster@oracle.com>
Wed, 12 Jan 2011 12:15:37 +1300
branch2010.2H
changeset 2189 dc8c8c2abf35
parent 2124 74a1732c2d10
permissions -rwxr-xr-x
17653 zone install fails with pkg.oracle.com/solaris/support set 17680 image creation should preserve ssl directory if it already exists 17681 image-create warning about -f not specific enough 17683 pkgcreatezone does a chown/chmod on the wrong path

#!/bin/ksh -p
#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#

#
# Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
#

#
# Resetting GZ_IMAGE to something besides slash allows for simplified
# debugging of various global zone image configurations-- simply make
# an image somewhere with the appropriate interesting parameters.
#
GZ_IMAGE=${GZ_IMAGE:-/}
PKG_IMAGE=$GZ_IMAGE
export PKG_IMAGE

. /usr/lib/brand/ipkg/common.ksh

f_a_obs=$(gettext "-a publisher=uri option is obsolete, use -P instead.")
f_pkg5_missing=$(gettext "pkg(5) does not seem to be present on this system.\n")
f_no_pref_publisher=$(gettext "Unable to get global zone preferred publisher information, and none was supplied.\nYou must specify one using the -P option.")
f_key_file=$(gettext "Key file not allowed without -P")
f_cert_file=$(gettext "Cert file not allowed without -P")
f_img=$(gettext "failed to create image\n")
f_pkg=$(gettext "failed to install package\n")
f_interrupted=$(gettext "Installation cancelled due to interrupt.\n")
f_bad_publisher=$(gettext "Syntax error in publisher information.")
f_no_entire_in_pref=$(gettext "Unable to locate the incorporation '%s' in the preferred publisher '%s'.\nUse -P to supply a publisher which contains this package.\n")
f_key_prop=$(gettext "Unable to propagate key %s to %s")
f_cert_prop=$(gettext "Unable to propagate cert %s to %s")
f_get_secinfo=$(gettext "Failed to get key/cert information for publisher %s")
f_nosuch_key=$(gettext "Failed to find key %s")
f_nosuch_cert=$(gettext "Failed to find cert %s")

m_publisher=$(gettext   "   Publisher: Using %s (%s).")
m_cache=$(gettext       "       Cache: Using %s.")
m_image=$(gettext       "       Image: Preparing at %s.")
m_incorp=$(gettext      "Sanity Check: Looking for 'entire' incorporation.\n")
m_key_prop=$(gettext    " Credentials: Propagating %s\n")
m_cert_prop=$(gettext   " Credentials: Propagating %s\n")
m_core=$(gettext	"  Installing: Core System (output follows)\n")
m_more=$(gettext	"  Installing: Additional Packages (output follows)\n")
m_smf=$(gettext		" Postinstall: Copying SMF seed repository ...")
m_more_brokenness=$(gettext " Postinstall: Applying workarounds.")
m_mannote=$(gettext     "        Note: Man pages can be obtained by installing SUNWman")

m_usage=$(gettext "\n        install [-h]\n        install [-c certificate_file] [-k key_file] [-P publisher=uri]\n                [-e extrapkg [...]]\n        install {-a archive|-d path} {-p|-u} [-s|-v]")

m_done=$(gettext      " done.")

trap_cleanup() {
	print "$f_interrupted"
	exit $int_code
}

int_code=$ZONE_SUBPROC_NOTCOMPLETE
trap trap_cleanup INT

extra_packages=""
ZONENAME=""
ZONEPATH=""
pub_and_origins=""
pub_and_mirrors=""

# Setup i18n output
TEXTDOMAIN="SUNW_OST_OSCMD"
export TEXTDOMAIN

KEYDIR=/var/pkg/ssl
PKG=/usr/bin/pkg

#
# Just in case.  This should probably be removed later.
#
[[ ! -x $PKG ]] && fail_incomplete "$f_pkg5_missing"

certfile="None"
keyfile="None"
unset install_archive
unset source_dir
unset msg
unset silent_mode
unset verbose_mode

while getopts "a:c:d:e:hk:P:pR:suvz:" opt; do
	case $opt in
		a)	# We're expecting a path to an archive
			if [[ ! -f $OPTARG ]]; then
				# If old style 'pub=uri' parameter then error.
				echo $OPTARG | egrep -s =
				if (( $? == 0 )); then
					fail_usage "$f_a_obs"
				fi
			fi
			install_archive="-a $OPTARG";;
		c)	certfile="$OPTARG" ;;
		d)	source_dir="-d $OPTARG";;
		e)	extra_packages="$extra_packages $OPTARG" ;;
		h)	fail_usage "";;
		k)	keyfile="$OPTARG" ;;
		P)	pub_and_origins="$OPTARG" ;;
		p)	preserve_zone="-p";;
		R)	ZONEPATH="$OPTARG" ;;
		s)	silent_mode=1;;
		u)	unconfig_zone="-u";;
		v)	verbose_mode="-v";;
		z)	ZONENAME="$OPTARG" ;;
		*)	fail_usage "";;
	esac
done
shift $((OPTIND-1))

if [[ -z $ZONEPATH || -z $ZONENAME ]]; then
	print -u2 "Brand error: No zone path or name"
	exit $ZONE_SUBPROC_USAGE
fi

# XXX shared/common script currently uses lower case zonename & zonepath
zonename="$ZONENAME"
zonepath="$ZONEPATH"

is_brand_labeled
brand_labeled=$?

ZONEROOT=$ZONEPATH/root
secinfo=""

# An image install can't use both -a AND -d...
[[ -n "$install_archive" && -n "$source_dir" ]] &&
    fail_usage "$f_incompat_options" "-a" "-d"

# The install can't be both verbose AND silent...
[[ -n $silent_mode && -n $verbose_mode ]] && \
    fail_usage "$f_incompat_options" "-s" "-v"

# The install can't both preserve and unconfigure
[[ -n $unconfig_zone && -n $preserve_zone ]] && \
    fail_usage "$f_incompat_options" "-u" "-p"

# IPS options aren't allowed when installing from a system image.
if [[ -n "$install_archive" || -n "$source_dir" ]]; then
	[[ -n $pub_and_origins ]] && fail_usage "$f_incompat_options" \
	    "-a|-d" "-P"
	[[ -n "$extra_packages" ]] && \
	    fail_usage "$f_incompat_options" "-a|-d" "-e"
	[[ "$certfile" != "None" ]] && \
	    fail_usage "$f_incompat_options" "-a|-d" "-c"
	[[ "$keyfile" != "None" ]] && \
	    fail_usage "$f_incompat_options" "-a|-d" "-k"
fi

# p2v options aren't allowed when installing from a repo.
if [[ -z $install_archive && -z $source_dir ]]; then
	[[ -n $preserve_zone || -n $unconfig_zone ]] && \
		fail_usage "$f_incompat_options" "default" "-p|-u"
fi

#
# If the user didn't give us a publisher, and there's a preferred publisher set
# for the system, set that as the default.
#
propagate_secinfo=
propagate_extra=
if [[ -z $pub_and_origins ]]; then
	if [[ $keyfile != "None" ]]; then
		fail_usage "$f_key_file"
	fi
	if [[ $certfile != "None" ]]; then
		fail_usage "$f_cert_file"
	fi

	# Look for a preferred online origin.
	tpub_and_origins=$(get_publisher_urls preferred origin)
	[[ $? -eq 0 && -n $tpub_and_origins ]] && \
	    pub_and_origins="$tpub_and_origins"

	# Get preferred mirror information as well if the above succeeded.
	if [[ -n "$tpub_and_origins" ]]; then
		tpub_and_mirrors=$(get_publisher_urls preferred mirror)
		[[ $? -eq 0 && -n $tpub_and_mirrors ]] && \
		    pub_and_mirrors="$tpub_and_mirrors"
	fi

	# Note that later we need to propagate key & cert.
	propagate_secinfo=1

	#
	# since the user didn't specify a publisher, propagate all the
	# publishers that don't have a key.
	#
	propagate_extra=1
fi
[[ -z $pub_and_origins ]] && fail_usage "$f_no_pref_publisher"

# find the remaining publishers in the global zone
publishers_extra_origins=""
publishers_extra_mirrors=""

if [[ -n $propagate_extra ]]; then

	# If cert and key information are ever allowed at the origin or
	# mirror level, then this will have to be changed.
	get_publisher_urls non-preferred origin | \
	    while IFS="=" read pub pub_urls; do
		# skip extra publishers that need a key/cert
		[[ "`get_pub_secinfo $pub`" != "None None" ]] && \
		    continue

		if [[ -z "$publishers_extra_origins" ]]; then
			publishers_extra_origins="$pub=$pub_urls"
		else
			publishers_extra_origins=`printf "%s\n%s" \
			    "$pub=$pub_urls" \
			    "$publishers_extra_origins"`
		fi
	done

	get_publisher_urls non-preferred mirror | \
	    while IFS="=" read pub pub_urls; do
		# skip extra publishers that need a key/cert
		[[ "`get_pub_secinfo $pub`" != "None None" ]] && \
		    continue

		if [[ -z "$publishers_extra_mirrors" ]]; then
			publishers_extra_mirrors="$pub=$pub_urls"
		else
			publishers_extra_mirrors=`printf "%s\n%s" \
			    "$pub=$pub_urls" \
			    "$publishers_extra_mirrors"`
		fi
	done
fi

#
# Crack pub=url into two pieces.
#
echo $pub_and_origins | IFS="=" read publisher pub_origins
if [[ -z $publisher || -z $pub_origins ]]; then
	fail_usage "$f_bad_publisher"
fi
echo $pub_and_mirrors | IFS="=" read ignored pub_mirrors

if [[ -n $propagate_secinfo ]]; then
	#
	# Get the global zone's cert and key (if any) so that we can propagate
	# them into the new image.
	#
	get_pub_secinfo $publisher | read keyfile certfile
	if [[ $? -ne 0 ]]; then
		fail_usage "$f_get_secinfo" $publisher
	fi
fi
#
# Do some sanity checks on key and cert.
#
[[ $keyfile != "None" && ! -f $keyfile ]] && \
    fail_usage "$f_nosuch_key" $keyfile
[[ $certfile != "None" && ! -f $certfile ]] && \
    fail_usage "$f_nosuch_cert" $certfile

#
# Look for the 'entire' incorporation's FMRI in the current image; due to users
# doing weird machinations with their publishers, we strip off the publisher
# from the FMRI if it is present.
# It's ok to not find entire in the current image, since this means the user
# can install pre-release development bits for testing purposes.
#
entire_fmri=$(get_entire_incorp)

#
# Before installing the zone, set up ZFS dataset hierarchy for the zone root
# dataset.
#
create_active_ds

#
# If we're installing from an image, branch off to that installer.
#
if [[ -n $install_archive || -n $source_dir ]]; then
	/usr/lib/brand/ipkg/image_install $ZONENAME $ZONEPATH \
	    $install_archive $source_dir $verbose_mode $silent_mode \
	    $unconfig_zone $preserve_zone
	ii_result=$?

	if (( $ii_result != 0 )); then
		exit $ZONE_SUBPROC_NOTCOMPLETE
	fi
	exit $ZONE_SUBPROC_OK
fi

# Display preferred publisher origin and mirror information.
printf "$m_publisher\n" $publisher "$pub_origins $pub_mirrors"

# Display extra publisher origin and mirror information.  This does mean that
# the extras have to be displayed twice to show mirror information.
if [[ -n "$publishers_extra_origins" ]]; then
	echo "$publishers_extra_origins" | while IFS="=" read pub pub_urls; do
		printf "$m_publisher\n" $pub "$pub_urls"
	done
	if [[ -n "$publishers_extra_mirrors" ]]; then
		echo "$publishers_extra_mirrors" | while IFS="=" read pub pub_urls; do
			printf "$m_publisher\n" $pub "$pub_urls"
		done
	fi
fi

printf "$m_image\n" $ZONEROOT

#
# We copy the credentials from the global zone into the new image
# we're about to create.
#
if [[ $keyfile != "None" ]]; then
	newkeylocation="$KEYDIR/$(basename $keyfile)"
	secinfo="$secinfo -k $newkeylocation"
	printf "$m_key_prop\n" $(basename $keyfile)
	mkdir -p -m 755 $ZONEROOT/$KEYDIR || fail_fatal "$f_key_prop"
	cp $keyfile $ZONEROOT/$newkeylocation || fail_fatal "$f_key_prop"
	chmod 644 $ZONEROOT/$newkeylocation
	chown -h root:root $ZONEROOT/$newkeylocation
fi
if [[ $certfile != "None" ]]; then
	newcertlocation="$KEYDIR/$(basename $certfile)"
	secinfo="$secinfo -c $newcertlocation"
	printf "$m_cert_prop\n" $(basename $certfile)
	mkdir -p -m 755 $ZONEROOT/$KEYDIR || fail_fatal "$f_cert_prop"
	cp $certfile $ZONEROOT/$newcertlocation || fail_fatal "$f_cert_prop"
	chmod 644 $ZONEROOT/$newcertlocation
	chown -h root:root $ZONEROOT/$newcertlocation
fi

#
# Regrettably, since we already copied the key information into place,
# we must pass the -f (force) option to image-create, since it thinks that
# something must be wrong, as the image exists.
#
pub_first_origin=""
pub_add_origins=""
for origin in $pub_origins; do
	if [[ -z "$pub_first_origin" ]]; then
		# The first origin is semi-special in that the publisher
		# argument to image-create requires it to be specified
		# by itself and then any additional origins after that.
		# Technically, image-create doesn't care if you specify
		# the first one again using -g, but there's no point in
		# doing so.
		pub_first_origin=$origin
		continue
	fi
	pub_add_origins="${pub_add_origins}-g $origin "
done

pub_add_mirrors=""
for mirror in $pub_mirrors; do
	pub_add_mirrors="${pub_add_mirrors}-m $mirror "
done

#
# The image is created with --no-refresh so that all of the publisher
# configuration can be put into place first before attempting to retrieve
# and build catalog information.  This substantially reduces the amount of
# time needed to create a zone.
#
LC_ALL=C $PKG image-create -f --no-refresh --zone --full \
    -p $publisher=$pub_first_origin $pub_add_origins $pub_add_mirrors $secinfo \
    $ZONEROOT || fail_incomplete "$f_img"

# Retrieve publisher attributes and update our new publisher
attrs=$(get_publisher_attr_args $publisher "origin")

# Change the value of PKG_IMAGE so that future PKG operation will work
# on the newly created zone rather than the global zone

PKG_IMAGE="$ZONEROOT"
export PKG_IMAGE

# --no-refresh is used here so that update operations can be
# coalesced.
# Update our new publisher
LC_ALL=C $PKG set-publisher --no-refresh $attrs $publisher \
    || fail_incomplete "$f_img"

# add extra publishers
# If cert and key information are ever allowed at the origin or
# mirror level, then this will have to be changed.
if [[ -n "$publishers_extra_origins" ]]; then
	echo "$publishers_extra_origins" | while IFS="=" read pub pub_urls; do
		pub_prefix=$pub
		pub_add_origins=""
		for origin in $pub_urls; do
			pub_add_origins="${pub_add_origins}-g $origin "
		done

		# Retrieve publisher attributes. Since we are retrieving
		# these attributes from the GLOBAL zone, we must reset
		# PKG_IMAGE temporarily
		SAVE_PKG_IMAGE=$PKG_IMAGE
		PKG_IMAGE=$GZ_IMAGE
		export PKG_IMAGE
		attrs=$(get_publisher_attr_args $pub_prefix "origin")
		# Now restore the save PKG_IMAGE value
		PKG_IMAGE=$SAVE_PKG_IMAGE
		export PKG_IMAGE
		# --no-refresh is used here so that update operations can be
		# coalesced.
		LC_ALL=C $PKG set-publisher --no-refresh $attrs \
                    ${pub_add_origins}${pub_prefix} || fail_incomplete "$f_img"
	done

	if [[ -n "$publishers_extra_mirrors" ]]; then
		echo "$publishers_extra_mirrors" | \
		    while IFS="=" read pub pub_urls; do
			pub_prefix=$pub
			pub_add_mirrors=""
			for mirror in $pub_urls; do
				pub_add_mirrors="${pub_add_mirrors}-m $mirror "
			done

			# Retrieve publisher attributes. Since we are retrieving
			# these attributes from the GLOBAL zone, we must reset
			# PKG_IMAGE temporarily
			SAVE_PKG_IMAGE=$PKG_IMAGE
			PKG_IMAGE=$GZ_IMAGE
			export PKG_IMAGE
			attrs=$(get_publisher_attr_args $pub_prefix "mirror")
			PKG_IMAGE=$SAVE_PKG_IMAGE
			export PKG_IMAGE
			# --no-refresh is used here so that update operations
			# can be coalesced.
			LC_ALL=C $PKG set-publisher --no-refresh $attrs \
			    ${pub_add_mirrors}${pub_prefix} || \
			    fail_incomplete "$f_img"
		done
	fi
fi

# Now that all of the publisher configurations are in place, attempt a refresh.
# If this fails, assume the image is incomplete.
LC_ALL=C $PKG refresh || fail_incomplete "$f_img"

if [ -d /var/pkg/download ]; then
	PKG_CACHEDIR=/var/pkg/download
	export PKG_CACHEDIR
	printf "$m_cache\n" $PKG_CACHEDIR
fi

#
# If we found an "entire" incorporation in the current image, then
# check to see if the user's choice of preferred publisher contains the
# version of the 'entire' incorporation needed.  This helps us to prevent
# mishaps in the event the user selected some weirdo publisher as their
# preferred one, or passed a preferred pub on the command line which doesn't
# have a suitable 'entire' in it.
#
# n.b. it would be nice to do this before we provision the zfs dataset, etc.
# but since the publisher specified by the user might not be known to
# the system, we can't do this test without first configuring the image.
#
if [[ -n $entire_fmri ]]; then
	printf "$m_incorp\n"
	LC_ALL=C $PKG list -af pkg://$publisher/$entire_fmri > /dev/null 2>&1
	if [[ $? -ne 0 ]]; then
		fail_fatal "$f_no_entire_in_pref" $entire_fmri $publisher
	fi
fi

printf "$m_core\n"
#
# We have to take baby steps here: first, by installing entire (if
# it's present in the current image), to constrain everything.  Then,
# SUNWcsd, to lay down device files which are subsequently needed by
# driver actions.  Then SUNWcs to lay down /etc/passwd, /etc/group,
# etc so that subsequent user and group actions work.  This can all
# hopefully go away once "primordial" actions arrive.
#
if [[ -n $entire_fmri ]]; then
	LC_ALL=C $PKG install -q --accept --no-refresh --no-index \
	    $entire_fmri || pkg_err_check "$f_pkg"
fi
LC_ALL=C $PKG install --accept --no-refresh --no-index SUNWcsd || \
    pkg_err_check "$f_pkg"
LC_ALL=C $PKG install --accept --no-refresh --no-index SUNWcs || \
    pkg_err_check "$f_pkg"

printf "$m_more\n"
pkglist=""
pkglist="$pkglist SUNWcnetr SUNWesu SUNWadmr SUNWadmap SUNWbzip SUNWgzip"

#
# Workaround: For now, SUNWipkg has no dependency on python, so we supply it.
#
pkglist="$pkglist SUNWPython26 SUNWipkg"

#
# Get some diagnostic tools, truss, dtrace, etc.
#
pkglist="$pkglist SUNWtoo SUNWdtrc SUNWrcmdc SUNWbip"

#
# Get at least one sensible shell, and vi
#
pkglist="$pkglist SUNWbash SUNWvim"

#
# Get ssh and sshd.
#
pkglist="$pkglist SUNWsshcu SUNWssh SUNWsshd"

#
# Get some name services.
#
pkglist="$pkglist SUNWnis SUNWlldap"

#
# Get nfs client and autofs; it's a pain not to have them.
#
pkglist="$pkglist SUNWnfsc SUNWatfs"

#
# Get routing daemons.  They're required for useful exclusive stack zones.
#
pkglist="$pkglist SUNWroute"

#
# Get packages for TX zones if appropriate.
#
(( $brand_labeled == 1 )) && pkglist="$pkglist system/trusted/trusted-nonglobal"

#
# Get man(1) but not the man pages
#
pkglist="$pkglist SUNWdoc"

#
# Add in any extra packages requested by the user.
#
pkglist="$pkglist $extra_packages"

#
# Do the install; we just refreshed on image-create, so skip that.  We
# also skip indexing here, as that is also what the LiveCD does.
#
LC_ALL=C $PKG install --accept --no-index --no-refresh $pkglist || \
    pkg_err_check "$f_pkg"

printf "\n$m_mannote\n"

printf "$m_smf"
ln -s ns_files.xml $ZONEROOT/var/svc/profile/name_service.xml
ln -s generic_limited_net.xml $ZONEROOT/var/svc/profile/generic.xml
ln -s inetd_generic.xml $ZONEROOT/var/svc/profile/inetd_services.xml
ln -s platform_none.xml $ZONEROOT/var/svc/profile/platform.xml

# This was formerly done in i.manifest
repfile=$ZONEROOT/etc/svc/repository.db
cp $ZONEROOT/lib/svc/seed/nonglobal.db $repfile
chmod 0600 $repfile
chown root:sys $repfile

printf "$m_done\n"

# Clean up root as a role and jack if needed
if grep "^root::::type=role;" $ZONEROOT/etc/user_attr >/dev/null 2>&1; then
	printf "$m_brokenness\n"
	#
	# Remove "jack" user.
	#
	print "/^jack:/d\nw" | ed -s $ZONEROOT/etc/passwd
	chmod u+w $ZONEROOT/etc/shadow
	print "/^jack:/d\nw" | ed -s $ZONEROOT/etc/shadow
	chmod u-w $ZONEROOT/etc/shadow

	#
	# Set root from a role back to... not a role.  Grr.
	#
	print "s/^root::::type=role;/root::::/\nw" |
	    ed -s $ZONEROOT/etc/user_attr
fi

#
# Make sure sysidtools run; we manually poke in the SSH action
# so that we get an SSH key.  Yes, this is seriously borken.
# See http://defect.opensolaris.org/bz/show_bug.cgi?id=741
#
printf "$m_more_brokenness\n"
/usr/sbin/sysidconfig -b $ZONEROOT -a /lib/svc/method/sshd
touch $ZONEROOT/etc/.UNCONFIGURED

#
# Labeled zones need to be able to modify /etc/gconf files, when gnome
# packages are installed in the zone.  Set up links in the zone to the
# global zone files -- this will provide default versions from the global
# zone, which can be modified by the zone, breaking the link.
if (( $brand_labeled == 1 )); then
	cd /etc/gconf
	for i in $(find .); do
		if [ ! -e $ZONEROOT/etc/gconf/$i ]; then
			if [ -d $i ]; then
				mkdir $ZONEROOT/etc/gconf/$i
			else
				ln -s /etc/gconf-global/$i \
				    $ZONEROOT/etc/gconf/$i
			fi
		fi
	done
fi

printf "$m_complete\n\n" ${SECONDS}
if (( $brand_labeled == 0 )); then
	printf "$m_postnote\n"
	printf "$m_postnote2\n"
else
	# Umount the dataset on the root.
	umount $ZONEROOT || printf "$f_zfs_unmount" "$ZONEPATH/root"
fi

exit $ZONE_SUBPROC_OK