#!/bin/bash

# mkupdate-with-scores
#
# This script generates, tests, and publishes rule updates for stable release
# versions.  It does the following:
#
# - retrieves the latest gernerated scores for new active.list rules
# - checks out the trunk revision of code that those scores were generated for
# - generates an update tarball and associated sha1 and asc files
# - checks out each of the 3.3 stable release tagged versions, builds and
#   installs that version (in a tmp dir) and then installs the above generated
#   update using sa-update --install to make sure it works with each version
# - if all goes well, it copies the update files to the update www directory,
#   updates the dns zone files and schedules (using the at queue) an update
#   of the zone soa and rndc reload using the tick_zone_serial script
#
# This script is similar to the run_part2 script used for trunk rule updates.
#
# Update May 19, 2011: this script now also takes a fourth parameter to do a
# reversion to an existing rule update.  This is useful for releasing an
# emergency update to correct a bad update that was automatically (or
# otherwise) released.
#
# The script also takes three initial parameters that are used for testing
# purposes.  The first is a root prefix for testing.  The second is a keydir.
# The third is a flag to update the local svn co of this script's directory.
#
# If https://svn.apache.org/repos/asf/spamassassin/trunk/rulesrc/scores/DISABLE-AUTOMATIC-UPDATES 
# exists then DNS updates will be skipped so that update publishing is
# effectively disabled.  Note that generated updates will still be visible
# on the mirrors but will not be published in DNS for sa-update clients.
# ALSO NOTE that this only applies to update generation.  DNS *will* be
# update when the script is run with a fourth parameter to revert to an
# existing version/revision update.

set -e
set -x

umask 022

UPDATEDIR=/var/www/buildbot.spamassassin.org/updatestage
DNSDIR=/var/named/updates.spamassassin.org.d
KEYDIR=/home/updatesd/key
UPDATE_BUILD_DIR=0
REVERT_REVISION=0

