2867N/A#!/usr/bin/ksh -p
2867N/A#
2867N/A# CDDL HEADER START
2867N/A#
2867N/A# The contents of this file are subject to the terms of the
2867N/A# Common Development and Distribution License (the "License").
2867N/A# You may not use this file except in compliance with the License.
2867N/A#
2867N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2867N/A# or http://www.opensolaris.org/os/licensing.
2867N/A# See the License for the specific language governing permissions
2867N/A# and limitations under the License.
2867N/A#
2867N/A# When distributing Covered Code, include this CDDL HEADER in each
2867N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2867N/A# If applicable, add the following below this CDDL HEADER, with the
2867N/A# fields enclosed by brackets "[]" replaced with your own identifying
2867N/A# information: Portions Copyright [yyyy] [name of copyright owner]
2867N/A#
2867N/A# CDDL HEADER END
2867N/A#
3356N/A# Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.
2867N/A#
2867N/A
2867N/A#
2867N/A# This is the method script for the svc:/application/pkg/mirror service
2867N/A#
2867N/A# When called using the 'start' or 'stop' SMF method script, it adds
2867N/A# or removes a crontab entry for the user running the service, pkg5srv
2867N/A# by default.
2867N/A#
2867N/A
2867N/A#
2867N/A# When called using the 'refresh' method, it runs pkgrecv(1) to update a
3356N/A# pkg(7) repository using configuration stored in the SMF instance.
2867N/A#
2867N/A# The following SMF properties are used to configure the service:
2867N/A#
2867N/A# config/repository the local pkg5 repository we update.
2867N/A#
2867N/A# config/ref_image the reference pkg5 image that contains
2867N/A# origin information that we should update
2867N/A# from.
2867N/A#
2867N/A# config/publishers a comma-separated list of the publishers
2867N/A# from ref_image that we pkgrecv from.
2867N/A#
2867N/A# config/crontab_period the first five fields of a crontab(4)
2867N/A# entry, with the 3rd field allowing the
2867N/A# special value 'random'.
2867N/A#
2867N/A# config/debug a boolean, 'true' or 'false'; whether
2867N/A# to log more output when debugging.
2867N/A#
2867N/A
2867N/A# Load SMF constants and functions
2867N/A. /lib/svc/share/smf_include.sh
2867N/A. /lib/svc/share/fs_include.sh
2867N/A. /lib/svc/share/pkg5_include.sh
2867N/A
2867N/AAWK=/usr/bin/awk
2867N/ACAT=/usr/bin/cat
2867N/ADATE=/usr/bin/date
2867N/AGREP=/usr/bin/grep
2867N/APKG=/usr/bin/pkg
2867N/APKGRECV=/usr/bin/pkgrecv
2867N/APKGREPO=/usr/bin/pkgrepo
2867N/APYTHON=/usr/bin/python
2867N/ARM=/usr/bin/rm
2867N/ASED=/usr/bin/sed
2867N/ASORT=/usr/bin/sort
2867N/ASVCCFG=/usr/sbin/svccfg
2867N/ASVCPROP=/usr/bin/svcprop
2867N/AWC=/usr/bin/wc
2867N/AZFS=/usr/sbin/zfs
2867N/A
2867N/ASVCNAME=svc:/application/pkg/mirror
2867N/A
2867N/A#
2867N/A# Since we deal with '*' values in crontab fields, we never want
2867N/A# globbing.
2867N/A#
2867N/Aset -o noglob
2867N/A
2867N/A#
2867N/A# Multiple instances of this service should not point at the
2867N/A# same local repository, since they could step on each other's toes
2867N/A# during updates, so we check for this before enabling the service.
2867N/A#
2867N/A# Usage:
2867N/A# check_duplicate_repos
2867N/A#
2867N/Afunction check_duplicate_repos {
2867N/A
2867N/A ALL_REPOS=$($SVCPROP -p config/repository "$SVCNAME:*" \
2867N/A | $AWK '{print $NF}' | $SORT | $WC -l)
2867N/A REPOS=$($SVCPROP -p config/repository "$SVCNAME:*" \
2867N/A | $AWK '{print $NF}' | $SORT -u | $WC -l)
2867N/A #
2867N/A # if the unique list of repositories is not the same as the
2867N/A # list of repositories, then we have duplicates.
2867N/A #
2867N/A if [ "$ALL_REPOS" != "$REPOS" ]; then
2867N/A return 1
2867N/A fi
2867N/A return 0
2867N/A}
2867N/A
2867N/A#
2867N/A# In order that all instances don't hit the remote origins on the same
2867N/A# day, when configured with a 'config/crontab_period' containing a
2867N/A# special value 'random' in the 'day of the month' field of the crontab
2867N/A# schedule, we randomize the day, choosing a value from 1-28, storing
2867N/A# that to the service config instead. We then print the crontab period.
2867N/A#
2867N/A# Usage:
2867N/A# add_date_jitter
2867N/A#
2867N/Afunction add_date_jitter {
2867N/A
2867N/A schedule=$($SVCPROP -p config/crontab_period $SMF_FMRI \
2867N/A | $SED -e 's/\\//g')
2867N/A
2867N/A #
2867N/A # Validate the cron_period property value, checking that we have
2867N/A # exactly 5 fields, and that 'random' only appears in the 3rd
2867N/A # field. We leave other validation up to cron(1).
2867N/A #
2867N/A echo "$schedule" | $AWK '
2867N/A NF != 5 {
2867N/A print "config/crontab_period property must contain 5 " \
2867N/A "values.";
2867N/A exit 1
2867N/A }
2867N/A $1 == "random" || $2 == "random" || $4 == "random" || \
2867N/A $5 == "random" {
2867N/A print "only field 3 can have the value random";
2867N/A exit 1
2867N/A }'
2867N/A
2867N/A check_failure $? "invalid value for config/crontab_period." \
2867N/A $SMF_FMRI exit
2867N/A
2867N/A RAND=$(( ($RANDOM % 27) + 1 ))
2867N/A new_schedule=$(echo "$schedule" | $SED -e "s/random/$RAND/1")
2867N/A if [ "$new_schedule" != "$schedule" ]; then
2867N/A #
2867N/A # Save the schedule in the instance. Note that this
2867N/A # will not appear in the running instance until the
2867N/A # refresh method has fired.
2867N/A #
2867N/A new_schedule=$(echo $new_schedule| $SED -e 's/ /\\ /g')
2867N/A $SVCCFG -s $SMF_FMRI setprop \
3018N/A config/crontab_period = astring: "$new_schedule"
2867N/A fi
2867N/A print $new_schedule
2867N/A}
2867N/A
2867N/A#
2867N/A# Add a crontab entry that does periodic pkgrecvs from a series of
2867N/A# remote pkg5 origins to a local repository. This is run as part of the
2867N/A# SMF start method for this service. If the repository doesn't exist,
2867N/A# we create it. We also attempt to create a zfs dataset if the parent
2867N/A# directory for the repository is the leaf of a zfs dataset.
2867N/A#
2867N/Afunction smf_schedule_updates {
2867N/A
2867N/A check_duplicate_repos
2867N/A check_failure $? "Two or more instances of $SVCNAME contain the
2867N/Asame 'config/repository' value, which is not supported." $SMF_FMRI exit
2867N/A typeset -f schedule=$(add_date_jitter | $SED -e 's/\\//g')
2867N/A typeset repo=$($SVCPROP -p config/repository $SMF_FMRI)
2867N/A
2872N/A SAVED_IFS="$IFS"
2872N/A IFS=,
2872N/A set -A publishers $($SVCPROP -p config/publishers $SMF_FMRI)
2872N/A IFS="$SAVED_IFS"
2872N/A
2867N/A if [ ! -f $repo/pkg5.repository ]; then
2867N/A repo_parent=$(dirname $repo)
2867N/A repo_base=$(basename $repo)
2867N/A readmnttab "$repo_parent" < /etc/mnttab
2867N/A if [ "$fstype" = "zfs" ]; then
2867N/A #
2867N/A # $special gets set by readmnttab in
2867N/A # /lib/svc/share/fs_include.sh
2867N/A #
2867N/A DS="$special/$repo_base"
3017N/A
3017N/A #
3017N/A # We set canmount=noauto so that multiple bootable
3017N/A # rpools can coexist on the same system.
3017N/A #
3017N/A $ZFS create -o canmount=noauto "$DS"
2867N/A check_failure $? \
2867N/A "unable to create zfs dataset $DS" \
2867N/A $SMF_FMRI degrade
3017N/A $ZFS mount "$DS"
3017N/A check_failure $? \
3017N/A "unable to mount zfs dataset $DS" \
3017N/A $SMF_FMRI degrade
2867N/A fi
2867N/A $PKGREPO create "$repo"
2867N/A check_failure $? "unable to create repository" \
2867N/A $SMF_FMRI degrade
2867N/A fi
2872N/A set_default_publisher "$repo" ${publishers[0]}
2867N/A add_cronjob $SMF_FMRI "$schedule" \
2867N/A "/usr/sbin/svcadm refresh $SMF_FMRI"
2867N/A}
2867N/A
2867N/A#
2867N/A# Remove the crontab entry that was added by 'schedule_updates'. This is
2867N/A# run as part of the SMF stop method for this service.
2867N/A#
2867N/Afunction smf_unschedule_updates {
2867N/A
2867N/A remove_cronjob $SMF_FMRI \
2867N/A "/usr/sbin/svcadm refresh $SMF_FMRI"
2867N/A}
2867N/A
2867N/A#
2872N/A# Checks whether the given repository has a publisher/prefix set,
2872N/A# and if not, sets it to the given publisher.
2872N/A#
2872N/A# Usage:
2872N/A# set_default_publisher <path to repo> <publisher>
2872N/A#
2872N/Afunction set_default_publisher {
2872N/A typeset repo="$1"
2872N/A typeset pub=$2
2872N/A
2872N/A DEFAULT=$($PKGREPO -s "$repo" get -H publisher/prefix | \
2872N/A $AWK '{print $NF}')
2872N/A if [ "$DEFAULT" = '""' ]; then
2872N/A $PKGREPO -s "$repo" set publisher/prefix=$pub
2872N/A fi
2872N/A}
2872N/A
2872N/A#
2867N/A# Intended to be called as part of a cron job firing, this calls
2867N/A# 'pkgrecv_from_origin' for each publisher configured in the SMF
2867N/A# instance.
2867N/A#
2867N/A# Usage:
2867N/A# update_repository <smf fmri>
2867N/A#
2867N/Afunction update_repository {
2867N/A
2867N/A typeset SMF_FMRI=$1
2867N/A typeset instance=$(echo $SMF_FMRI | $AWK -F: '{print $NF}')
3355N/A typeset lockfile=/system/volatile/pkg/mirror.$instance.lock
2867N/A
2867N/A if [ -f $lockfile ]; then
2867N/A pid=$(<$lockfile)
2867N/A check_failure 1 "A mirror operation was already running
2867N/A under process $pid when the cron job fired. Remove $lockfile to
2867N/A override, or check the SMF property 'config/crontab_period' to ensure
2867N/A cron schedules don't overlap." $SMF_FMRI degrade
2867N/A return 1
2867N/A fi
2867N/A # write our pid into the lock file
2867N/A echo $$ > $lockfile
2867N/A check_failure $? "unable to create lockfile" $SMF_FMRI degrade
2867N/A
2867N/A typeset repo=$($SVCPROP -p config/repository $SMF_FMRI \
2867N/A | $SED -e 's/\\//g')
2867N/A typeset cachedir=$($SVCPROP -p config/cache_dir $SMF_FMRI \
2867N/A | $SED -e 's/\\//g')
2867N/A typeset ref_image=$($SVCPROP -p config/ref_image $SMF_FMRI\
2867N/A | $SED -e 's/\\//g')
2867N/A
2867N/A SAVED_IFS="$IFS"
2867N/A IFS=,
2867N/A set -A publishers $($SVCPROP -p config/publishers $SMF_FMRI)
2867N/A IFS="$SAVED_IFS"
2867N/A if [ -z "$publishers" ]; then
2867N/A echo "ERROR: no publishers found in 'config/publishers'"
2867N/A return $SMF_EXIT_FATAL
2867N/A fi
2867N/A
2867N/A set -A origins ""
2867N/A set -A ssl_keys ""
2867N/A set -A ssl_certs ""
2867N/A set -A http_proxies ""
2867N/A set -A https_proxies ""
3156N/A set -A clones ""
3156N/A set -A pubs ""
2867N/A
2867N/A #
2867N/A # Gather the details we need to connect to the origins
2867N/A # we want to pkgrecv from.
2867N/A #
2867N/A i=0
2953N/A index=0
2867N/A while [ $i -lt ${#publishers[@]} ]; do
2867N/A pub=${publishers[$i]}
2867N/A sslkey=$($PKG -R $ref_image publisher $pub \
2867N/A | $GREP 'SSL Key:' \
2867N/A | $GREP -v None | $SED -e 's/.* //g')
2867N/A sslcert=$($PKG -R $ref_image publisher $pub \
2867N/A | $GREP 'SSL Cert:' \
2867N/A | $GREP -v None | $SED -e 's/.* //g')
2867N/A $PKG -R $ref_image publisher -F tsv > /tmp/pkg.mirror.$$
2867N/A
2867N/A #
2867N/A # this function depends on the output of
2867N/A # 'pkg publisher -F tsv'. It really ought to use
2867N/A # 'pkg publisher -o' option when that's available.
2867N/A #
3156N/A
3156N/A first_index=$index
2867N/A while read publisher sticky syspub enabled ptype status \
2867N/A uri proxy ; do
2867N/A if [ "$pub" != "$publisher" ]; then
2867N/A continue
2867N/A fi
2867N/A if [ -z "$uri" ]; then
2867N/A echo "WARNING: no URI \
2867N/Aconfigured for publisher $pub"
2867N/A continue
2867N/A fi
2867N/A origins[$index]=$uri
2867N/A echo $uri | $GREP -q https://
2867N/A if [ $? -eq 0 ]; then
2867N/A ssl_keys[$index]=$sslkey
2867N/A ssl_certs[$index]=$sslcert
2867N/A else
2867N/A ssl_keys[$index]=''
2867N/A ssl_certs[$index]=''
2867N/A fi
2867N/A if [ "$proxy" = "-" ]; then
2867N/A proxy=''
2867N/A fi
2867N/A https_proxies[$index]=$proxy
3079N/A http_proxies[$index]=$proxy
3156N/A clones[$index]=""
3156N/A pubs[$index]=$pub
2867N/A index=$(( $index + 1 ))
2867N/A done < /tmp/pkg.mirror.$$
2867N/A $RM /tmp/pkg.mirror.$$
2867N/A i=$(( $i + 1 ))
3156N/A
3156N/A # If only one origin for this publisher
3156N/A if [ $first_index -eq $(( $index - 1 )) ]; then
3156N/A clones[$first_index]="true"
3156N/A fi
2867N/A done
2867N/A
2867N/A # Iterate over all configured origins
2867N/A i=0
2867N/A while [ $i -lt ${#origins[@]} ]; do
2867N/A origin=${origins[$i]}
3156N/A pub=${pubs[$i]}
2867N/A key=${ssl_keys[$i]}
2867N/A cert=${ssl_certs[$i]}
2867N/A http_proxy=${http_proxies[$i]}
2867N/A https_proxy=${https_proxies[$i]}
3156N/A clone=${clones[$i]}
2867N/A
2867N/A pkgrecv_from_origin "$repo" "$origin" "$key" \
2867N/A "$cert" $SMF_FMRI "$cachedir" "$http_proxy" \
3156N/A "$https_proxy" "$clone" "$pub"
2867N/A check_failure $? \
2867N/A "unable to update repository $repo" $SMF_FMRI \
2867N/A degrade
2867N/A if [ $? -ne 0 ]; then
2867N/A $RM $lockfile
2867N/A return 1
2867N/A fi
2867N/A i=$(( $i + 1 ))
2867N/A done
2867N/A
2867N/A EXIT=$?
2867N/A $RM $lockfile
2867N/A return $EXIT
2867N/A}
2867N/A
2867N/A#
2867N/A# When retrieving values from SMF, we can get the string '""'
2867N/A# (two quotes) returned. For our purposes, this is equivalent to the
2867N/A# null string, so we normalize it to ''. This function reads from stdin.
2867N/A#
2867N/Afunction reduce_null_str {
2867N/A while read value; do
2867N/A if [ "$value" = '""' ]; then
2867N/A echo ''
2867N/A else
2867N/A echo $value
2867N/A fi
2867N/A done
2867N/A}
2867N/A
2867N/A#
2867N/A# Perform a pkgrecv from the given origin to the given repository.
2867N/A# We assume that the repository exists.
2867N/A#
2867N/A# Usage:
2867N/A# pkgrecv_from_origin <repo> <origin> <key path> <cert path> <FMRI>
2867N/A# <cache dir> <http_proxy> <https_proxy>
2867N/A#
2867N/Afunction pkgrecv_from_origin {
2867N/A
2867N/A typeset repo=$1
2867N/A typeset origin=$2
2867N/A typeset key=$(echo $3 | reduce_null_str)
2867N/A typeset cert=$(echo $4 | reduce_null_str)
2867N/A typeset SMF_FMRI=$5
2867N/A typeset cachedir=$6
2867N/A typeset http_proxy=$(echo $7 | reduce_null_str)
2867N/A typeset https_proxy=$(echo $8 | reduce_null_str)
3156N/A typeset clone=$9
3156N/A typeset publisher=${10}
2867N/A
2867N/A typeset instance=$(echo $SMF_FMRI | $AWK -F: '{print $NF}')
2867N/A typeset debug_flag=$($SVCPROP -p config/debug $SMF_FMRI)
2867N/A typeset LOG=/var/log/pkg/mirror/mirror.$instance.log
2867N/A
2867N/A export http_proxy=$http_proxy
2867N/A export https_proxy=$https_proxy
2867N/A
2867N/A TSTAMP=$($DATE +%Y%m%dT%H%M%SZ)
2867N/A echo "$TSTAMP: $SMF_FMRI updates to $repo from $origin :" \
2867N/A >> $LOG
2867N/A
2867N/A if [ -n "$key" ] && [ -n "$cert" ]; then
2867N/A key="--key $key"
2867N/A cert="--cert $cert"
2867N/A fi
3156N/A
3156N/A set -f
3156N/A if [ -n "$clone" ]; then
3156N/A cmd="$PKGRECV -s $origin -d "$repo" -p $publisher \
3156N/A $key $cert --clone"
3156N/A else
3156N/A cmd="$PKGRECV -s $origin -c "$cachedir"/$instance \
3156N/A -d "$repo" -m all-timestamps $key $cert *"
3156N/A fi
3156N/A
2867N/A # show the command we're running
2867N/A if [ "$debug_flag" = "true" ] ; then
3156N/A echo $cmd
2867N/A fi
3156N/A
3156N/A $cmd > $LOG.tmp 2>&1
3156N/A set +f
2867N/A EXIT=$?
2867N/A
2867N/A if [ "$debug_flag" = "true" ]; then
2867N/A $CAT $LOG.tmp >> $LOG
2867N/A elif [ $EXIT -ne 0 ]; then
2867N/A #
2867N/A # in the case of errors, getting the full pkgrecv output
2867N/A # can be helpful.
2867N/A #
2867N/A $CAT $LOG.tmp >> $LOG
2867N/A else
2867N/A # otherwise, we only log messages containing pkg5 FMRIs
2867N/A $GREP 'pkg:/' $LOG.tmp >> $LOG
2872N/A # we only destroy the cache if a pkgrecv was successful
2872N/A $RM -rf "$cachedir"/$instance
2867N/A fi
2867N/A $PKGREPO -s "$repo" refresh
2867N/A $RM $LOG.tmp
2867N/A return $EXIT
2867N/A}
2867N/A
2867N/A# $1 start | stop | an FMRI containing configuration
2867N/Acase "$1" in
2867N/A'start')
2867N/A smf_schedule_updates
2867N/A if [ $? -eq 0 ]; then
2867N/A result=$SMF_EXIT_OK
2867N/A else
2867N/A echo "Problem mirroring repository for $SMF_FMRI"
2867N/A result=$SMF_EXIT_ERR_FATAL
2867N/A fi
2867N/A ;;
2867N/A
2867N/A'stop')
2867N/A smf_unschedule_updates
2867N/A if [ $? -eq 0 ]; then
2867N/A result=$SMF_EXIT_OK
2867N/A else
2867N/A echo "Problem mirroring repository for $SMF_FMRI"
2867N/A result=$SMF_EXIT_ERR_FATAL
2867N/A fi
2867N/A ;;
2867N/A
2867N/A#
2867N/A# A note on logging.
2867N/A#
2867N/A# The following log files are created while this service is running:
2867N/A#
2867N/A# /var/log/pkg/mirror/mirror.<instance>.log
2867N/A# This is the top-level log file for the service. This log
2867N/A# shows a summary of each pkgrecv, listing a timestamp and the
2867N/A# packages that were received during that run of the cron job.
2867N/A#
2867N/A# /var/log/pkg/mirror/mirror.<instance>.run.<pid>
2867N/A# This is a temporary log file, which should contain very little
2867N/A# output - it exists to capture all other output from the service
2867N/A# If 'config/debug' is set, then this file will also include the
2867N/A# full pkgrecv(1) command that is executed.
2867N/A#
2867N/A# /var/log/pkg/mirror/mirror.<instance>.log.tmp
2867N/A# Another temporary log file, which captures the complete output
2867N/A# of each pkgrecv command as it runs. At the end of the pkgrecv
2867N/A# process, we extract a summary and append it to
2867N/A# mirror.<instance>.log. If 'config/debug' is set, the contents
2867N/A# of this log are appended to mirror.<instance>.log. If any errors
2867N/A# were encountered while running pkgrecv, the contents of this log
2867N/A# are appended to mirror.<instance>.log.
2867N/A#
2867N/A
2867N/A'refresh')
2867N/A typeset instance=$(echo $SMF_FMRI | $AWK -F: '{print $NF}')
2867N/A typeset LOG=/var/log/pkg/mirror/mirror.$instance.log
2867N/A typeset debug_flag=$($SVCPROP -p config/debug $SMF_FMRI)
3355N/A typeset pkg_dir=/system/volatile/pkg
2867N/A
2867N/A # Most output should get captured by update_repository, but we
2867N/A # capture any remaining output.
3355N/A mkdir -p $pkg_dir
2867N/A update_repository $SMF_FMRI > $LOG.run.$$ 2>&1
2867N/A RET=$?
2867N/A
2867N/A if [ -s $LOG.run.$$ ]; then
2867N/A cat $LOG.run.$$ >> $LOG
2867N/A fi
2867N/A
2867N/A if [ "$debug_flag" = "false" ]; then
2867N/A $RM $LOG.run.$$
2867N/A fi
2867N/A
2867N/A if [ $RET -eq 0 ]; then
2867N/A result=$SMF_EXIT_OK
2867N/A else
2867N/A echo "Mirror refresh failed: see $LOG for more detail."
2867N/A # try to remove the cron job so we don't keep failing
2867N/A smf_unschedule_updates
2867N/A result=$SMF_EXIT_ERR_FATAL
2867N/A fi
2867N/A ;;
2867N/Aesac
2867N/A
2867N/Aexit $result