#! /bin/bash # No, we can not deal with sh alone. set -e set -u # ftpsync script for Debian # Based losely on a number of existing scripts, written by an # unknown number of different people over the years. # # Copyright (C) 2008 Joerg Jaspert # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; version 2. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # In case the admin somehow wants to have this script located someplace else, # he can set BASEDIR, and we will take that. If it is unset we take ${HOME} # How the admin sets this isn't our place to deal with. One could use a wrapper # for that. Or pam_env. Or whatever fits in the local setup. :) BASEDIR=${BASEDIR:-"${HOME}"} # Source our common functions . ${BASEDIR}/etc/common ######################################################################## ######################################################################## ## functions ## ######################################################################## ######################################################################## # We want to be able to get told what kind of sync we should do. This # might be anything, from the archive to sync, the stage to do, etc. A # list of currently understood and valid options is below. Multiple # options are seperated by space. All the words have to have the word # sync: in front or nothing will get used! # # Option Behaviour # stage1 Only do stage1 sync # stage2 Only do stage2 sync # all Do a complete sync # archive:foo Sync archive foo (if config for foo is available) # callback Call back when done (needs proper ssh setup for this to # work). It will always use the "command" callback:$HOSTNAME # where $HOSTNAME is the one defined below/in config and # will happen before slave mirrors are triggered. # # So to get us to sync all of the archive behind bpo and call back when # we are done, a trigger command of # "ssh $USER@$HOST sync:all sync:archive:bpo sync:callback" will do the # trick. check_commandline() { while [ $# -gt 0 ]; do case "$1" in sync:stage1) SYNCSTAGE1="true" SYNCALL="false" ;; sync:stage2) SYNCSTAGE2="true" SYNCALL="false" ;; sync:callback) SYNCCALLBACK="true" ;; sync:archive:*) ARCHIVE=${1##sync:archive:} ;; sync:all) SYNCALL="true" ;; *) echo "Unknown option ${1} ignored" ;; esac shift # Check next set of parameters. done } # All the stuff we want to do when we exit, no matter where cleanup() { trap - ERR TERM HUP INT QUIT EXIT # all done. Mail the log, exit. log "Mirrorsync done"; if [ -n "${MAILTO}" ]; then # In case rsync had something on stderr if [ -s "${LOGDIR}/rsync-${NAME}.error" ]; then cat "${LOGDIR}/rsync-${NAME}.error" | mail -e -s "$PROGRAM rsync ERROR ($(hostname -s)) [$$]" ${MAILTO} fi if [ "x${ERRORSONLY}x" = "xfalsex" ]; then # And the normal log mail -e -s "${PROGRAM} ($(hostname)) - archive sync finished on $(date +"%Y.%m.%d-%H:%M:%S")" ${MAILTO} < ${LOG} fi fi ${SAVELOG} ${LOGDIR}/rsync-${NAME}.log ${SAVELOG} ${LOGDIR}/rsync-${NAME}.error ${SAVELOG} "$LOG" > /dev/null rm -f ${LOCK}; } ######################################################################## ######################################################################## # As what are we called? NAME="`basename $0`" # The original command line arguments need to be saved! ORIGINAL_COMMAND="$*" # Now, check if we got told about stuff via ssh if [ -n "${SSH_ORIGINAL_COMMAND}" ]; then # We deliberately add "nothing" and ignore it right again, to avoid # people from outside putting some set options in the first place, # making us parse them... set "nothing" ${SSH_ORIGINAL_COMMAND} shift check_commandline "$*" fi # Now, we can locally override all the above variables by just putting # them into the .ssh/authorized_keys file forced command. if [ -n "${ORIGINAL_COMMAND}" ]; then set ${ORIGINAL_COMMAND} check_commandline "$*" fi # If we have been told to do stuff for a different archive than default, # set the name accordingly. if [ -n "${ARCHIVE}" ]; then NAME="${NAME}-${ARCHIVE}" fi # Now source the config for the archive we run on. # (Yes, people can also overwrite the options above in the config file # if they want to) . ${BASEDIR}/etc/${NAME}.conf ######################################################################## # Config options go here. Feel free to overwrite them in the config # # file if you need to. # # On debian.org machines the defaults should be ok. # # # # The following extra variables can be defined in the config file: # # # # ARCH_EXCLUDE # # can be used to exclude a complete architecture from # # mirrorring. Use as space seperated list. # # Possible values are: # # alpha, amd64, arm, armel, hppa, hurd-i386, i386, ia64, # # m68k, mipsel, mips, powerpc, s390, sh, sparc and source # # eg. ARCH_EXCLUDE="alpha arm armel mipsel mips s390 sparc" # # # # An unset value will mirror all architectures # ######################################################################## ######################################################################## # There should be nothing to edit here, use the config file # ######################################################################## HOSTNAME=${HOSTNAME:-`hostname -f`} # Where to put logfiles in LOGDIR=${LOGDIR:-"${BASEDIR}/log"} # Our own logfile LOG=${LOG:-"${LOGDIR}/${NAME}.log"} # Where should we put all the mirrored files? TO=${TO:-"/org/ftp.debian.org/ftp/"} # used by log() and error() PROGRAM=${PROGRAM:-"${NAME}-$(hostname -s)"} # Where to send mails about mirroring to? MAILTO=${MAILTO:-"mirrorlogs@debian.org"} # Want errors only or every log? ERRORSONLY=${ERRORSONLY:-"false"} # How to rotate our log SAVELOG=${SAVELOG:-"savelog -t -c 14"} # Lockfile program LOCKFILE=${LOCKFILE:-"lockfile"} # Our lockfile LOCK=${LOCK:-"${TO}/Archive-Update-in-Progress-${HOSTNAME}"} # Do we need another rsync run? UPDATEREQUIRED="${TO}/Archive-Update-Required-${HOSTNAME}" # Trace file for mirror stats and checks TRACE=${TRACE:-"project/trace/$(hostname -f)"} # rsync program RSYNC=${RSYNC:-rsync} # Default rsync options for *every* rsync call RSYNC_OPTIONS=${RSYNC_OPTIONS:-"-rltvHSB8192 --timeout 3600 --stats --exclude Archive-Update-in-Progress-${HOSTNAME} --exclude ${TRACE} --exclude Archive-Update-Required-${HOSTNAME}"} # Options we only use in the first pass, where we do not want packages/sources to fly in yet and dont want to delete files RSYNC_OPTIONS1=${RSYNC_OPTIONS1:-"--exclude Packages* --exclude Sources* --exclude Release* --exclude ls-lR*"} # Options for the second pass, where we do want everything, including deletion of old and now unused files RSYNC_OPTIONS2=${RSYNC_OPTIONS2:-"--max-delete=40000 --delay-updates --delete --delete-after --delete-excluded"} # Which rsync share to use on our upstream mirror? RSYNC_PATH=${RSYNC_PATH:-"ftp"} # Do we sync stage1? SYNCSTAGE1=${SYNCSTAGE1:-"false"} # Do we sync stage2? SYNCSTAGE2=${SYNCSTAGE2:-"false"} # Do we sync all? SYNCALL=${SYNCALL:-"true"} # Do we callback? SYNCCALLBACK=${SYNCCALLBACK:-"false"} # If we call back we need some more options defined in the config file. CALLBACKUSER=${CALLBACKUSER:-"archvsync"} CALLBACKHOST=${CALLBACKHOST:-"none"} CALLBACKKEY=${CALLBACKKEY:-"none"} # General excludes. Dont list architecture specific stuff here, use ARCH_EXCLUDE for that! EXCLUDE=${EXCLUDE:-""} # The temp directory used by rsync --delay-updates is not # world-readable remotely. Always exclude it to avoid errors. EXCLUDE="${EXCLUDE} --exclude .~tmp~/" SOURCE_EXCLUDE=${SOURCE_EXCLUDE:-""} # Exclude architectures defined in $ARCH_EXCLUDE for ARCH in ${ARCH_EXCLUDE}; do EXCLUDE=${EXCLUDE}"\ --exclude binary-${ARCH}/ \ --exclude disks-${ARCH}/ \ --exclude installer-${ARCH}/ \ --exclude Contents-${ARCH}.gz \ --exclude Contents-${ARCH}.bz2 \ --exclude Contents-${ARCH}.diff/ \ --exclude arch-${ARCH}.files \ --exclude arch-${ARCH}.list.gz \ --exclude *_${ARCH}.deb \ --exclude *_${ARCH}.udeb " if [ "${ARCH}" = "source" ]; then if [ -z ${SOURCE_EXCLUDE} ]; then SOURCE_EXCLUDE="\ --exclude source/ \ --exclude *.tar.gz \ --exclude *.diff.gz \ --exclude *.tar.bz2 \ --exclude *.diff.bz2 \ --exclude *.dsc " fi fi done # Some sane defaults cd ${BASEDIR} umask 002 # If we are here for the first time, create the # destination and the trace directory mkdir -p ${TO}/project/trace # Used to make sure we will have the archive fully and completly synced before # we stop, even if we get multiple pushes while this script is running. # Otherwise we can end up with a half-synced archive: # - get a push # - sync, while locked # - get another push. Of course no extra sync run then happens, we are locked. # - done. Archive not correctly synced, we don't have all the changes from the second push. touch "${UPDATEREQUIRED}" # Check to see if another sync is in progress if ${LOCKFILE} -! -l 43200 -r 0 "${LOCK}"; then error "Unable to start rsync, lock file still exists" exit 1 fi trap cleanup EXIT ERR TERM HUP INT QUIT # Start log by redirecting everything there. exec >"$LOG" 2>&1 log "Mirrorsync start" log "Acquired main lock" if [ "xtruex" = "x${SYNCCALLBACK}x" ] || [ "xnonex" = "x${CALLBACKHOST}x" ] || [ "xnonex" = "x${CALLBACKKEY}x"]; then SYNCCALLBACK="false" error "We are asked to call back, but we do not know where to and do not have a key, ignoring callback" fi if [ -n "${HOOK1}" ]; then log "Running hook1: ${HOOK1}" ${HOOK1} result=$? log "Back from hook1, got returncode ${result}" fi # Now, we might want to sync from anonymous too. # This is that deep in this script so hook1 could, if wanted, change things! if [ -z ${RSYNC_USER} ]; then RSYNCPTH="${RSYNC_HOST}" else RSYNCPTH="${RSYNC_USER}@${RSYNC_HOST}" fi # Now do the actual mirroring, and run as long as we have an updaterequired file. export RSYNC_PASSWORD while [ -e "${UPDATEREQUIRED}" ]; do log "Running mirrorsync, update is required, ${UPDATEREQUIRED} exists" rm -f "${UPDATEREQUIRED}" # if we want stage1 *or* all if [ "xtruex" = "x${SYNCSTAGE1}x"] || [ "xtruex" = "x${SYNCALL}x"]; then log "Running stage1: ${RSYNC} ${RSYNC_OPTIONS} ${RSYNC_OPTIONS1} ${EXCLUDE} ${SOURCE_EXCLUDE} ${RSYNCPTH}::${RSYNC_PATH} ${TO}" # Step one, sync everything except Packages/Releases ${RSYNC} ${RSYNC_OPTIONS} ${RSYNC_OPTIONS1} ${EXCLUDE} ${SOURCE_EXCLUDE} \ ${RSYNCPTH}::${RSYNC_PATH} ${TO} >${LOGDIR}/rsync-${NAME}.log 2>${LOGDIR}/rsync-${NAME}.error result=$? log "Back from rsync with returncode ${result}" else # Fake a good resultcode result=0 fi # Sync stage 1? # 24 - vanished source files. Ignored, that should be the target of $UPDATEREQUIRED # and us re-running. If it's not, uplink is broken anyways. if [ $result -eq 0 ] || [ $result -eq 24 ]; then if [ -n "${HOOK2}" ]; then log "Running hook2: ${HOOK1}" ${HOOK2} result=$? log "Back from hook1, got returncode ${result}" fi # if we want stage2 *or* all if [ "xtruex" = "x${SYNCSTAGE2}x"] || [ "xtruex" = "x${SYNCALL}x"]; then log "Running stage2: ${RSYNC} ${RSYNC_OPTIONS} ${RSYNC_OPTIONS2} ${EXCLUDE} ${SOURCE_EXCLUDE} ${RSYNCPTH}::${RSYNC_PATH} ${TO}" # We are lucky, it worked. Now do step 2 and sync again, this time including # the packages/releases files ${RSYNC} ${RSYNC_OPTIONS} ${RSYNC_OPTIONS2} ${EXCLUDE} ${SOURCE_EXCLUDE} \ ${RSYNCPTH}::${RSYNC_PATH} ${TO} >>${LOGDIR}/rsync-${NAME}.log 2>>${LOGDIR}/rsync-${NAME}.error result=$? log "Back from rsync with returncode ${result}" else # Fake a good resultcode result=0 fi # Sync stage 2? if [ $result -eq 0 ] || [ $result -eq 24 ]; then if [ -n "${HOOK3}" ]; then log "Running hook3: ${HOOK1}" ${HOOK3} result=$? log "Back from hook3, got returncode ${result}" fi else error "ERROR: Sync step 2 went wrong, got errorcode ${result}. Logfile: ${LOG}" exit 4 fi else error "ERROR: Sync step 1 went wrong, got errorcode ${result}. Logfile: ${LOG}" exit 3 fi done if [ -d "${TO}/project/trace" ]; then date -u > "${TO}/${TRACE}" fi if [ -n "${HOOK4}" ]; then log "Running hook4: ${HOOK1}" ${HOOK4} result=$? log "Back from hook4, got returncode ${result}" fi if [ "xtruex" = "x${SYNCCALLBACK}x" ]; then callback ${CALLBACKUSER} ${CALLBACKHOST} ${CALLBACKKEY} fi if [ x${HUB} = "xtrue" ]; then log "Trigger slave mirrors" ${BASEDIR}/bin/runmirrors log "Trigger slave done" if [ -n "${HOOK5}" ]; then log "Running hook5: ${HOOK1}" ${HOOK5} result=$? log "Back from hook5, got returncode ${result}" fi fi # All done, remove our LOCK, rest is done by cleanup hook. rm -f $LOCK