Current File : //bin/cowpoke |
#!/bin/bash
# Simple shell script for driving a remote cowbuilder via ssh
#
# Copyright(C) 2007, 2008, 2009, 2011, 2012, 2014, Ron <ron@debian.org>
# This script is distributed according to the terms of the GNU GPL.
set -e
#BUILDD_HOST=
#BUILDD_USER=
BUILDD_ARCH="$(dpkg-architecture -qDEB_BUILD_ARCH 2>/dev/null)"
# The 'default' dist is whatever cowbuilder is locally configured for
BUILDD_DIST="default"
INCOMING_DIR="cowbuilder-incoming"
PBUILDER_BASE="/var/cache/pbuilder"
#SIGN_KEYID=
#UPLOAD_QUEUE="ftp-master"
BUILDD_ROOTCMD="sudo"
REMOTE_SCRIPT="cowssh_it"
DEBOOTSTRAP="cdebootstrap"
for f in /etc/cowpoke.conf ~/.cowpoke .cowpoke "$COWPOKE_CONF"; do [ -r "$f" ] && . "$f"; done
get_archdist_vars()
{
_ARCHDIST_OPTIONS="RESULT_DIR BASE_PATH BASE_DIST CREATE_OPTS UPDATE_OPTS BUILD_OPTS SIGN_KEYID UPLOAD_QUEUE"
_RESULT_DIR="result"
_BASE_PATH="base.cow"
for arch in $BUILDD_ARCH; do
for dist in $BUILDD_DIST; do
for var in $_ARCHDIST_OPTIONS; do
eval "val=( \"\${${arch}_${dist}_${var}[@]}\" )"
if [ "$1" = "display" ]; then
case $var in
RESULT_DIR | BASE_PATH )
[ ${#val[@]} -gt 0 ] || eval "val=\"$PBUILDER_BASE/$arch/$dist/\$_$var\""
echo " ${arch}_${dist}_${var} = $val"
;;
*_OPTS )
# Don't display these if they are overridden on the command line.
eval "override=( \"\${OVERRIDE_${var}[@]}\" )"
[ ${#override[@]} -gt 0 ] || [ ${#val[@]} -eq 0 ] ||
echo " ${arch}_${dist}_${var} =$(printf " '%s'" "${val[@]}")"
;;
* )
[ ${#val[@]} -eq 0 ] || echo " ${arch}_${dist}_${var} = $val"
;;
esac
else
case $var in
RESULT_DIR | BASE_PATH )
# These are always a single value, and must always be set,
# either by the user or to their default value.
[ ${#val[@]} -gt 0 ] || eval "val=\"$PBUILDER_BASE/$arch/$dist/\$_$var\""
echo "${arch}_${dist}_${var}='$val'"
;;
*_OPTS )
# These may have zero, one, or many values which we must not word-split.
# They can safely remain unset if there are no values.
#
# We don't need to worry about the command line overrides here,
# they will be taken care of in the remote script.
[ ${#val[@]} -eq 0 ] ||
echo "${arch}_${dist}_${var}=($(printf " %q" "${val[@]}") )"
;;
SIGN_KEYID | UPLOAD_QUEUE )
# We don't need these in the remote script
;;
* )
# These may have zero or one value.
# They can safely remain unset if there are no values.
[ ${#val[@]} -eq 0 ] || echo "${arch}_${dist}_${var}='$val'"
;;
esac
fi
done
done
done
}
display_override_vars()
{
_OVERRIDE_OPTIONS="CREATE_OPTS UPDATE_OPTS BUILD_OPTS SIGN_KEYID UPLOAD_QUEUE"
for var in $_OVERRIDE_OPTIONS; do
eval "override=( \"\${OVERRIDE_${var}[@]}\" )"
[ ${#override[@]} -eq 0 ] || echo " override: $var =$(printf " '%s'" "${override[@]}")"
done
}
PROGNAME="$(basename $0)"
version ()
{
echo \
"This is $PROGNAME, from the Debian devscripts package, version 2.22.1ubuntu1
This code is Copyright 2007-2014, Ron <ron@debian.org>.
This program comes with ABSOLUTELY NO WARRANTY.
You are free to redistribute this code under the terms of the
GNU General Public License."
exit 0
}
usage()
{
cat 1>&2 <<EOF
cowpoke [options] package.dsc
Uploads a Debian source package to a cowbuilder host and builds it,
optionally also signing and uploading the result to an incoming queue.
The following options are supported:
--arch="arch" Specify the Debian architecture(s) to build for.
--dist="dist" Specify the Debian distribution(s) to build for.
--buildd="host" Specify the remote host to build on.
--buildd-user="name" Specify the remote user to build as.
--create Create the remote cowbuilder root if necessary.
--return[="path"] Copy results of the build to 'path'. If path is
not specified, return them to the current directory.
--no-return Do not copy results of the build to RETURN_DIR
(overriding a path set for it in the config files).
--sign="keyid" Specify the key to sign packages with.
--upload="queue" Specify the dput queue to upload signed packages to.
The current default configuration is:
BUILDD_HOST = $BUILDD_HOST
BUILDD_USER = $BUILDD_USER
BUILDD_ARCH = $BUILDD_ARCH
BUILDD_DIST = $BUILDD_DIST
RETURN_DIR = $RETURN_DIR
SIGN_KEYID = $SIGN_KEYID
UPLOAD_QUEUE = $UPLOAD_QUEUE
The expected remote paths are:
INCOMING_DIR = $INCOMING_DIR
PBUILDER_BASE = ${PBUILDER_BASE:-/}
$(get_archdist_vars display)
$(display_override_vars)
The cowbuilder image must have already been created on the build host
and the expected remote paths must already exist if the --create option
is not passed. You must have ssh access to the build host as BUILDD_USER
if that is set, else as the user executing cowpoke or a user specified
in your ssh config for '$BUILDD_HOST'.
That user must be able to execute cowbuilder as root using '$BUILDD_ROOTCMD'.
EOF
exit $1
}
for arg; do
case "$arg" in
--arch=*)
BUILDD_ARCH="${arg#*=}"
;;
--dist=*)
BUILDD_DIST="${arg#*=}"
;;
--buildd=*)
BUILDD_HOST="${arg#*=}"
;;
--buildd-user=*)
BUILDD_USER="${arg#*=}"
;;
--create)
CREATE_COW="yes"
;;
--return=*)
RETURN_DIR="${arg#*=}"
;;
--return)
RETURN_DIR=.
;;
--no-return)
RETURN_DIR=
;;
--dpkg-opts=*)
# This one is a bit tricky, given the combination of the calling convention here,
# the calling convention for cowbuilder, and the behaviour of things that might
# pass this option to us. Some things, like when we are called from the gitpkg
# hook using options from git-config, will preserve any quoting that was used in
# the .gitconfig file, which is natural for anyone to want to use in a construct
# like: options = --dpkg-opts='-uc -us -j6'. People are going to cringe if we
# tell them they must not use quotes there no matter how much it may 'make sense'
# if you know too much about the internals. And it will only get worse when we
# then tell them they must quote it like that if they type it directly in their
# shell ...
#
# So we do the only thing that seems sensible, and try to Deal With It here.
# If the outermost characters are paired quotes, we manually strip them off.
# We don't want to let the shell do quote removal, since that might change a
# part of this which we don't want modified.
# We collect however many sets of those we are passed in an array, which we'll
# then combine back into a single argument at the final point of use.
#
# Which _should_ DTRT for anyone who isn't trying to blow this up deliberately
# and maybe will still do it for them too in spite of their efforts. But unless
# someone finds a sensible case this fails on, I'm not going to cry over people
# who want to stuff up their own system with input they created themselves.
val=${arg#*=}
[[ $val == \'*\' || $val == \"*\" ]] && val=${val:1:-1}
DEBBUILDOPTS+=( "$val" )
;;
--create-opts=*)
OVERRIDE_CREATE_OPTS+=( "${arg#*=}" )
;;
--update-opts=*)
OVERRIDE_UPDATE_OPTS+=( "${arg#*=}" )
;;
--build-opts=*)
OVERRIDE_BUILD_OPTS+=( "${arg#*=}" )
;;
--sign=*)
OVERRIDE_SIGN_KEYID=${arg#*=}
;;
--upload=*)
OVERRIDE_UPLOAD_QUEUE=${arg#*=}
;;
*.dsc)
DSC="$arg"
;;
--help)
usage 0
;;
--version)
version
;;
*)
echo "ERROR: unrecognised option '$arg'"
usage 1
;;
esac
done
if [ -z "$REMOTE_SCRIPT" ]; then
echo "No remote script name set. Aborted."
exit 1
fi
if [ -z "$DSC" ]; then
echo "ERROR: No package .dsc specified"
usage 1
fi
if ! [ -r "$DSC" ]; then
echo "ERROR: '$DSC' not found."
exit 1
fi
if [ -z "$BUILDD_ARCH" ]; then
echo "No BUILDD_ARCH set. Aborted."
exit 1
fi
if [ -z "$BUILDD_HOST" ]; then
echo "No BUILDD_HOST set. Aborted."
exit 1
fi
if [ -z "$BUILDD_ROOTCMD" ]; then
echo "No BUILDD_ROOTCMD set. Aborted."
exit 1
fi
if [ -e "$REMOTE_SCRIPT" ]; then
echo "$REMOTE_SCRIPT file already exists and will be overwritten."
echo -n "Do you wish to continue (Y/n)? "
read -e yesno
case "$yesno" in
N* | n*)
echo "Ok, bailing out."
echo "You should set the REMOTE_SCRIPT variable to some other value"
echo "if this name conflicts with something you already expect to use"
exit 1
;;
*) ;;
esac
fi
[ -z "$BUILDD_USER" ] || BUILDD_USER="$BUILDD_USER@"
PACKAGE="$(basename $DSC .dsc)"
DATE="$(date +%Y%m%d 2>/dev/null)"
cat > "$REMOTE_SCRIPT" <<-EOF
#!/bin/bash
# cowpoke generated remote worker script.
# Normally this should have been deleted already, you can safely remove it now.
compare_changes()
{
p1="\${1%_*.changes}"
p2="\${2%_*.changes}"
p1="\${p1##*_}"
p2="\${p2##*_}"
dpkg --compare-versions "\$p1" gt "\$p2"
}
$(get_archdist_vars)
for arch in $BUILDD_ARCH; do
for dist in $BUILDD_DIST; do
echo " ------- Begin build for \$arch \$dist -------"
CHANGES="\$arch.changes"
LOGFILE="$INCOMING_DIR/build.${PACKAGE}_\$arch.\$dist.log"
UPDATELOG="$INCOMING_DIR/cowbuilder-\${arch}-\${dist}-update-log-$DATE"
eval "RESULT_DIR=\"\\\$\${arch}_\${dist}_RESULT_DIR\""
eval "BASE_PATH=\"\\\$\${arch}_\${dist}_BASE_PATH\""
eval "BASE_DIST=\"\\\$\${arch}_\${dist}_BASE_DIST\""
eval "CREATE_OPTS=( \"\\\${\${arch}_\${dist}_CREATE_OPTS[@]}\" )"
eval "UPDATE_OPTS=( \"\\\${\${arch}_\${dist}_UPDATE_OPTS[@]}\" )"
eval "BUILD_OPTS=( \"\\\${\${arch}_\${dist}_BUILD_OPTS[@]}\" )"
[ -n "\$BASE_DIST" ] || BASE_DIST=\$dist
[ ${#OVERRIDE_CREATE_OPTS[@]} -eq 0 ] || CREATE_OPTS=("${OVERRIDE_CREATE_OPTS[@]}")
[ ${#OVERRIDE_UPDATE_OPTS[@]} -eq 0 ] || UPDATE_OPTS=("${OVERRIDE_UPDATE_OPTS[@]}")
[ ${#OVERRIDE_BUILD_OPTS[@]} -eq 0 ] || BUILD_OPTS=("${OVERRIDE_BUILD_OPTS[@]}")
[ ${#DEBBUILDOPTS[*]} -eq 0 ] || DEBBUILDOPTS=("--debbuildopts" "${DEBBUILDOPTS[*]}")
# Sort the list of old changes files for this package to try and
# determine the most recent one preceding this version. We will
# debdiff to this revision in the final sanity checks if one exists.
# This is adapted from the insertion sort trickery in git-debimport.
OLD_CHANGES="\$(find "\$RESULT_DIR/" -maxdepth 1 -type f \\
-name "${PACKAGE%%_*}_*_\$CHANGES" 2>/dev/null \\
| sort 2>/dev/null)"
P=( \$OLD_CHANGES )
count=\${#P[*]}
for(( i=1; i < count; ++i )) do
j=i
#echo "was \$i: \${P[i]}"
while ((\$j)) && compare_changes "\${P[j-1]}" "\${P[i]}"; do ((--j)); done
((i==j)) || P=( \${P[@]:0:j} \${P[i]} \${P[j]} \${P[@]:j+1:i-(j+1)} \${P[@]:i+1} )
done
#for(( i=1; i < count; ++i )) do echo "now \$i: \${P[i]}"; done
OLD_CHANGES=
for(( i=count-1; i >= 0; --i )) do
if [ "\${P[i]}" != "\$RESULT_DIR/${PACKAGE}_\$CHANGES" ]; then
OLD_CHANGES="\${P[i]}"
break
fi
done
set -eo pipefail
if ! [ -e "\$BASE_PATH" ]; then
if [ "$CREATE_COW" = "yes" ]; then
mkdir -p "\$RESULT_DIR"
mkdir -p "\$(dirname \$BASE_PATH)"
mkdir -p "$PBUILDER_BASE/aptcache"
$BUILDD_ROOTCMD cowbuilder --create --distribution \$BASE_DIST \\
--basepath "\$BASE_PATH" \\
--aptcache "$PBUILDER_BASE/aptcache" \\
--debootstrap "$DEBOOTSTRAP" \\
--debootstrapopts --arch="\$arch" \\
"\${CREATE_OPTS[@]}" \\
2>&1 | tee "\$UPDATELOG"
else
echo "SKIPPING \$dist/\$arch build, '\$BASE_PATH' does not exist" | tee "\$LOGFILE"
echo " use the cowpoke --create option to bootstrap a new build root" | tee -a "\$LOGFILE"
continue
fi
elif ! [ -e "\$UPDATELOG" ]; then
$BUILDD_ROOTCMD cowbuilder --update --distribution \$BASE_DIST \\
--basepath "\$BASE_PATH" \\
--aptcache "$PBUILDER_BASE/aptcache" \\
--autocleanaptcache \\
"\${UPDATE_OPTS[@]}" \\
2>&1 | tee "\$UPDATELOG"
fi
$BUILDD_ROOTCMD cowbuilder --build --basepath "\$BASE_PATH" \\
--aptcache "$PBUILDER_BASE/aptcache" \\
--buildplace "$PBUILDER_BASE/build" \\
--buildresult "\$RESULT_DIR" \\
"\${DEBBUILDOPTS[@]}" \\
"\${BUILD_OPTS[@]}" \\
"$INCOMING_DIR/$(basename $DSC)" 2>&1 \\
| tee "\$LOGFILE"
set +eo pipefail
echo >> "\$LOGFILE"
echo "lintian \$RESULT_DIR/${PACKAGE}_\$CHANGES" >> "\$LOGFILE"
lintian "\$RESULT_DIR/${PACKAGE}_\$CHANGES" 2>&1 | tee -a "\$LOGFILE"
if [ -n "\$OLD_CHANGES" ]; then
echo >> "\$LOGFILE"
echo "debdiff \$OLD_CHANGES ${PACKAGE}_\$CHANGES" >> "\$LOGFILE"
debdiff "\$OLD_CHANGES" "\$RESULT_DIR/${PACKAGE}_\$CHANGES" 2>&1 \\
| tee -a "\$LOGFILE"
else
echo >> "\$LOGFILE"
echo "No previous packages for \$dist/\$arch to compare" >> "\$LOGFILE"
fi
done
done
EOF
chmod 755 "$REMOTE_SCRIPT"
if ! dcmd rsync -vP $DSC "$REMOTE_SCRIPT" "$BUILDD_USER$BUILDD_HOST:$INCOMING_DIR";
then
dcmd scp $DSC "$REMOTE_SCRIPT" "$BUILDD_USER$BUILDD_HOST:$INCOMING_DIR"
fi
ssh -t "$BUILDD_USER$BUILDD_HOST" "\"$INCOMING_DIR/$REMOTE_SCRIPT\" && rm -f \"$INCOMING_DIR/$REMOTE_SCRIPT\""
echo
echo "Build completed."
for arch in $BUILDD_ARCH; do
CHANGES="$arch.changes"
for dist in $BUILDD_DIST; do
sign_keyid=$OVERRIDE_SIGN_KEYID
[ -n "$sign_keyid" ] || eval "sign_keyid=\"\$${arch}_${dist}_SIGN_KEYID\""
[ -n "$sign_keyid" ] || sign_keyid="$SIGN_KEYID"
[ -n "$sign_keyid" ] || continue
eval "RESULT_DIR=\"\$${arch}_${dist}_RESULT_DIR\""
[ -n "$RESULT_DIR" ] || RESULT_DIR="$PBUILDER_BASE/$arch/$dist/result"
_desc="$dist/$arch"
[ "$dist" != "default" ] || _desc="$arch"
while true; do
echo -n "Sign $_desc $PACKAGE with key '$sign_keyid' (yes/no)? "
read -e yesno
case "$yesno" in
YES | yes)
debsign "-k$sign_keyid" -r "$BUILDD_USER$BUILDD_HOST" "$RESULT_DIR/${PACKAGE}_$CHANGES"
upload_queue=$OVERRIDE_UPLOAD_QUEUE
[ -n "$upload_queue" ] || eval "upload_queue=\"\$${arch}_${dist}_UPLOAD_QUEUE\""
[ -n "$upload_queue" ] || upload_queue="$UPLOAD_QUEUE"
if [ -n "$upload_queue" ]; then
while true; do
echo -n "Upload $_desc $PACKAGE to '$upload_queue' (yes/no)? "
read -e upload
case "$upload" in
YES | yes)
ssh "$BUILDD_USER$BUILDD_HOST" \
"cd \"$RESULT_DIR/\" && dput \"$upload_queue\" \"${PACKAGE}_$CHANGES\""
break 2
;;
NO | no)
echo "Package upload skipped."
break 2
;;
*)
echo "Please answer 'yes' or 'no'"
;;
esac
done
fi
break
;;
NO | no)
echo "Package signing skipped."
break
;;
*)
echo "Please answer 'yes' or 'no'"
;;
esac
done
done
done
if [ -n "$RETURN_DIR" ]; then
for arch in $BUILDD_ARCH; do
CHANGES="$arch.changes"
for dist in $BUILDD_DIST; do
eval "RESULT_DIR=\"\$${arch}_${dist}_RESULT_DIR\""
[ -n "$RESULT_DIR" ] || RESULT_DIR="$PBUILDER_BASE/$arch/$dist/result"
cache_dir="./cowpoke-return-cache"
mkdir -p $cache_dir
scp "$BUILDD_USER$BUILDD_HOST:$RESULT_DIR/${PACKAGE}_$CHANGES" $cache_dir
for f in $(cd $cache_dir && dcmd ${PACKAGE}_$CHANGES); do
RESULTS="$RESULTS $RESULT_DIR/$f"
done
rm -f $cache_dir/${PACKAGE}_$CHANGES
rmdir $cache_dir
if ! rsync -vP "$BUILDD_USER$BUILDD_HOST:$RESULTS" "$RETURN_DIR" ;
then
scp "$BUILDD_USER$BUILDD_HOST:$RESULTS" "$RETURN_DIR"
fi
done
done
fi
rm -f "$REMOTE_SCRIPT"
# vi:sts=4:sw=4:noet:foldmethod=marker