| 1 | # -*- mode:sh -*- |
| 2 | # Little common functions |
| 3 | |
| 4 | # push a mirror attached to us. |
| 5 | # Arguments (using an array named SIGNAL_OPTS): |
| 6 | # |
| 7 | # $MIRROR - Name for the mirror, also basename for the logfile |
| 8 | # $HOSTNAME - Hostname to push to |
| 9 | # $USERNAME - Username there |
| 10 | # $SSHPROTO - Protocol version, either 1 or 2. |
| 11 | # $SSHKEY - the ssh private key file to use for this push |
| 12 | # $SSHOPTS - any other option ssh accepts, passed blindly, be careful |
| 13 | # $PUSHLOCKOWN - own lockfile name to touch after stage1 in pushtype=staged |
| 14 | # $PUSHTYPE - what kind of push should be done? |
| 15 | # all - normal, just push once with ssh backgrounded and finish |
| 16 | # staged - staged. first push stage1, then wait for $PUSHLOCKs to appear, |
| 17 | # then push stage2 |
| 18 | # $PUSHARCHIVE - what archive to sync? (Multiple mirrors behind one ssh key!) |
| 19 | # $PUSHCB - do we want a callback? |
| 20 | # $PUSHKIND - whats going on? are we doing mhop push or already stage2? |
| 21 | # $FROMFTPSYNC - set to true if we run from within ftpsync. |
| 22 | # |
| 23 | # This function assumes that the variable LOG is set to a directory where |
| 24 | # logfiles can be written to. |
| 25 | # Additionally $PUSHLOCKS has to be defined as a set of space delimited strings |
| 26 | # (list of "lock"files) to wait for if you want pushtype=staged |
| 27 | # |
| 28 | # Pushes might be done in background (for type all). |
| 29 | signal () { |
| 30 | ARGS="SIGNAL_OPTS[*]" |
| 31 | local ${!ARGS} |
| 32 | |
| 33 | MIRROR=${MIRROR:-""} |
| 34 | HOSTNAME=${HOSTNAME:-""} |
| 35 | USERNAME=${USERNAME:-""} |
| 36 | SSHPROTO=${SSHPROTO:-""} |
| 37 | SSHKEY=${SSHKEY:-""} |
| 38 | SSHOPTS=${SSHOPTS:-""} |
| 39 | PUSHLOCKOWN=${PUSHLOCKOWN:-""} |
| 40 | PUSHTYPE=${PUSHTYPE:-"all"} |
| 41 | PUSHARCHIVE=${PUSHARCHIVE:-""} |
| 42 | PUSHCB=${PUSHCB:-""} |
| 43 | PUSHKIND=${PUSHKIND:-"all"} |
| 44 | FROMFTPSYNC=${FROMFTPSYNC:-"false"} |
| 45 | |
| 46 | # And now get # back to space... |
| 47 | SSHOPTS=${SSHOPTS/\#/ } |
| 48 | |
| 49 | # Defaults we always want, no matter what |
| 50 | SSH_OPTIONS="-o user=${USERNAME} -o BatchMode=yes -o ServerAliveInterval=45 -o ConnectTimeout=45 -o PasswordAuthentication=no" |
| 51 | |
| 52 | # If there are userdefined ssh options, add them. |
| 53 | if [ -n "${SSH_OPTS}" ]; then |
| 54 | SSH_OPTIONS="${SSH_OPTIONS} ${SSH_OPTS}" |
| 55 | fi |
| 56 | |
| 57 | # Does this machine need a special key? |
| 58 | if [ -n "${SSHKEY}" ]; then |
| 59 | SSH_OPTIONS="${SSH_OPTIONS} -i ${SSHKEY}" |
| 60 | fi |
| 61 | |
| 62 | # Does this machine have an extra own set of ssh options? |
| 63 | if [ -n "${SSHOPTS}" ]; then |
| 64 | SSH_OPTIONS="${SSH_OPTIONS} ${SSHOPTS}" |
| 65 | fi |
| 66 | |
| 67 | # Set the protocol version |
| 68 | if [ ${SSHPROTO} -ne 1 ] && [ ${SSHPROTO} -ne 2 ] && [ ${SSHPROTO} -ne 99 ]; then |
| 69 | # Idiots, we only want 1 or 2. Cant decide? Lets force 2. |
| 70 | SSHPROTO=2 |
| 71 | fi |
| 72 | if [ -n "${SSHPROTO}" ] && [ ${SSHPROTO} -ne 99 ]; then |
| 73 | SSH_OPTIONS="${SSH_OPTIONS} -${SSHPROTO}" |
| 74 | fi |
| 75 | |
| 76 | date -u >> "${LOGDIR}/${MIRROR}.log" |
| 77 | |
| 78 | PUSHARGS="" |
| 79 | # PUSHARCHIVE empty or not, we always add the sync:archive: command to transfer. |
| 80 | # Otherwise, if nothing else is added, ssh -f would not work ("no command to execute") |
| 81 | # But ftpsync does treat "sync:archive:" as the main archive, so this works nicely. |
| 82 | PUSHARGS="${PUSHARGS} sync:archive:${PUSHARCHIVE}" |
| 83 | |
| 84 | # We have a callback wish, tell downstreams |
| 85 | if [ -n "${PUSHCB}" ]; then |
| 86 | PUSHARGS="${PUSHARGS} sync:callback" |
| 87 | fi |
| 88 | # If we are running an mhop push AND our downstream is one to receive it, tell it. |
| 89 | if [ "xmhopx" = "x${PUSHKIND}x" ] && [ "xmhopx" = "x${PUSHTYPE}x" ]; then |
| 90 | PUSHARGS="${PUSHARGS} sync:mhop" |
| 91 | fi |
| 92 | |
| 93 | if [ "xallx" = "x${PUSHTYPE}x" ]; then |
| 94 | # Default normal "fire and forget" push. We background that, we do not care about the mirrors doings |
| 95 | log "Sending normal push" >> "${LOGDIR}/${MIRROR}.log" |
| 96 | PUSHARGS1="sync:all" |
| 97 | ssh -f $SSH_OPTIONS "${HOSTNAME}" "${PUSHARGS} ${PUSHARGS1}" >>"${LOGDIR}/${MIRROR}.log" |
| 98 | elif [ "xstagedx" = "x${PUSHTYPE}x" ] || [ "xmhopx" = "x${PUSHTYPE}x" ]; then |
| 99 | # Want a staged push. Fine, lets do that. Not backgrounded. We care about the mirrors doings. |
| 100 | log "Sending staged push" >> "${LOGDIR}/${MIRROR}.log" |
| 101 | |
| 102 | # Only send stage1 if we havent already send it. When called with stage2, we already did. |
| 103 | if [ "xstage2x" != "x${PUSHKIND}x" ]; then |
| 104 | # Step1: Do a push to only sync stage1, do not background |
| 105 | PUSHARGS1="sync:stage1" |
| 106 | ssh $SSH_OPTIONS "${HOSTNAME}" "${PUSHARGS} ${PUSHARGS1}" >>"${LOGDIR}/${MIRROR}.log" 2>&1 |
| 107 | touch "${PUSHLOCKOWN}" |
| 108 | |
| 109 | # Step2: Wait for all the other "lock"files to appear. |
| 110 | tries=0 |
| 111 | # We do not wait forever |
| 112 | while [ ${tries} -lt ${PUSHDELAY} ]; do |
| 113 | total=0 |
| 114 | found=0 |
| 115 | for file in ${PUSHLOCKS}; do |
| 116 | total=$((total + 1)) |
| 117 | if [ -f ${file} ]; then |
| 118 | found=$((found + 1)) |
| 119 | fi |
| 120 | done |
| 121 | if [ ${total} -eq ${found} ] || [ -f "${LOCKDIR}/all_stage1" ]; then |
| 122 | touch "${LOCKDIR}/all_stage1" |
| 123 | break |
| 124 | fi |
| 125 | tries=$((tries + 5)) |
| 126 | sleep 5 |
| 127 | done |
| 128 | # In case we did not have all PUSHLOCKS and still continued, note it |
| 129 | # This is a little racy, especially if the other parts decide to do this |
| 130 | # at the same time, but it wont hurt more than a mail too much, so I don't care much |
| 131 | if [ ${tries} -ge ${PUSHDELAY} ]; then |
| 132 | log "Failed to wait for all other mirrors. Failed ones are:" >> "${LOGDIR}/${MIRROR}.log" |
| 133 | for file in ${PUSHLOCKS}; do |
| 134 | if [ ! -f ${file} ]; then |
| 135 | log "${file}" >> "${LOGDIR}/${MIRROR}.log" |
| 136 | error "Missing Pushlockfile ${file} after waiting ${tries} second, continuing" |
| 137 | fi |
| 138 | done |
| 139 | fi |
| 140 | rm -f "${PUSHLOCKOWN}" |
| 141 | fi |
| 142 | |
| 143 | # Step3: It either timed out or we have all the "lock"files, do the rest |
| 144 | # If we are doing mhop AND are called from ftpsync - we now exit. |
| 145 | # That way we notify our uplink that we and all our clients are done with their |
| 146 | # stage1. It can then finish its own, and if all our upstreams downlinks are done, |
| 147 | # it will send us stage2. |
| 148 | # If we are not doing mhop or are not called from ftpsync, we start stage2 |
| 149 | if [ "xtruex" = "x${FROMFTPSYNC}x" ] && [ "xmhopx" = "x${PUSHKIND}x" ]; then |
| 150 | return |
| 151 | else |
| 152 | PUSHARGS2="sync:stage2" |
| 153 | log "Now doing the second stage push" >> "${LOGDIR}/${MIRROR}.log" |
| 154 | ssh $SSH_OPTIONS "${HOSTNAME}" "${PUSHARGS} ${PUSHARGS2}" >>"${LOGDIR}/${MIRROR}.log" 2>&1 |
| 155 | fi |
| 156 | else |
| 157 | # Can't decide? Then you get nothing. |
| 158 | return |
| 159 | fi |
| 160 | } |
| 161 | |
| 162 | # callback, used by ftpsync |
| 163 | callback () { |
| 164 | # Defaults we always want, no matter what |
| 165 | SSH_OPTIONS="-o BatchMode=yes -o ServerAliveInterval=45 -o ConnectTimeout=45 -o PasswordAuthentication=no" |
| 166 | ssh $SSH_OPTIONS -i "$3" -o"user $1" "$2" callback:${HOSTNAME} |
| 167 | } |
| 168 | |
| 169 | # log something (basically echo it together with a timestamp) |
| 170 | # |
| 171 | # Set $PROGRAM to a string to have it added to the output. |
| 172 | log () { |
| 173 | if [ -z "${PROGRAM}" ]; then |
| 174 | echo "$(date +"%b %d %H:%M:%S") $(hostname -s) [$$] $@" |
| 175 | else |
| 176 | echo "$(date +"%b %d %H:%M:%S") $(hostname -s) ${PROGRAM}[$$]: $@" |
| 177 | fi |
| 178 | } |
| 179 | |
| 180 | # log the message using log() but then also send a mail |
| 181 | # to the address configured in MAILTO (if non-empty) |
| 182 | error () { |
| 183 | log "$@" |
| 184 | if [ -n "${MAILTO}" ]; then |
| 185 | echo "$@" | mail -e -s "[$PROGRAM@$(hostname -s)] ERROR [$$]" ${MAILTO} |
| 186 | fi |
| 187 | } |
| 188 | |
| 189 | # run a hook |
| 190 | # needs array variable HOOK setup with HOOKNR being a number an HOOKSCR |
| 191 | # the script to run. |
| 192 | hook () { |
| 193 | ARGS='HOOK[@]' |
| 194 | local "${!ARGS}" |
| 195 | if [ -n "${HOOKSCR}" ]; then |
| 196 | log "Running hook $HOOKNR: ${HOOKSCR}" |
| 197 | set +e |
| 198 | ${HOOKSCR} |
| 199 | result=$? |
| 200 | set -e |
| 201 | if [ ${result} -ne 0 ] ; then |
| 202 | error "Back from hook $HOOKNR, got returncode ${result}" |
| 203 | else |
| 204 | log "Back from hook $HOOKNR, got returncode ${result}" |
| 205 | fi |
| 206 | return $result |
| 207 | else |
| 208 | return 0 |
| 209 | fi |
| 210 | } |
| 211 | |
| 212 | # Return the list of 2-stage mirrors. |
| 213 | get2stage() { |
| 214 | egrep '^(staged|mhop)' "${MIRRORS}" | { |
| 215 | while read MTYPE MLNAME MHOSTNAME MUSER MPROTO MKEYFILE; do |
| 216 | PUSHLOCKS="${LOCKDIR}/${MLNAME}.stage1 ${PUSHLOCKS}" |
| 217 | done |
| 218 | echo "$PUSHLOCKS" |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | # Rotate logfiles |
| 223 | savelog() { |
| 224 | torotate="$1" |
| 225 | count=${2:-${LOGROTATE}} |
| 226 | while [ ${count} -gt 0 ]; do |
| 227 | prev=$(( count - 1 )) |
| 228 | if [ -e "${torotate}.${prev}" ]; then |
| 229 | mv "${torotate}.${prev}" "${torotate}.${count}" |
| 230 | fi |
| 231 | count=$prev |
| 232 | done |
| 233 | mv "${torotate}" "${torotate}.0" |
| 234 | } |
| 235 | |
| 236 | # Return rsync version |
| 237 | rsync_protocol() { |
| 238 | RSYNC_VERSION="$(${RSYNC} --version)" |
| 239 | RSYNC_REGEX="(protocol[ ]+version[ ]+([0-9]+))" |
| 240 | if [[ ${RSYNC_VERSION} =~ ${RSYNC_REGEX} ]]; then |
| 241 | echo ${BASH_REMATCH[2]} |
| 242 | fi |
| 243 | unset RSYNC_VERSION RSYNC_REGEX |
| 244 | } |