# if $1 is present redirect output files to a test directory structure
if [ ${#1} -gt 1 ]; then
  UPDATEDIR=$1$UPDATEDIR
  DNSDIR=$1$DNSDIR

  # make the test directory structure
  mkdir -p $UPDATEDIR
  mkdir -p $DNSDIR
fi

if [ ${#2} -gt 1 ]; then
  KEYDIR=$2
fi

if [ ${#3} -gt 1 ]; then
  UPDATE_BUILD_DIR=1
fi

if [ $4 ]; then
  REVERT_REVISION=$4
fi

echo "UPDATEDIR=$UPDATEDIR"
echo "DNSDIR=$DNSDIR"
echo "KEYDIR=$KEYDIR"
echo "REVERT_REVISION=$REVERT_REVISION"

test_version() {
  SA_VERSION=$1
  SA_SVN_TAG=$2

  # to heck with dealing with svn update failures
  rm -rf release_$SA_VERSION

  # test the release on the version(s) of spamassassin the update is meant for
  svn co https://svn.apache.org/repos/asf/spamassassin/$SA_SVN_TAG release_$SA_VERSION

  cd release_$SA_VERSION

  perl Makefile.PL PREFIX=$TMPDIR/release_$SA_VERSION < /dev/null
  make
  make install

  set +e
  ./sa-update -D --install $TMPDIR/${REVISION}.tar.gz
  STATUS=$?
  set -e

  cd ..
  rm -rf release_$SA_VERSION $TMPDIR/release_$SA_VERSION
  return $STATUS
}

update_dns_record() {
  SA_VERSION=$1
  UPDATE_REVISION=$2

  if [ $AUTOUPDATESDISABLED -eq 1 -a $REVERT_REVISION -eq 0 ]; then
    echo "DNS updating disabled (auto update publishing disabled), skipping DNS record update"
    return 0
  fi

  # turn "3.2.0" into "0.2.3"
  RVERS=`echo $SA_VERSION | perl -pe 's/^(\d+)\.(\d+)\.(\d+)$/$3.$2.$1/'`

  DNS_RECORD="$RVERS  TXT  \"$UPDATE_REVISION\""
  echo "DNS Record: $DNS_RECORD"

  DNSFILE="$DNSDIR/$SA_VERSION"
  
  mkdir $TMPDIR/dns-backup

  set +e
  cp $DNSFILE $TMPDIR/dns-backup/.
  set -e

  # set -e should catch any errors here
  echo $DNS_RECORD > $DNSFILE.mkupdate-with-scores.new
  mv $DNSFILE.mkupdate-with-scores.new $DNSFILE

  return 0
}

revert_dns_record() {
  SA_VERSION=$1

  if [ $AUTOUPDATESDISABLED -eq 1 -a $REVERT_REVISION -eq 0 ]; then
    echo "DNS updating disabled (auto update publishing disabled), skipping DNS record revert"
    return 0
  fi

  DNSFILE="$DNSDIR/$SA_VERSION"

  set +e
  cp $TMPDIR/dns-backup/$SA_VERSION $DNSFILE
  set -e
}

copy_update_paranoid() {
  SRC=$1
  DST=$2

  set +e
  cp $SRC $DST
  diff -u $SRC $DST
  if [ $? -ne 0 ]; then
    set -e
    return 1
  fi
  set -e
  return 0
}

make_rule_update_from_trunk() {
  # to heck with dealing with svn update failures
  rm -rf trunk trunk-rulesrc-scores

  # get the latest scores for new rules
  svn co https://svn.apache.org/repos/asf/spamassassin/trunk/rulesrc/scores trunk-rulesrc-scores

  # get the revision number of the rules
  # TODO: have the script that make 72_scores.cf include a revision number
  #REVISION=`head -1 trunk-rulesrc-scores/72_scores.cf | cut -d" " -f6`
  REVISION=`head -1 trunk-rulesrc-scores/scores-set* | cut -d" " -f9 | sort -rn | head -1`

  svn co --revision=$REVISION https://svn.apache.org/repos/asf/spamassassin/trunk trunk

  cd trunk

  if [ $UPDATE_BUILD_DIR ]; then
    svn up build
  fi

  perl Makefile.PL PREFIX=$TMPDIR/trunk < /dev/null
  make

  cd ..

  cp trunk-rulesrc-scores/72_scores.cf trunk/rules/72_scores.cf

  # note: one of set0 or set1 stats might be incorrect (not all of their rules
  #       are included in the update) I can't remember if we eliminate dropped
  #       rules in generate-new-scores or not (we run the sets in a particular
  #       order for some reason)
  cp trunk-rulesrc-scores/stats-set0 trunk/rules/STATISTICS-set0-72_scores.cf.txt
  cp trunk-rulesrc-scores/stats-set1 trunk/rules/STATISTICS-set1-72_scores.cf.txt
  cp trunk-rulesrc-scores/stats-set2 trunk/rules/STATISTICS-set2-72_scores.cf.txt
  cp trunk-rulesrc-scores/stats-set3 trunk/rules/STATISTICS-set3-72_scores.cf.txt

  cd trunk/rules

  # remove files we don't want to ship in updates
  # remember that 3KB == 1GB of traffic on the mirrors as of Jan 1, 2010
  set +e
  rm 70_sandbox.cf 70_inactive.cf
  rm STATISTICS-set?.txt
  set -e

  mkdir -p $TMPDIR/trunk/etc/mail/spamassassin
  #cp *.pre *.cf *.txt languages user_prefs.template $TMPDIR/trunk/etc/mail/spamassassin/.

  ../spamassassin --lint -D

  tar cvf - *.cf *.txt languages user_prefs.template | gzip -9 > $TMPDIR/${REVISION}.tar.gz

  cd ../..

  shasum $TMPDIR/${REVISION}.tar.gz > $TMPDIR/${REVISION}.tar.gz.sha1
  gpg --batch --homedir $KEYDIR -bas $TMPDIR/${REVISION}.tar.gz || exit $?
}

copy_existing_update_for_reversion_testing() {
  EXIT=0
  (
    copy_update_paranoid "$UPDATEDIR/$REVERT_REVISION.tar.gz"      "$TMPDIR/$REVISION.tar.gz" &&
    copy_update_paranoid "$UPDATEDIR/$REVERT_REVISION.tar.gz.asc"  "$TMPDIR/$REVISION.tar.gz.asc" &&
    copy_update_paranoid "$UPDATEDIR/$REVERT_REVISION.tar.gz.sha1" "$TMPDIR/$REVISION.tar.gz.sha1"
  ) || EXIT=6

  # copying the update files went wrong exit
  if [ $EXIT -gt 0 ]; then
    echo "Could not copy existing revision $REVERT_REVISION, to temporary testing directory, aborting!"
    exit $EXIT
  fi
}

check_for_disable-automatic-update_file_in_svn() {
  rm -rf $TMPDIR/svn-scores-latest
  # checkout the latest scores directory
  svn co https://svn.apache.org/repos/asf/spamassassin/trunk/rulesrc/scores/ $TMPDIR/svn-scores-latest

  AUTOUPDATESDISABLED=0

  if [ -f $TMPDIR/svn-scores-latest/DISABLE-AUTOMATIC-UPDATES ]; then
    echo "Auto-updates have been previously disabled... continuing with manual update reversion"
    AUTOUPDATESDISABLED=1
  fi

  return $AUTOUPDATESDISABLED
}

disable_auto_update_publishing_and_get_new_update_revision_number() {
  date > $TMPDIR/svn-scores-latest/DISABLE-AUTOMATIC-UPDATES
  echo "Automatic sa-update rule update publishing has been disabled via the revert-stable-update script." >> $TMPDIR/svn-scores-latest/DISABLE-AUTOMATIC-UPDATES
  echo "Current stable update is being reverted to update $REVERT_REVISION." >> $TMPDIR/svn-scores-latest/DISABLE-AUTOMATIC-UPDATES
  echo "Update version $REVERT_REVISION will be republished using this commit's revision number as the new version number." >> $TMPDIR/svn-scores-latest/DISABLE-AUTOMATIC-UPDATES
  echo "To re-enable updates: publish the latest update using the 'revert-stable-update' script (this is optional)" >> $TMPDIR/svn-scores-latest/DISABLE-AUTOMATIC-UPDATES
  echo "and then delete this file from SVN to re-enable DNS publishing of generated updates." >> $TMPDIR/svn-scores-latest/DISABLE-AUTOMATIC-UPDATES

  if [ $AUTOUPDATESDISABLED ]; then
    svn add $TMPDIR/svn-scores-latest/DISABLE-AUTOMATIC-UPDATES
  fi

  svn ci $TMPDIR/svn-scores-latest/* -m "sa-update auto-update disabled; reversion to version $REVERT_REVISION in progress; version $REVERT_REVISION will be republished as the same version number of this commit revision number" > $TMPDIR/NEW-REVERT-REVISION 2>&1

  set +e
  grep revision $TMPDIR/NEW-REVERT-REVISION
  if [ $? -ne 0 ]; then
    echo "Failed to obtain a new revision number to use as the new update version number."
    exit 7
  fi
  set -e

  REVISION=`cat $TMPDIR/NEW-REVERT-REVISION | grep revision | cut -d" " -f3 | cut -d "." -f1`

  echo "New update version/revision will be $REVISION"
}

TMPDIR="/tmp/sa-mkupdate-$$"
rm -rf $TMPDIR
mkdir $TMPDIR
cd $TMPDIR

if [ $REVERT_REVISION -eq 0 ]; then
  set +e
  check_for_disable-automatic-update_file_in_svn
  AUTOUPDATESDISABLED=$?
  set -e

  # generate a rule update using rules from trunk at a revision
  # that we have generated scores for
  make_rule_update_from_trunk
else
  if [ ! -f $UPDATEDIR/$REVERT_REVISION.tar.gz -a ! -f $UPDATEDIR/$REVERT_REVISION.tar.gz.asc -a ! -f $UPDATEDIR/$REVERT_REVISION.tar.gz.sha1 ]; then
    echo "Could not find update files for update revision $REVERT_REVISION, aborting."
    exit 8
  fi
  set +e
  check_for_disable-automatic-update_file_in_svn
  AUTOUPDATESDISABLED=$?
  set -e

  disable_auto_update_publishing_and_get_new_update_revision_number
  echo "Copying existing version/revision $REVERT_REVISION to new version/revision $REVISION for testing."
  copy_existing_update_for_reversion_testing
fi

# test to make sure it works with sa-update --install
UPDATED_VERSIONS=0
MINOR_VERS=0

for (( MINOR_VERS=0; 1; MINOR_VERS++ )); do
  set +e
  svn info https://svn.apache.org/repos/asf/spamassassin/tags/spamassassin_release_3_3_$MINOR_VERS | grep Revision
  VERSION_EXISTS=$?
  set -e
  if [ $VERSION_EXISTS -ne 0 ]; then
    break;
  fi
  test_version 3.3.$MINOR_VERS tags/spamassassin_release_3_3_$MINOR_VERS \
    && update_dns_record "3.3.$MINOR_VERS" "$REVISION" && UPDATED_VERSIONS=$((UPDATED_VERSIONS+1))
done

# we just assume that the next stable version is the branch's current version
test_version 3.3.$MINOR_VERS branches/3.3 \
  && update_dns_record "3.3.$MINOR_VERS" "$REVISION" && UPDATED_VERSIONS=$((UPDATED_VERSIONS+1))

echo "VERSIONS UPDATE PASSED ON: $UPDATED_VERSIONS"

# publish update
if [ $UPDATED_VERSIONS -gt 0 ]; then
  # be careful, we want to make sure that the DNS records are always in a good state;
  # even if we end up exiting unexpectedly due to an error
  EXIT=0
  (
    copy_update_paranoid "$TMPDIR/$REVISION.tar.gz"      "$UPDATEDIR/$REVISION.tar.gz" &&
    copy_update_paranoid "$TMPDIR/$REVISION.tar.gz.asc"  "$UPDATEDIR/$REVISION.tar.gz.asc" &&
    copy_update_paranoid "$TMPDIR/$REVISION.tar.gz.sha1" "$UPDATEDIR/$REVISION.tar.gz.sha1" &&
    chmod 544 $UPDATEDIR/$REVISION.tar.gz*
  ) || EXIT=5

  # copying the update files went wrong, revert dns and exit
  if [ $EXIT -gt 0 ]; then
    for (( I=0; I<=$MINOR_VERS; I++ )); do
      revert_dns_record "3.3.$I"
    done
    exit $EXIT
  fi

  # schedule dns commit/reload atq job
  # note that we're probably not going to be able to commit the DNS changes
  # until the update tarball mirrors have had time to sync since the ASF
  # name servers will probably update and reload via a commit hook trigger or
  # on a set interval

  if [ $AUTOUPDATESDISABLED -eq 1 -a $REVERT_REVISION -eq 0 ]; then
    echo "DNS updating disabled (auto update publishing disabled), skipping DNS reload"
  else
    # delete any existing jobs in at queue 'n' used for named reloads
    for JOB in `at -l -q n | cut -d" " -f1`; do atrm $JOB; done

    # schedule a job to tick the zone serial and reload named in 16 minutes
    # (mirror rsyncs are done every 15 minutes)
    cd
    echo "/export/home/updatesd/svn/spamassassin/build/mkupdates/tick_zone_serial" | at -q n now + 16min
  fi
fi

cd
rm -rf $TMPDIR
