1#!/bin/bash 2# Simple shell script for driving a remote cowbuilder via ssh 3# 4# Copyright(C) 2007, 2008, 2009, 2011, 2012, 2014, Ron <ron@debian.org> 5# This script is distributed according to the terms of the GNU GPL. 6 7set -e 8 9#BUILDD_HOST= 10#BUILDD_USER= 11BUILDD_ARCH="$(dpkg-architecture -qDEB_BUILD_ARCH 2>/dev/null)" 12 13# The 'default' dist is whatever cowbuilder is locally configured for 14BUILDD_DIST="default" 15 16INCOMING_DIR="cowbuilder-incoming" 17PBUILDER_BASE="/var/cache/pbuilder" 18 19#SIGN_KEYID= 20#UPLOAD_QUEUE="ftp-master" 21BUILDD_ROOTCMD="sudo" 22 23REMOTE_SCRIPT="cowssh_it" 24DEBOOTSTRAP="cdebootstrap" 25 26for f in /etc/cowpoke.conf ~/.cowpoke .cowpoke "$COWPOKE_CONF"; do [ -r "$f" ] && . "$f"; done 27 28 29get_archdist_vars() 30{ 31 _ARCHDIST_OPTIONS="RESULT_DIR BASE_PATH BASE_DIST CREATE_OPTS UPDATE_OPTS BUILD_OPTS SIGN_KEYID UPLOAD_QUEUE" 32 _RESULT_DIR="result" 33 _BASE_PATH="base.cow" 34 35 for arch in $BUILDD_ARCH; do 36 for dist in $BUILDD_DIST; do 37 for var in $_ARCHDIST_OPTIONS; do 38 eval "val=( \"\${${arch}_${dist}_${var}[@]}\" )" 39 40 if [ "$1" = "display" ]; then 41 case $var in 42 RESULT_DIR | BASE_PATH ) 43 [ ${#val[@]} -gt 0 ] || eval "val=\"$PBUILDER_BASE/$arch/$dist/\$_$var\"" 44 echo " ${arch}_${dist}_${var} = $val" 45 ;; 46 47 *_OPTS ) 48 # Don't display these if they are overridden on the command line. 49 eval "override=( \"\${OVERRIDE_${var}[@]}\" )" 50 [ ${#override[@]} -gt 0 ] || [ ${#val[@]} -eq 0 ] || 51 echo " ${arch}_${dist}_${var} =$(printf " '%s'" "${val[@]}")" 52 ;; 53 54 * ) 55 [ ${#val[@]} -eq 0 ] || echo " ${arch}_${dist}_${var} = $val" 56 ;; 57 esac 58 else 59 case $var in 60 RESULT_DIR | BASE_PATH ) 61 # These are always a single value, and must always be set, 62 # either by the user or to their default value. 63 [ ${#val[@]} -gt 0 ] || eval "val=\"$PBUILDER_BASE/$arch/$dist/\$_$var\"" 64 echo "${arch}_${dist}_${var}='$val'" 65 ;; 66 67 *_OPTS ) 68 # These may have zero, one, or many values which we must not word-split. 69 # They can safely remain unset if there are no values. 70 # 71 # We don't need to worry about the command line overrides here, 72 # they will be taken care of in the remote script. 73 [ ${#val[@]} -eq 0 ] || 74 echo "${arch}_${dist}_${var}=($(printf " %q" "${val[@]}") )" 75 ;; 76 77 SIGN_KEYID | UPLOAD_QUEUE ) 78 # We don't need these in the remote script 79 ;; 80 81 * ) 82 # These may have zero or one value. 83 # They can safely remain unset if there are no values. 84 [ ${#val[@]} -eq 0 ] || echo "${arch}_${dist}_${var}='$val'" 85 ;; 86 esac 87 fi 88 done 89 done 90 done 91} 92 93display_override_vars() 94{ 95 _OVERRIDE_OPTIONS="CREATE_OPTS UPDATE_OPTS BUILD_OPTS SIGN_KEYID UPLOAD_QUEUE" 96 97 for var in $_OVERRIDE_OPTIONS; do 98 eval "override=( \"\${OVERRIDE_${var}[@]}\" )" 99 [ ${#override[@]} -eq 0 ] || echo " override: $var =$(printf " '%s'" "${override[@]}")" 100 done 101} 102 103 104PROGNAME="$(basename $0)" 105version () 106{ 107 echo \ 108"This is $PROGNAME, from the Debian devscripts package, version ###VERSION### 109This code is Copyright 2007-2014, Ron <ron@debian.org>. 110This program comes with ABSOLUTELY NO WARRANTY. 111You are free to redistribute this code under the terms of the 112GNU General Public License." 113 exit 0 114} 115 116usage() 117{ 118 cat 1>&2 <<EOF 119 120cowpoke [options] package.dsc 121 122 Uploads a Debian source package to a cowbuilder host and builds it, 123 optionally also signing and uploading the result to an incoming queue. 124 The following options are supported: 125 126 --arch="arch" Specify the Debian architecture(s) to build for. 127 --dist="dist" Specify the Debian distribution(s) to build for. 128 --buildd="host" Specify the remote host to build on. 129 --buildd-user="name" Specify the remote user to build as. 130 --create Create the remote cowbuilder root if necessary. 131 --return[="path"] Copy results of the build to 'path'. If path is 132 not specified, return them to the current directory. 133 --no-return Do not copy results of the build to RETURN_DIR 134 (overriding a path set for it in the config files). 135 --sign="keyid" Specify the key to sign packages with. 136 --upload="queue" Specify the dput queue to upload signed packages to. 137 138 The current default configuration is: 139 140 BUILDD_HOST = $BUILDD_HOST 141 BUILDD_USER = $BUILDD_USER 142 BUILDD_ARCH = $BUILDD_ARCH 143 BUILDD_DIST = $BUILDD_DIST 144 RETURN_DIR = $RETURN_DIR 145 SIGN_KEYID = $SIGN_KEYID 146 UPLOAD_QUEUE = $UPLOAD_QUEUE 147 148 The expected remote paths are: 149 150 INCOMING_DIR = $INCOMING_DIR 151 PBUILDER_BASE = ${PBUILDER_BASE:-/} 152 153$(get_archdist_vars display) 154$(display_override_vars) 155 156 The cowbuilder image must have already been created on the build host 157 and the expected remote paths must already exist if the --create option 158 is not passed. You must have ssh access to the build host as BUILDD_USER 159 if that is set, else as the user executing cowpoke or a user specified 160 in your ssh config for '$BUILDD_HOST'. 161 That user must be able to execute cowbuilder as root using '$BUILDD_ROOTCMD'. 162 163EOF 164 165 exit $1 166} 167 168 169for arg; do 170 case "$arg" in 171 --arch=*) 172 BUILDD_ARCH="${arg#*=}" 173 ;; 174 175 --dist=*) 176 BUILDD_DIST="${arg#*=}" 177 ;; 178 179 --buildd=*) 180 BUILDD_HOST="${arg#*=}" 181 ;; 182 183 --buildd-user=*) 184 BUILDD_USER="${arg#*=}" 185 ;; 186 187 --create) 188 CREATE_COW="yes" 189 ;; 190 191 --return=*) 192 RETURN_DIR="${arg#*=}" 193 ;; 194 195 --return) 196 RETURN_DIR=. 197 ;; 198 199 --no-return) 200 RETURN_DIR= 201 ;; 202 203 --dpkg-opts=*) 204 # This one is a bit tricky, given the combination of the calling convention here, 205 # the calling convention for cowbuilder, and the behaviour of things that might 206 # pass this option to us. Some things, like when we are called from the gitpkg 207 # hook using options from git-config, will preserve any quoting that was used in 208 # the .gitconfig file, which is natural for anyone to want to use in a construct 209 # like: options = --dpkg-opts='-uc -us -j6'. People are going to cringe if we 210 # tell them they must not use quotes there no matter how much it may 'make sense' 211 # if you know too much about the internals. And it will only get worse when we 212 # then tell them they must quote it like that if they type it directly in their 213 # shell ... 214 # 215 # So we do the only thing that seems sensible, and try to Deal With It here. 216 # If the outermost characters are paired quotes, we manually strip them off. 217 # We don't want to let the shell do quote removal, since that might change a 218 # part of this which we don't want modified. 219 # We collect however many sets of those we are passed in an array, which we'll 220 # then combine back into a single argument at the final point of use. 221 # 222 # Which _should_ DTRT for anyone who isn't trying to blow this up deliberately 223 # and maybe will still do it for them too in spite of their efforts. But unless 224 # someone finds a sensible case this fails on, I'm not going to cry over people 225 # who want to stuff up their own system with input they created themselves. 226 val=${arg#*=} 227 [[ $val == \'*\' || $val == \"*\" ]] && val=${val:1:-1} 228 DEBBUILDOPTS+=( "$val" ) 229 ;; 230 231 --create-opts=*) 232 OVERRIDE_CREATE_OPTS+=( "${arg#*=}" ) 233 ;; 234 235 --update-opts=*) 236 OVERRIDE_UPDATE_OPTS+=( "${arg#*=}" ) 237 ;; 238 239 --build-opts=*) 240 OVERRIDE_BUILD_OPTS+=( "${arg#*=}" ) 241 ;; 242 243 --sign=*) 244 OVERRIDE_SIGN_KEYID=${arg#*=} 245 ;; 246 247 --upload=*) 248 OVERRIDE_UPLOAD_QUEUE=${arg#*=} 249 ;; 250 251 *.dsc) 252 DSC="$arg" 253 ;; 254 255 --help) 256 usage 0 257 ;; 258 259 --version) 260 version 261 ;; 262 263 *) 264 echo "ERROR: unrecognised option '$arg'" 265 usage 1 266 ;; 267 esac 268done 269 270if [ -z "$REMOTE_SCRIPT" ]; then 271 echo "No remote script name set. Aborted." 272 exit 1 273fi 274if [ -z "$DSC" ]; then 275 echo "ERROR: No package .dsc specified" 276 usage 1 277fi 278if ! [ -r "$DSC" ]; then 279 echo "ERROR: '$DSC' not found." 280 exit 1 281fi 282if [ -z "$BUILDD_ARCH" ]; then 283 echo "No BUILDD_ARCH set. Aborted." 284 exit 1 285fi 286if [ -z "$BUILDD_HOST" ]; then 287 echo "No BUILDD_HOST set. Aborted." 288 exit 1 289fi 290if [ -z "$BUILDD_ROOTCMD" ]; then 291 echo "No BUILDD_ROOTCMD set. Aborted." 292 exit 1 293fi 294if [ -e "$REMOTE_SCRIPT" ]; then 295 echo "$REMOTE_SCRIPT file already exists and will be overwritten." 296 echo -n "Do you wish to continue (Y/n)? " 297 read -e yesno 298 case "$yesno" in 299 N* | n*) 300 echo "Ok, bailing out." 301 echo "You should set the REMOTE_SCRIPT variable to some other value" 302 echo "if this name conflicts with something you already expect to use" 303 exit 1 304 ;; 305 *) ;; 306 esac 307fi 308 309[ -z "$BUILDD_USER" ] || BUILDD_USER="$BUILDD_USER@" 310 311PACKAGE="$(basename $DSC .dsc)" 312DATE="$(date +%Y%m%d 2>/dev/null)" 313 314 315cat > "$REMOTE_SCRIPT" <<-EOF 316 #!/bin/bash 317 # cowpoke generated remote worker script. 318 # Normally this should have been deleted already, you can safely remove it now. 319 320 compare_changes() 321 { 322 p1="\${1%_*.changes}" 323 p2="\${2%_*.changes}" 324 p1="\${p1##*_}" 325 p2="\${p2##*_}" 326 327 dpkg --compare-versions "\$p1" gt "\$p2" 328 } 329 330 $(get_archdist_vars) 331 332 for arch in $BUILDD_ARCH; do 333 for dist in $BUILDD_DIST; do 334 335 echo " ------- Begin build for \$arch \$dist -------" 336 337 CHANGES="\$arch.changes" 338 LOGFILE="$INCOMING_DIR/build.${PACKAGE}_\$arch.\$dist.log" 339 UPDATELOG="$INCOMING_DIR/cowbuilder-\${arch}-\${dist}-update-log-$DATE" 340 eval "RESULT_DIR=\"\\\$\${arch}_\${dist}_RESULT_DIR\"" 341 eval "BASE_PATH=\"\\\$\${arch}_\${dist}_BASE_PATH\"" 342 eval "BASE_DIST=\"\\\$\${arch}_\${dist}_BASE_DIST\"" 343 eval "CREATE_OPTS=( \"\\\${\${arch}_\${dist}_CREATE_OPTS[@]}\" )" 344 eval "UPDATE_OPTS=( \"\\\${\${arch}_\${dist}_UPDATE_OPTS[@]}\" )" 345 eval "BUILD_OPTS=( \"\\\${\${arch}_\${dist}_BUILD_OPTS[@]}\" )" 346 347 [ -n "\$BASE_DIST" ] || BASE_DIST=\$dist 348 [ ${#OVERRIDE_CREATE_OPTS[@]} -eq 0 ] || CREATE_OPTS=("${OVERRIDE_CREATE_OPTS[@]}") 349 [ ${#OVERRIDE_UPDATE_OPTS[@]} -eq 0 ] || UPDATE_OPTS=("${OVERRIDE_UPDATE_OPTS[@]}") 350 [ ${#OVERRIDE_BUILD_OPTS[@]} -eq 0 ] || BUILD_OPTS=("${OVERRIDE_BUILD_OPTS[@]}") 351 [ ${#DEBBUILDOPTS[*]} -eq 0 ] || DEBBUILDOPTS=("--debbuildopts" "${DEBBUILDOPTS[*]}") 352 353 354 # Sort the list of old changes files for this package to try and 355 # determine the most recent one preceding this version. We will 356 # debdiff to this revision in the final sanity checks if one exists. 357 # This is adapted from the insertion sort trickery in git-debimport. 358 359 OLD_CHANGES="\$(find "\$RESULT_DIR/" -maxdepth 1 -type f \\ 360 -name "${PACKAGE%%_*}_*_\$CHANGES" 2>/dev/null \\ 361 | sort 2>/dev/null)" 362 P=( \$OLD_CHANGES ) 363 count=\${#P[*]} 364 365 for(( i=1; i < count; ++i )) do 366 j=i 367 #echo "was \$i: \${P[i]}" 368 while ((\$j)) && compare_changes "\${P[j-1]}" "\${P[i]}"; do ((--j)); done 369 ((i==j)) || P=( \${P[@]:0:j} \${P[i]} \${P[j]} \${P[@]:j+1:i-(j+1)} \${P[@]:i+1} ) 370 done 371 #for(( i=1; i < count; ++i )) do echo "now \$i: \${P[i]}"; done 372 373 OLD_CHANGES= 374 for(( i=count-1; i >= 0; --i )) do 375 if [ "\${P[i]}" != "\$RESULT_DIR/${PACKAGE}_\$CHANGES" ]; then 376 OLD_CHANGES="\${P[i]}" 377 break 378 fi 379 done 380 381 382 set -eo pipefail 383 384 if ! [ -e "\$BASE_PATH" ]; then 385 if [ "$CREATE_COW" = "yes" ]; then 386 mkdir -p "\$RESULT_DIR" 387 mkdir -p "\$(dirname \$BASE_PATH)" 388 mkdir -p "$PBUILDER_BASE/aptcache" 389 $BUILDD_ROOTCMD cowbuilder --create --distribution \$BASE_DIST \\ 390 --basepath "\$BASE_PATH" \\ 391 --aptcache "$PBUILDER_BASE/aptcache" \\ 392 --debootstrap "$DEBOOTSTRAP" \\ 393 --debootstrapopts --arch="\$arch" \\ 394 "\${CREATE_OPTS[@]}" \\ 395 2>&1 | tee "\$UPDATELOG" 396 else 397 echo "SKIPPING \$dist/\$arch build, '\$BASE_PATH' does not exist" | tee "\$LOGFILE" 398 echo " use the cowpoke --create option to bootstrap a new build root" | tee -a "\$LOGFILE" 399 continue 400 fi 401 elif ! [ -e "\$UPDATELOG" ]; then 402 $BUILDD_ROOTCMD cowbuilder --update --distribution \$BASE_DIST \\ 403 --basepath "\$BASE_PATH" \\ 404 --aptcache "$PBUILDER_BASE/aptcache" \\ 405 --autocleanaptcache \\ 406 "\${UPDATE_OPTS[@]}" \\ 407 2>&1 | tee "\$UPDATELOG" 408 fi 409 $BUILDD_ROOTCMD cowbuilder --build --basepath "\$BASE_PATH" \\ 410 --aptcache "$PBUILDER_BASE/aptcache" \\ 411 --buildplace "$PBUILDER_BASE/build" \\ 412 --buildresult "\$RESULT_DIR" \\ 413 "\${DEBBUILDOPTS[@]}" \\ 414 "\${BUILD_OPTS[@]}" \\ 415 "$INCOMING_DIR/$(basename $DSC)" 2>&1 \\ 416 | tee "\$LOGFILE" 417 418 set +eo pipefail 419 420 421 echo >> "\$LOGFILE" 422 echo "lintian \$RESULT_DIR/${PACKAGE}_\$CHANGES" >> "\$LOGFILE" 423 lintian "\$RESULT_DIR/${PACKAGE}_\$CHANGES" 2>&1 | tee -a "\$LOGFILE" 424 425 if [ -n "\$OLD_CHANGES" ]; then 426 echo >> "\$LOGFILE" 427 echo "debdiff \$OLD_CHANGES ${PACKAGE}_\$CHANGES" >> "\$LOGFILE" 428 debdiff "\$OLD_CHANGES" "\$RESULT_DIR/${PACKAGE}_\$CHANGES" 2>&1 \\ 429 | tee -a "\$LOGFILE" 430 else 431 echo >> "\$LOGFILE" 432 echo "No previous packages for \$dist/\$arch to compare" >> "\$LOGFILE" 433 fi 434 435 done 436 done 437 438EOF 439chmod 755 "$REMOTE_SCRIPT" 440 441 442if ! dcmd rsync -vP $DSC "$REMOTE_SCRIPT" "$BUILDD_USER$BUILDD_HOST:$INCOMING_DIR"; 443then 444 dcmd scp $DSC "$REMOTE_SCRIPT" "$BUILDD_USER$BUILDD_HOST:$INCOMING_DIR" 445fi 446 447ssh -t "$BUILDD_USER$BUILDD_HOST" "\"$INCOMING_DIR/$REMOTE_SCRIPT\" && rm -f \"$INCOMING_DIR/$REMOTE_SCRIPT\"" 448 449echo 450echo "Build completed." 451 452for arch in $BUILDD_ARCH; do 453 CHANGES="$arch.changes" 454 for dist in $BUILDD_DIST; do 455 456 sign_keyid=$OVERRIDE_SIGN_KEYID 457 [ -n "$sign_keyid" ] || eval "sign_keyid=\"\$${arch}_${dist}_SIGN_KEYID\"" 458 [ -n "$sign_keyid" ] || sign_keyid="$SIGN_KEYID" 459 [ -n "$sign_keyid" ] || continue 460 461 eval "RESULT_DIR=\"\$${arch}_${dist}_RESULT_DIR\"" 462 [ -n "$RESULT_DIR" ] || RESULT_DIR="$PBUILDER_BASE/$arch/$dist/result" 463 464 _desc="$dist/$arch" 465 [ "$dist" != "default" ] || _desc="$arch" 466 467 while true; do 468 echo -n "Sign $_desc $PACKAGE with key '$sign_keyid' (yes/no)? " 469 read -e yesno 470 case "$yesno" in 471 YES | yes) 472 debsign "-k$sign_keyid" -r "$BUILDD_USER$BUILDD_HOST" "$RESULT_DIR/${PACKAGE}_$CHANGES" 473 474 upload_queue=$OVERRIDE_UPLOAD_QUEUE 475 [ -n "$upload_queue" ] || eval "upload_queue=\"\$${arch}_${dist}_UPLOAD_QUEUE\"" 476 [ -n "$upload_queue" ] || upload_queue="$UPLOAD_QUEUE" 477 478 if [ -n "$upload_queue" ]; then 479 while true; do 480 echo -n "Upload $_desc $PACKAGE to '$upload_queue' (yes/no)? " 481 read -e upload 482 case "$upload" in 483 YES | yes) 484 ssh "$BUILDD_USER$BUILDD_HOST" \ 485 "cd \"$RESULT_DIR/\" && dput \"$upload_queue\" \"${PACKAGE}_$CHANGES\"" 486 break 2 487 ;; 488 489 NO | no) 490 echo "Package upload skipped." 491 break 2 492 ;; 493 *) 494 echo "Please answer 'yes' or 'no'" 495 ;; 496 esac 497 done 498 fi 499 break 500 ;; 501 502 NO | no) 503 echo "Package signing skipped." 504 break 505 ;; 506 *) 507 echo "Please answer 'yes' or 'no'" 508 ;; 509 esac 510 done 511 done 512done 513 514if [ -n "$RETURN_DIR" ]; then 515 for arch in $BUILDD_ARCH; do 516 CHANGES="$arch.changes" 517 for dist in $BUILDD_DIST; do 518 519 eval "RESULT_DIR=\"\$${arch}_${dist}_RESULT_DIR\"" 520 [ -n "$RESULT_DIR" ] || RESULT_DIR="$PBUILDER_BASE/$arch/$dist/result" 521 522 523 cache_dir="./cowpoke-return-cache" 524 mkdir -p $cache_dir 525 526 scp "$BUILDD_USER$BUILDD_HOST:$RESULT_DIR/${PACKAGE}_$CHANGES" $cache_dir 527 528 for f in $(cd $cache_dir && dcmd ${PACKAGE}_$CHANGES); do 529 RESULTS="$RESULTS $RESULT_DIR/$f" 530 done 531 532 rm -f $cache_dir/${PACKAGE}_$CHANGES 533 rmdir $cache_dir 534 535 536 if ! rsync -vP "$BUILDD_USER$BUILDD_HOST:$RESULTS" "$RETURN_DIR" ; 537 then 538 scp "$BUILDD_USER$BUILDD_HOST:$RESULTS" "$RETURN_DIR" 539 fi 540 541 done 542 done 543fi 544 545rm -f "$REMOTE_SCRIPT" 546 547# vi:sts=4:sw=4:noet:foldmethod=marker 548