import mirror

from tempfile import TemporaryDirectory
from pathlib import Path
import subprocess
import logging
import os

SCRIPT = """#!/usr/bin/env bash\n# No, we can not deal with sh alone.\n\nset -e\nset -u\n# ERR traps should be inherited from functions too. (And command\n# substitutions and subshells and whatnot, but for us the function is\n# the important part here)\nset -E\n\n# A pipeline\'s return status is the value of the last (rightmost)\n# command to exit with a non-zero status, or zero if all commands exit\n# success fully.\nset -o pipefail\n\n# ftpsync script for Debian\n# Based losely on a number of existing scripts, written by an\n# unknown number of different people over the years.\n#\n# Copyright (C) 2008-2016 Joerg Jaspert <joerg@debian.org>\n# Copyright (C) 2016 Peter Palfrader\n#\n# This program is free software; you can redistribute it and/or\n# modify it under the terms of the GNU General Public License as\n# published by the Free Software Foundation; version 2.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program; if not, write to the Free Software\n# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n\nVERSION="20180513"\n# -*- mode:sh -*-\n# vim:syn=sh\n# Little common functions\n\n# push a mirror attached to us.\n# Arguments (using an array named SIGNAL_OPTS):\n#\n# $MIRROR      - Name for the mirror, also basename for the logfile\n# $HOSTNAME    - Hostname to push to\n# $USERNAME    - Username there\n# $SSHPROTO    - Protocol version, either 1 or 2.\n# $SSHKEY      - the ssh private key file to use for this push\n# $SSHOPTS     - any other option ssh accepts, passed blindly, be careful\n# $PUSHLOCKOWN - own lockfile name to touch after stage1 in pushtype=staged\n# $PUSHTYPE    - what kind of push should be done?\n#                all    - normal, just push once with ssh backgrounded and finish\n#
  staged - staged. first push stage1, then wait for $PUSHLOCKs to appear,\n#                         then push stage2\n# $PUSHARCHIVE - what archive to sync? (Multiple mirrors behind one ssh key!)\n# $PUSHCB      - do we want a callback?\n# $PUSHKIND    - whats going on? are we doing mhop push or already stage2?\n# $FROMFTPSYNC - set to true if we run from within ftpsync.\n#\n# This function assumes that the variable LOG is set to a directory where\n# logfiles can be written to.\n# Additionally $PUSHLOCKS has to be defined as a set of space delimited strings\n# (list of "lock"files) to wait for if you want pushtype=staged\n#\n# Pushes might be done in background (for type all).\nsignal () {\n    ARGS="SIGNAL_OPTS[*]"\n    local ${!ARGS}\n\n    MIRROR=${MIRROR:-""}\n    HOSTNAME=${HOSTNAME:-""}\n    USERNAME=${USERNAME:-""}\n    SSHPROTO=${SSHPROTO:-""}\n    SSHKEY=${SSHKEY:-""}\n    SSHOPTS=${SSHOPTS:-""}\n    PUSHLOCKOWN=${PUSHLOCKOWN:-""}\n    PUSHTYPE=${PUSHTYPE:-"all"}\n    PUSHARCHIVE=${PUSHARCHIVE:-""}\n    PUSHCB=${PUSHCB:-""}\n    PUSHKIND=${PUSHKIND:-"all"}\n    FROMFTPSYNC=${FROMFTPSYNC:-"false"}\n\n    # And now get # back to space...\n    SSHOPTS=${SSHOPTS/\\#/ }\n\n    # Defaults we always want, no matter what\n    SSH_OPTIONS="-o user=${USERNAME} -o BatchMode=yes -o ServerAliveInterval=45 -o ConnectTimeout=45 -o PasswordAuthentication=no"\n\n    # If there are userdefined ssh options, add them.\n    if [[ -n ${SSH_OPTS} ]]; then\n        SSH_OPTIONS="${SSH_OPTIONS} ${SSH_OPTS}"\n    fi\n\n    # Does this machine need a special key?\n    if [[ -n ${SSHKEY} ]]; then\n        SSH_OPTIONS="${SSH_OPTIONS} -i ${SSHKEY}"\n    fi\n\n    # Does this machine have an extra own set of ssh options?\n    if [[ -n ${SSHOPTS} ]]; then\n        SSH_OPTIONS="${SSH_OPTIONS} ${SSHOPTS}"\n    fi\n\n    # Set the protocol version\n    if [[ ${SSHPROTO} -ne 1 ]] && [[ ${SSHPROTO} -ne 2 ]] && [[ ${SSHPROTO} -ne 99 ]]; then\n        # Idiots, we only want 1 or 2. Cant decide? Lets force 2.\n        SSHPROTO=2\n    fi\n\n    if [[ -n ${SSHPROTO} ]] && [[ ${SSHPROTO} -ne 99 ]]; then\n        SSH_OPTIONS="${SSH_OPTIONS} -${SSHPROTO}"\n    fi\n\n    date -u >> "${LOGDIR}/${MIRROR}.log"\n\n    PUSHARGS=""\n    # PUSHARCHIVE empty or not, we always add the sync:archive: command to transfer.\n    # Otherwise, if nothing else is added, ssh -f would not work ("no command to execute")\n    # But ftpsync does treat "sync:archive:" as the main archive, so this works nicely.\n    PUSHARGS="${PUSHARGS} sync:archive:${PUSHARCHIVE}"\n\n    # We have a callback wish, tell downstreams\n    if [[ -n ${PUSHCB} ]]; then\n        PUSHARGS="${PUSHARGS} sync:callback"\n    fi\n    # If we are running an mhop push AND our downstream is one to receive it, tell it.\n    if [[ mhop = ${PUSHKIND} ]] && [[ mhop = ${PUSHTYPE} ]]; then\n        PUSHARGS="${PUSHARGS} sync:mhop"\n    fi\n\n    if [[ all = ${PUSHTYPE} ]]; then\n        # Default normal "fire and forget" push. We background that, we do not care about the mirrors doings\n        PUSHARGS1="sync:all"\n        signal_ssh "normal" "${MIRROR}" "${HOSTNAME}" $SSH_OPTIONS "${PUSHARGS} ${PUSHARGS1}"\n    elif [[ staged = ${PUSHTYPE} ]] || [[ mhop = ${PUSHTYPE} ]]; then\n        # Want a staged push. Fine, lets do that. Not backgrounded. We care about the mirrors doings.\n        # Only send stage1 if we havent already send it. When called with stage2, we already did.\n        if [[ stage2 != ${PUSHKIND} ]]; then\n            # Step1: Do a push to only sync stage1, do not background\n            PUSHARGS1="sync:stage1"\n            signal_ssh "first stage" "${MIRROR}" "${HOSTNAME}" $SSH_OPTIONS "${PUSHARGS} ${PUSHARGS1}"\n            touch "${PUSHLOCKOWN}"\n\n            # Step2: Wait for all the other "lock"files to appear.\n            # In case we did not have all PUSHLOCKS and still continued, note it\n            # This is a little racy, especially if the other parts decide to do this\n            # at the same time, but it wont hurt more than a mail too much, so I don\'t care much\n            if ! wait_for_pushlocks ${PUSHDELAY}; then\n                msg "Failed to wait for all other mirrors. Failed ones are:" >> "${LOGDIR}/${MIRROR}.log"\n                for file in ${PUSHLOCKS}; do\n                    if [[ ! -f ${file} ]]; then\n                        msg "${file}" >> "${LOGDIR}/${MIRROR}.log"\n                        log "Missing Pushlockfile ${file} after waiting for more than ${PUSHDELAY} seconds, continuing"\n                    fi\n
  done\n            fi\n            rm -f "${PUSHLOCKOWN}"\n        fi\n\n        # Step3: It either timed out or we have all the "lock"files, do the rest\n        # If we are doing mhop AND are called from ftpsync - we now exit.\n        # That way we notify our uplink that we and all our clients are done with their\n        # stage1. It can then finish its own, and if all our upstreams downlinks are done,\n        # it will send us stage2.\n        # If we are not doing mhop or are not called from ftpsync, we start stage2\n        if [[ true = ${FROMFTPSYNC} ]] && [[ mhop = ${PUSHKIND} ]]; then\n            return\n        else\n            PUSHARGS2="sync:stage2"\n            signal_ssh "second stage" "${MIRROR}" "${HOSTNAME}" $SSH_OPTIONS "${PUSHARGS} ${PUSHARGS2}"\n        fi\n    else\n        # Can\'t decide? Then you get nothing.\n        return\n    fi\n}\n\nsignal_ssh() {\n    local t=$1\n    local mirror_log="${LOGDIR}/${2}.log"\n    local hostname=$3\n    shift 3\n\n    msg "Sending ${t} trigger" >> $mirror_log\n    output=$(ssh -n $hostname "$@" 2>&1 | tee -a $mirror_log)\n    if [[ $? -eq 255 ]]; then\n        error_mailf "${t} trigger failed: $hostname" -b "$output"\n    else\n        log "${t} trigger succeeded: $hostname"\n    fi\n}\n\nwait_for_pushlocks() {\n  local tries=0\n  local found\n  local total\n  local timeout=${1}; shift\n  # We do not wait forever\n  while [[ ${tries} -lt ${timeout} ]]; do\n      total=0\n      found=0\n      for file in ${PUSHLOCKS}; do\n          total=$(( total + 1 ))\n          if [[ -f ${file} ]]; then\n              found=$(( found + 1 ))\n          fi\n      done\n      if [[ ${total} -eq ${found} ]] || [[ -f ${LOCKDIR}/all_stage1 ]]; then\n          break\n      fi\n      tries=$(( tries + 5 ))\n      sleep 5\n  done\n  # Regardless of the state of our siblings, hitting one timeout cancels all waits\n  touch "${LOCKDIR}/all_stage1"\n  if [[ ${tries} -ge ${timeout} ]]; then\n    return 1\n  else\n    return 0\n  fi\n}\n\n# callback, used by ftpsync\ncallback () {\n    # Defaults we always want, no matter what\n    SSH_OPTIONS="-o BatchMode=yes -o ServerAliveInterval=45 -o ConnectTimeout=45 -o PasswordAuthentication=no"\n    ssh -n $SSH_OPTIONS -i "$3" -o"user $1" "$2" callback:${HOSTNAME}\n}\n\n# open log file\nopen_log() {\n    local log=$1\n    shift\n    exec 4>&1 1>>$log\n}\n\n# assemble log message (basically echo it together with a timestamp)\n#\n# Set $PROGRAM to a string to have it added to the output.\nmsg() {\n    if [[ -z "${PROGRAM}" ]]; then\n        echo "$(date +"%b %d %H:%M:%S") $(hostname -s) [$$] $@"\n    else\n        echo "$(date +"%b %d %H:%M:%S") $(hostname -s) ${PROGRAM}[$$]: $@"\n    fi\n}\n\n# log something\nlog() {\n    msg "$@"\n}\n\n# log the message using log() but then also send a mail\n# to the address configured in MAILTO (if non-empty)\nerror () {\n    log "$@"\n    LOG_ERROR=1\n    mailf -s "[$PROGRAM@$(hostname -s)] ERROR: $*" -b "$*" ${MAILTO}\n}\n\n# log the message using log() but then also send a mail\n# to the address configured in MAILTO (if non-empty)\nerror_mailf () {\n    local m="$1"\n    shift\n    log "$m"\n    LOG_ERROR=1\n    mailf -s "[$PROGRAM@$(hostname -s)] ERROR: $m" "$@" ${MAILTO}\n}\n\n# run a hook\n# needs array variable HOOK setup with HOOKNR being a number an HOOKSCR\n# the script to run.\nhook () {\n    ARGS=\'HOOK[@]\'\n    local "${!ARGS}"\n    if [[ -n ${HOOKSCR} ]]; then\n        log "Running hook $HOOKNR: ${HOOKSCR}"\n        set +e\n        ${HOOKSCR}\n        result=$?\n        set -e\n        if [[ ${result} -ne 0 ]] ; then\n            error "Back from hook $HOOKNR, got returncode ${result}"\n        else\n            log "Back from hook $HOOKNR, got returncode ${result}"\n        fi\n        return $result\n    else\n        return 0\n    fi\n}\n\n# Return the list of 2-stage mirrors.\nget2stage() {\n    egrep -s \'^(staged|mhop)\' "${MIRRORS}" | {\n        while read MTYPE MLNAME MHOSTNAME MUSER MPROTO MKEYFILE; do\n            PUSHLOCKS="${LOCKDIR}/${MLNAME}.stage1 ${PUSHLOCKS}"\n        done\n        echo "$PUSHLOCKS"\n    }\n}\n\n# Rotate logfiles\nsavelog() {\n    torotate="$1"\n    count=${2:-${LOGROTATE}}\n    while [[ ${count} -gt 0 ]]; do\n        prev=$(( count - 1 ))\n        if [[ -e ${torotate}.${prev} ]]; then\n            mv "${torotate}.${prev}" "${torotate}.${count}"\n        fi\n        count=$prev\n    done\n    if [[ -e ${torotate} ]]; then\n        mv "${torotate}" "${torotate}.0"\n    fi\n}\n\n# Return rsync version\nrsync_protocol() {\n    RSYNC_VERSION="$(${RSYNC} --version)"\n    RSYNC_REGEX="(protocol[ ]+version[ ]+([0-9]+))"    \n    if [[ ${RSYNC_VERSION} =~ ${RSYNC_REGEX} ]]; then\n        echo ${BASH_REMATCH[2]}\n    fi\n    unset RSYNC_VERSION RSYNC_REGEX\n}\n\nextract_trace_field() {\n    local field="$1"\n    local file="$2"\n    local value=$(awk -F\': \' "\\$1==\\"$field\\" {print \\$2; exit}" "$file" 2>/dev/null)\n    [[ $value ]] || return 1\n    echo $value\n}\n\nextract_trace_field_string() {\n    local field="$1"\n    local string="$2"\n    local value=$(awk -F\': \' "\\$1==\\"$field\\" {print \\$2; exit}" <<< "$string" 2>/dev/null)\n    [[ $value ]] || return 1\n    echo $value\n}\n\nextract_trace_serial() {\n    extract_trace_field \'Archive serial\' "$1"\n    return $?\n}\n\nextract_trace_serial_string() {\n    extract_trace_field_string \'Archive serial\' "$1"\n    return $?\n}\n\n# Search config files in various locations\nsearch_config() {\n  local file\n  for i in ${CONFDIRS[@]}; do\n    file="$i/$1"\n    if [ -f "$file" ]; then\n      echo "$file"\n      return\n    fi\n  done\n}\n\n# Read config file\nread_config() {\n  local name=$(echo "$1" | sed -e \'s/[^A-Za-z0-9._-]/_/g\')\n  local config=$(search_config "$name")\n  if [ "$config" ]; then\n    . "$config"\n    CURRENT_CONFIG="$config"\n    return 0\n  else\n    echo "Can\'t read config file ${name}!" >&2\n    exit 78 # EX_CONFIG\n  fi\n}\n\n# Create lock dir\ncreate_lockdir() {\n  mkdir -p "$LOCKDIR"\n}\n\n# Create log dir\ncreate_logdir() {\n  mkdir -p "$LOGDIR"\n}\n\njoin_by() {\n    local IFS="$1"\n    shift\n    echo $*\n}\n\n# Sends mail\n# mailf [-a attachment] [-b body] [-s subject] to-addr ...\nmailf() {\n    local boundary="==--$RANDOM--$RANDOM--$RANDOM--=="\n    local attachment=()\n    local body=()\n    local subject=\n\n    OPTIND=1\n    while getopts ":a:b:s:" arg; do\n        case $arg in\n            a)\n
   attachment+=("$OPTARG")\n                ;;\n            b)\n                body+=("$OPTARG")\n                ;;\n            s)\n                subject="$OPTARG"\n                ;;\n        esac\n    done\n    shift $((OPTIND-1))\n\n    (\n        cat <<EOF\nSubject: ${subject}\nTo: $(join_by \', \' "$@")\nAuto-Submitted: auto-generated\nMIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=${boundary}\n\nEOF\n        if [[ ${body[@]:-} ]]; then\n            for a in "${body[@]}"; do\n                cat <<EOF\n--${boundary}\nContent-type: text/plain\n\nEOF\n                echo "$a"\n            done\n        fi\n        if [[ ${attachment[@]:-} ]]; then\n            for a in "${attachment[@]}"; do\n                cat <<EOF\n--${boundary}\nContent-type: text/plain; name="$(basename $a)"\n\nEOF\n                cat "$a" || echo "Failed to attach $a"\n            done\n        fi\n        cat <<EOF\n--${boundary}--\nEOF\n    ) | /usr/sbin/sendmail -i -- "$@" || :\n}\n# -*- mode:sh -*-\n# vim:syn=sh\n\nBINDIR=$(dirname $(readlink -f "$0"))\nBASEDIR=${BASEDIR:-"$(readlink -f $(dirname "$0")/..)"}\nCONFDIRS=("${BASEDIR}/etc" ~/.config/ftpsync /etc/ftpsync)\nLOCKDIR="${BASEDIR}/locks"\nLOGDIR="${BASEDIR}/log"\n\nfunction send_mail_new_version() {\n    # Check if there is a newer version of ftpsync. If so inform the admin, but not\n    # more than once every third day.\n    if [[ -r ${TO}/project/ftpsync/LATEST.VERSION ]]; then\n        LATEST=$(< "${TO}/project/ftpsync/LATEST.VERSION")\n        if [[ ${VERSION} =~ ^[0-9]+$ ]] && [[ ${LATEST} =~ ^[0-9]+$ ]] &&\n            [[ ${LATEST} -gt ${VERSION} ]]; then\n            if [[ -n ${MAILTO} ]]; then\n                interval=$((7 * 24 * 3600))\n                difference=$interval\n                if [[ -f ${LOGDIR}/ftpsync.newversion ]]; then\n                    stamptime=$(< "${LOGDIR}/ftpsync.newversion")\n
 unixtime=$(date +%s)\n                    difference=$(( $unixtime - $stamptime ))\n                fi\n
 if [[ ${difference} -ge $interval ]]; then\n                    # Only warn every seventh day\n                    mailf -s "[$(hostname -s)] Update for ftpsync available" -b "Hello admin,\n\ni found that there is a new version of me available.\nMe lonely ftpsync is currently version: ${VERSION}\nNew release of myself is available as:  ${LATEST}\n\nMe, myself and I - and the Debian mirroradmins - would be very grateful\nif you could update me. You can find the latest version on your mirror,\ncheck $(hostname -s):${TO}/project/ftpsync/ftpsync-${LATEST}.tar.gz\n\nYou can ensure the validity of that file by using sha512sum or md5sum\nagainst the available checksum files secured with a signature from the\nDebian FTPMaster signing key.\n\n" ${MAILTO}\n\n                    date +%s > "${LOGDIR}/ftpsync.newversion"\n                fi\n            fi\n        else\n            # Remove a possible stampfile\n            rm -f "${LOGDIR}/ftpsync.newversion"\n        fi\n    fi\n}\n\n########################################################################\n########################################################################\n## functions
         ##\n########################################################################\n########################################################################\ncheck_commandline() {\n    while [[ $# -gt 0 ]]; do\n        case "$1" in\n            sync:stage1)\n                SYNCSTAGE1="true"\n                SYNCALL="false"\n                ;;\n
  sync:stage2)\n                SYNCSTAGE2="true"\n                SYNCALL="false"\n                ;;\n            sync:callback)\n                SYNCCALLBACK="true"\n                ;;\n            sync:archive:*)\n                ARCHIVE=${1##sync:archive:}\n                ;;\n            sync:all)\n                SYNCALL="true"\n                ;;\n            sync:mhop)\n                SYNCMHOP="true"\n                ;;\n            *)\n                echo "Unknown option ${1} ignored"\n                ;;\n        esac\n        shift  # Check next set of parameters.\n    done\n}\n\n# All the stuff we want to do when we exit, no matter where\ncleanup() {\n    rc=$?\n\n    trap - ERR TERM HUP INT QUIT EXIT\n    # all done. Mail the log, exit.\n\n    if [[ $rc -gt 0 ]]; then\n        log "Mirrorsync done with errors"\n    else\n        log "Mirrorsync done"\n    fi\n\n    if [[ -n ${MAILTO} ]]; then\n        local args=()\n        local send=\n        local subject="SUCCESS"\n\n        # In case rsync had something on stderr\n        if [[ -s $LOG_RSYNC_ERROR ]]; then\n            args+=(-a $LOG_RSYNC_ERROR -a $LOG)\n            subject="ERROR: rsync errors"\n            send=1\n        # In case of direct errors\n        elif [[ $rc -gt 0 ]]; then\n            args+=(-a $LOG)\n            subject="ERROR"\n            send=1\n        # In case admin want all logs\n        elif [[ ${ERRORSONLY} = false ]]; then\n            args+=(-a $LOG)\n            if [[ ${LOG_ERROR:-} ]]; then\n                subject="ERROR"\n
fi\n            send=1\n        fi\n        if [[ $send ]]; then\n            # Someone wants full logs including rsync\n            if [[ ${FULLLOGS} = true ]]; then\n                args+=(-a $LOG_RSYNC)\n            fi\n            mailf "${args[@]}" -s "[${PROGRAM}@$(hostname -s)] ${subject}" ${MAILTO}\n        fi\n    fi\n\n    savelog "${LOG_RSYNC}"\n    savelog "${LOG_RSYNC_ERROR}"\n    savelog "$LOG" > /dev/null\n\n    rm -f "${LOCK}"\n\n    exit $rc\n}\n\nrun_rsync() {\n  local t=$1\n  shift\n\n  log "Running $t:" "${_RSYNC[@]}" "$@"\n\n  "${_RSYNC[@]}" "$@" \\\n    >>"${LOG_RSYNC_ERROR}" 2>&1 || return $?\n}\n\n# Check rsyncs return value\ncheck_rsync() {\n    ret=$1\n    msg=$2\n\n    # Lets get a statistical value\n    if [[ -f ${LOG_RSYNC} ]]; then\n        SPEED=$(tail -n 2 ${LOG_RSYNC} | sed -Ene \'s#.* ([0-9.,]+) bytes/sec#\\1#p\')\n        if [[ ${SPEED} ]]; then\n            SPEED=${SPEED%%.*}\n            SPEED=${SPEED//,}\n            SPEED=$(( SPEED / 1024 ))\n            log "Latest recorded rsync transfer speed: ${SPEED} KB/s"\n        fi\n    fi\n\n    # 24 - vanished source files. Ignored, that should be the target of $UPDATEREQUIRED\n    # and us re-running. If it\'s not, uplink is broken anyways.\n    case "${ret}" in\n        0) return 0;;\n        24) return 0;;\n        23) return 2;;\n        30) return 2;;\n        *)\n            error "ERROR: ${msg}"\n            return 1\n
  ;;\n    esac\n}\n\nfunction tracefile_content() {\n    set +e\n\n    LC_ALL=POSIX LANG=POSIX date -u\n    rfc822date=$(LC_ALL=POSIX LANG=POSIX date -u -R)\n    echo "Date: ${rfc822date}"\n    echo "Date-Started: ${DATE_STARTED}"\n\n    if [[ -e $TRACEFILE_MASTER ]]; then\n        echo "Archive serial: $(extract_trace_serial $TRACEFILE_MASTER || echo unknown )"\n    fi\n\n    echo "Used ftpsync version: ${VERSION}"\n    echo "Creator: ftpsync ${VERSION}"\n    echo "Running on host: ${TRACEHOST}"\n\n    if [[ ${INFO_MAINTAINER:-} ]]; then\n        echo "Maintainer: ${INFO_MAINTAINER}"\n    fi\n    if [[ ${INFO_SPONSOR:-} ]]; then\n        echo "Sponsor: ${INFO_SPONSOR}"\n    fi\n    if [[ ${INFO_COUNTRY:-} ]]; then\n        echo "Country: ${INFO_COUNTRY}"\n    fi\n    if [[ ${INFO_LOCATION:-} ]]; then\n        echo "Location: ${INFO_LOCATION}"\n    fi\n    if [[ ${INFO_THROUGHPUT:-} ]]; then\n        echo "Throughput: ${INFO_THROUGHPUT}"\n    fi\n    if [[ ${INFO_TRIGGER:-} ]]; then\n        echo "Trigger: ${INFO_TRIGGER}"\n    fi\n\n    if [[ -d ${TO}/dists ]]; then\n        ARCH=$(find ${TO}/dists \\( -name \'Packages.*\' -o -name \'Sources.*\' \\) 2>/dev/null |\n            sed -Ene \'s#.*/binary-([^/]+)/Packages.*#\\1#p; s#.*/(source)/Sources.*#\\1#p\' |\n            sort -u | tr \'\\n\' \' \')\n        if [[ $ARCH ]]; then\n            echo "Architectures: ${ARCH}"\n        fi\n    fi\n    if [[ ${ARCH_INCLUDE} ]]; then\n        echo "Architectures-Configuration: INCLUDE $(tr \' \' \'\\n\' <<< ${ARCH_INCLUDE} | sort -u | tr \'\\n\' \' \')"\n    elif [[ ${ARCH_EXCLUDE} ]]; then\n        echo "Architectures-Configuration: EXCLUDE $(tr \' \' \'\\n\' <<< ${ARCH_EXCLUDE} | sort -u | tr \'\\n\' \' \')"\n    else\n        echo "Architectures-Configuration: ALL"\n    fi\n    echo "Upstream-mirror: ${RSYNC_HOST:-unknown}"\n    echo "Rsync-Transport: ${RSYNC_TRANSPORT}"\n    total=0\n    if [[ -e ${LOG_RSYNC} ]]; then\n        for bytes in $(sed -Ene \'s/(^|.* )sent ([0-9]+) bytes  received ([0-9]+) bytes.*/\\3/p\' "${LOG_RSYNC}"); do\n            total=$(( total + bytes ))\n        done\n        if [[ $total -gt 0 ]]; then\n            echo "Total bytes received in rsync: ${total}"\n        fi\n    fi\n    total_time=$(( STATS_TOTAL_RSYNC_TIME1 + STATS_TOTAL_RSYNC_TIME2 ))\n    echo "Total time spent in stage1 rsync: ${STATS_TOTAL_RSYNC_TIME1}"\n    echo "Total time spent in stage2 rsync: ${STATS_TOTAL_RSYNC_TIME2}"\n    echo "Total time spent in rsync: ${total_time}"\n    if [[ 0 != ${total_time} ]]; then\n        rate=$(( total / total_time ))\n        echo "Average rate: ${rate} B/s"\n    fi\n\n    set -e\n}\n\n# Write a tracefile\ntracefile() {\n    local TRACEFILE=${1:-"${TO}/${TRACE}"}\n    local TRACEFILE_MASTER="${TO}/${TRACEDIR}/master"\n\n    tracefile_content > "${TRACEFILE}.new"\n    mv "${TRACEFILE}.new" "${TRACEFILE}"\n\n    {\n        if [[ -e ${TO}/${TRACEHIERARCHY}.mirror ]]; then\n            cat ${TO}/${TRACEHIERARCHY}.mirror\n        fi\n        echo "$(basename "${TRACEFILE}") ${MIRRORNAME} ${TRACEHOST} ${RSYNC_HOST:-unknown}"\n    } > "${TO}/${TRACEHIERARCHY}".new\n    mv "${TO}/${TRACEHIERARCHY}".new "${TO}/${TRACEHIERARCHY}"\n    cp "${TO}/${TRACEHIERARCHY}" "${TO}/${TRACEHIERARCHY}.mirror"\n\n    (cd "${TO}/${TRACEDIR}" && ls -1rt $(find * -type f \\! -name "_*")) > "${TO}/${TRACELIST}"\n}\n\narch_imexclude() {\n    local param="$1" arch="$2"\n    if [[ $arch = source ]]; then\n        _RSYNC+=(\n            "--filter=${param}_/dists/**/source/"\n            "--filter=${param}_/pool/**/*.tar.*"\n            "--filter=${param}_/pool/**/*.diff.*"\n            "--filter=${param}_/pool/**/*.dsc"\n        )\n    else\n        _RSYNC+=(\n            "--filter=${param}_/dists/**/binary-${arch}/"\n            "--filter=${param}_/dists/**/installer-${arch}/"\n            "--filter=${param}_/dists/**/Contents-${arch}.gz"\n            "--filter=${param}_/dists/**/Contents-udeb-${arch}.gz"\n            "--filter=${param}_/dists/**/Contents-${arch}.diff/"\n            "--filter=${param}_/indices/**/arch-${arch}.files"\n            "--filter=${param}_/indices/**/arch-${arch}.list.gz"\n            "--filter=${param}_/pool/**/*_${arch}.deb"\n            "--filter=${param}_/pool/**/*_${arch}.udeb"\n            "--filter=${param}_/pool/**/*_${arch}.changes"\n        )\n    fi\n}\n\narch_exclude() {\n    arch_imexclude exclude "$1"\n}\n\narch_include() {\n    arch_imexclude include "$1"\n}\n\n# Learn which archs to include/exclude based on ARCH_EXCLUDE and ARCH_INCLUDE\n# settings.\n# Sets EXCLUDE (which might also have --include statements\n# followed by a --exclude *_*.<things>.\nset_exclude_include_archs() {\n    if [[ -n "${ARCH_EXCLUDE}" ]] && [[ -n "${ARCH_INCLUDE}" ]]; then\n        echo >&2 "ARCH_EXCLUDE and ARCH_INCLUDE are mutually exclusive.  Set only one."\n        exit 1\n    fi\n\n    if [[ -n "${ARCH_EXCLUDE}" ]]; then\n        for ARCH in ${ARCH_EXCLUDE}; do\n            arch_exclude ${ARCH}\n        done\n        arch_include \'*\'\n        arch_include source\n    elif [[ -n "${ARCH_INCLUDE}" ]]; then\n        local include_arch_all=false\n        for ARCH in ${ARCH_INCLUDE}; do\n            arch_include ${ARCH}\n            if [[ ${ARCH} != source ]]; then\n                include_arch_all=true\n            fi\n        done\n        if [[ true = ${include_arch_all} ]]; then\n            arch_include all\n        fi\n        arch_exclude \'*\'\n        arch_exclude source\n    fi\n}\n\n########################################################################\n########################################################################\n\n\n# As what are we called?\nNAME="$(basename $0)"\n\n# What should we do?\nARCHIVE=\n# Do we sync stage1?\nSYNCSTAGE1=false\n# Do we sync stage2?\nSYNCSTAGE2=false\n# Do we sync all?\nSYNCALL=true\n# Do we have a mhop sync?\nSYNCMHOP=false\n# Do we callback? (May get changed later)\nSYNCCALLBACK=false\n\nwhile getopts T: option; do\n    case $option in\n        T) INFO_TRIGGER=$OPTARG;;\n        ?) exit 64;;\n    esac\ndone\nshift $(($OPTIND - 1))\n\n# Now, check if we got told about stuff via ssh\nif [[ -n ${SSH_ORIGINAL_COMMAND:-} ]]; then\n    INFO_TRIGGER=${INFO_TRIGGER:-ssh}\n    check_commandline ${SSH_ORIGINAL_COMMAND}\nfi\n\n# Now, we can locally override all the above variables by just putting\n# them into the .ssh/authorized_keys file forced command.\nif [[ $# -gt 0 ]]; then\n    check_commandline "$@"\nfi\n\n# If we have been told to do stuff for a different archive than default,\n# set the name accordingly.\nif [[ -n ${ARCHIVE} ]]; then\n    NAME="${NAME}-${ARCHIVE}"\nfi\n\n# Now source the config for the archive we run on.\n# (Yes, people can also overwrite the options above in the config file\n# if they want to)\nread_config "${NAME}.conf"\n\ncreate_logdir\n\n########################################################################\n# Config defaults
                                    #\n########################################################################\nMIRRORNAME=${MIRRORNAME:-$(hostname -f)}\nTO=${TO:-"/srv/mirrors/debian/"}\nMAILTO=${MAILTO:-${LOGNAME:?Environment variable LOGNAME unset, please check your system or specify MAILTO}}\nHUB=${HUB:-"false"}\n\n# Connection options\nif [[ -z ${RSYNC_SOURCE:-} ]]; then\n    RSYNC_HOST=${RSYNC_HOST:?Missing a host to mirror from, please set RSYNC_HOST variable in ${CURRENT_CONFIG}}\n    RSYNC_PATH=${RSYNC_PATH:-"debian"}\n    RSYNC_USER=${RSYNC_USER:-""}\nfi\nRSYNC_PASSWORD=${RSYNC_PASSWORD:-""}\nif [[ ${RSYNC_SSL:-} = true ]]; then\n    RSYNC_TRANSPORT=${RSYNC_TRANSPORT:-"ssl"}\nelse\n    RSYNC_TRANSPORT=${RSYNC_TRANSPORT:-"undefined"}\nfi\nRSYNC_SSL_PORT=${RSYNC_SSL_PORT:-"1873"}\nRSYNC_SSL_CAPATH=${RSYNC_SSL_CAPATH:-"/etc/ssl/certs"}\nRSYNC_SSL_METHOD=${RSYNC_SSL_METHOD:-"stunnel"}\nRSYNC_PROXY=${RSYNC_PROXY:-""}\n\n# Include and exclude options\nARCH_INCLUDE=${ARCH_INCLUDE:-""}\nARCH_EXCLUDE=${ARCH_EXCLUDE:-""}\nEXCLUDE=${EXCLUDE:-""}\n\n# Log options\nLOG=${LOG:-"${LOGDIR}/${NAME}.log"}\nERRORSONLY=${ERRORSONLY:-"true"}\nFULLLOGS=${FULLLOGS:-"false"}\nLOGROTATE=${LOGROTATE:-14}\nLOG_RSYNC="${LOGDIR}/rsync-${NAME}.log"\nLOG_RSYNC_ERROR="${LOGDIR}/rsync-${NAME}.error"\n\n# Other options\nLOCKTIMEOUT=${LOCKTIMEOUT:-3600}\nUIPSLEEP=${UIPSLEEP:-1200}\nUIPRETRIES=${UIPRETRIES:-3}\nTRACEHOST=${TRACEHOST:-$(hostname -f)}\nRSYNC=${RSYNC:-rsync}\nRSYNC_PROTOCOL=$(rsync_protocol)\nRSYNC_EXTRA=${RSYNC_EXTRA:-""}\nRSYNC_BW=${RSYNC_BW:-0}\nif [[ $RSYNC_PROTOCOL -ge 31 ]]; then\n    RSYNC_OPTIONS=${RSYNC_OPTIONS:-"-prltvHSB8192 --safe-links --chmod=D755,F644 --timeout 120 --stats --no-human-readable --no-inc-recursive"}\nelse\n    RSYNC_OPTIONS=${RSYNC_OPTIONS:-"-prltvHSB8192 --safe-links --timeout 120 --stats --no-human-readable --no-inc-recursive"}\nfi\nRSYNC_OPTIONS1=${RSYNC_OPTIONS1:-"--include=*.diff/ --exclude=*.diff/Index --exclude=Packages* --exclude=Sources* --exclude=Release* --exclude=InRelease --include=i18n/by-hash --exclude=i18n/* --exclude=ls-lR*"}\nRSYNC_OPTIONS2=${RSYNC_OPTIONS2:-"--max-delete=40000 --delay-updates --delete --delete-delay --delete-excluded"}\nCALLBACKUSER=${CALLBACKUSER:-"archvsync"}\nCALLBACKHOST=${CALLBACKHOST:-"none"}\nCALLBACKKEY=${CALLBACKKEY:-"none"}\n\n# Hooks\nHOOK1=${HOOK1:-""}\nHOOK2=${HOOK2:-""}\nHOOK3=${HOOK3:-""}\nHOOK4=${HOOK4:-""}\nHOOK5=${HOOK5:-""}\n########################################################################\n########################################################################\n\n# used by log() and error()\nPROGRAM=${PROGRAM:-"${NAME}"}\n\n# Our trace and lock files\nLOCK_NAME="Archive-Update-in-Progress-${MIRRORNAME}"\nLOCK="${TO}/${LOCK_NAME}"\nUPDATEREQUIRED_NAME="Archive-Update-Required-${MIRRORNAME}"\nUPDATEREQUIRED="${TO}/${UPDATEREQUIRED_NAME}"\nTRACEDIR=project/trace\nTRACE="${TRACEDIR}/${MIRRORNAME}"\nTRACE_STAGE1="${TRACEDIR}/${MIRRORNAME}-stage1"\nTRACEHIERARCHY="${TRACEDIR}/_hierarchy"\nTRACELIST="${TRACEDIR}/_traces"\n\n_TRACE_FILES=(\n  "${LOCK_NAME}"\n  "${UPDATEREQUIRED_NAME}"\n  "${TRACE}"\n  "${TRACE_STAGE1}"\n  "${TRACEHIERARCHY}"\n  "${TRACELIST}"\n)\n\n_RSYNC=(\n  $RSYNC\n  --quiet\n  --log-file "${LOG_RSYNC}"\n)\n\n# Rsync filter rules. Used to protect various files we always want to keep, even if we otherwise delete\n# excluded files\nfor i in ${_TRACE_FILES[@]}; do\n  _RSYNC+=("--filter=exclude_/${i}" "--filter=protect_/${i}")\ndone\n_RSYNC+=(\n  "--filter=include_/project/"\n  "--filter=protect_/project/"\n  "--filter=include_/project/trace/"\n  "--filter=protect_/project/trace/"\n  "--filter=include_/project/trace/*"\n)\n\n# Default rsync options for *every* rsync call\n# Now add the bwlimit option. As default is 0 we always add it, rsync interprets\n# 0 as unlimited, so this is safe.\n_RSYNC+=(${RSYNC_EXTRA} --bwlimit=${RSYNC_BW} ${RSYNC_OPTIONS} ${EXCLUDE})\n\n# collect some stats\nSTATS_TOTAL_RSYNC_TIME1=0\nSTATS_TOTAL_RSYNC_TIME2=0\n\n# The temp directory used by rsync --delay-updates is not\n# world-readable remotely. Always exclude it to avoid errors.\n_RSYNC+=("--exclude=.~tmp~/")\n\nif [[ ${RSYNC_TRANSPORT} = undefined ]]; then\n    :\nelif [[ ${RSYNC_TRANSPORT} = ssh ]]; then\n    _RSYNC+=(-e "ssh")\nelif [[ ${RSYNC_TRANSPORT} = ssl ]]; then\n    export RSYNC_SSL_PORT\n    export RSYNC_SSL_CAPATH\n    export RSYNC_SSL_METHOD\n    _RSYNC+=(-e "${BINDIR:+${BINDIR}/}rsync-ssl-tunnel")\nelse\n    echo "Unknown rsync transport configured (${RSYNC_TRANSPORT})" >&2\n    exit 1\nfi\n\n# Exclude architectures defined in $ARCH_EXCLUDE\nset_exclude_include_archs\n\n########################################################################\n# Really nothing to see below here. Only code follows.                 #\n########################################################################\n########################################################################\nDATE_STARTED=$(LC_ALL=POSIX LANG=POSIX date -u -R)\n\n# Some sane defaults\ncd "${BASEDIR:-}"\numask 022\n\n# If we are here for the first time, create the\n# destination and the trace directory\nmkdir -p "${TO}/${TRACEDIR}"\n\n# Used to make sure we will have the archive fully and completly synced before\n# we stop, even if we get multiple pushes while this script is running.\n# Otherwise we can end up with a half-synced archive:\n# - get a push\n# - sync, while locked\n# - get another push. Of course no extra sync run then happens, we are locked.\n# - done. Archive not correctly synced, we don\'t have all the changes from the second push.\ntouch "${UPDATEREQUIRED}"\n\n# Check to see if another sync is in progress\nif ! ( set -o noclobber; echo "$$" > "${LOCK}") 2> /dev/null; then\n    if [[ ${BASH_VERSINFO[0]} -gt 3 ]] || [[ -L /proc/self ]]; then\n        # We have a recent enough bash version, lets do it the easy way,\n        # the lock will contain the right pid, thanks to $BASHPID\n        if ! $(kill -0 $(< ${LOCK}) 2>/dev/null); then\n            # Process does either not exist or is not owned by us.\n            echo "$$" > "${LOCK}"\n        else\n            echo "Unable to start rsync, lock file still exists, PID $(< ${LOCK})"\n            exit 1\n        fi\n    else\n        # Old bash, means we dont have the right pid in our lockfile\n        # So take a different way - guess if it is still there by comparing its age.\n        # Not optimal, but hey.\n        stamptime=$(date --reference="${LOCK}" +%s)\n        unixtime=$(date +%s)\n        difference=$(( $unixtime - $stamptime ))\n        if [[ ${difference} -ge ${LOCKTIMEOUT} ]]; then\n            # Took longer than LOCKTIMEOUT minutes? Assume it broke and take the lock\n            echo "$$" > "${LOCK}"\n        else\n            echo "Unable to start rsync, lock file younger than one hour"\n
 exit 1\n        fi\n    fi\nfi\n\n# We want to cleanup always\ntrap cleanup EXIT TERM HUP INT QUIT\n\n# Open log and close stdin\nopen_log $LOG\nexec 2>&1 <&-\nlog "Mirrorsync start"\n\n# Look who pushed us and note that in the log.\nSSH_CONNECTION=${SSH_CONNECTION:-""}\nPUSHFROM="${SSH_CONNECTION%%\\ *}"\nif [[ -n ${PUSHFROM} ]]; then\n    log "We got pushed from ${PUSHFROM}"\nfi\n\nif [[ true = ${SYNCCALLBACK} ]]; then\n    if [[ none = ${CALLBACKHOST} ]] || [[ none = ${CALLBACKKEY} ]]; then\n        SYNCCALLBACK="false"\n        error "We are asked to call back, but we do not know where to and do not have a key, ignoring callback"\n    fi\nfi\n\nHOOK=(\n    HOOKNR=1\n    HOOKSCR=${HOOK1}\n)\nhook $HOOK\n\n# Now, we might want to sync from anonymous too.\n# This is that deep in this script so hook1 could, if wanted, change things!\nif [[ -z ${RSYNC_SOURCE:-} ]]; then\n    if [[ -z ${RSYNC_USER:-} ]]; then\n        RSYNC_SOURCE="${RSYNC_HOST}::${RSYNC_PATH}"\n    else\n        RSYNC_SOURCE="${RSYNC_USER}@${RSYNC_HOST}::${RSYNC_PATH}"\n    fi\nfi\n\n_RSYNC+=("${RSYNC_SOURCE}" "$TO")\n\n# Now do the actual mirroring, and run as long as we have an updaterequired file.\nexport RSYNC_PASSWORD\nexport RSYNC_PROXY\n\nUPDATE_RETRIES=0\n\nwhile [[ -e ${UPDATEREQUIRED} ]]; do\n    log "Running mirrorsync, update is required, ${UPDATEREQUIRED} exists"\n\n    # if we want stage1 *or* all\n    if [[ true = ${SYNCSTAGE1} ]] || [[ true = ${SYNCALL} ]]; then\n        while [[ -e ${UPDATEREQUIRED} ]]; do\n            rm -f "${UPDATEREQUIRED}"\n            # Step one, sync everything except Packages/Releases\n            rsync_started=$(date +%s)\n            result=0\n            run_rsync "stage1" ${RSYNC_OPTIONS1} || result=$?\n            rsync_ended=$(date +%s)\n            STATS_TOTAL_RSYNC_TIME1=$(( STATS_TOTAL_RSYNC_TIME1 + rsync_ended - rsync_started ))\n\n            log "Back from rsync with returncode ${result}"\n        done\n    else\n        time1=$(extract_trace_field \'Total time spent in stage1 rsync\' "${TO}/${TRACE_STAGE1}" || :)\n        if [[ $time1 ]]; then\n            STATS_TOTAL_RSYNC_TIME1="$time1"\n        fi\n        # Fake a good resultcode\n        result=0\n    fi # Sync stage 1?\n    rm -f "${UPDATEREQUIRED}"\n\n    set +e\n    check_rsync $result "Sync step 1 went wrong, got errorcode ${result}. Logfile: ${LOG}"\n    GO=$?\n    set -e\n    if [[ ${GO} -eq 2 ]] && [[ -e ${UPDATEREQUIRED} ]]; then\n        log "We got error ${result} from rsync, but a second push went in hence ignoring this error for now"\n    elif [[ ${GO} -ne 0 ]]; then\n        exit 3\n    fi\n\n    HOOK=(\n        HOOKNR=2\n        HOOKSCR=${HOOK2}\n    )\n    hook $HOOK\n\n    # if we want stage2 *or* all\n    if [[ true = ${SYNCSTAGE2} ]] || [[ true = ${SYNCALL} ]]; then\n        upstream_uip=false\n        for aupfile in "${TO}/Archive-Update-in-Progress-"*; do\n            case "$aupfile" in\n                "${TO}/Archive-Update-in-Progress-*")\n
           error "Lock file is missing, this should not happen"\n                    ;;\n                "${LOCK}")\n                    :\n                    ;;\n                *)\n                    if [[ -f $aupfile ]]; then\n                        # Remove the file, it will be synced again if\n                        # upstream is still not done\n                        rm -f "$aupfile"\n                    else\n                        log "AUIP file \'$aupfile\' is not really a file, weird"\n                    fi\n                    upstream_uip=true\n                    ;;\n            esac\n        done\n\n        if [[ true = ${upstream_uip} ]]; then\n            log "Upstream archive update in progress, skipping stage2"\n            if [[ ${UPDATE_RETRIES} -lt ${UIPRETRIES} ]]; then\n                log "Retrying update in ${UIPSLEEP}"\n                touch "${UPDATEREQUIRED}"\n                UPDATE_RETRIES=$(($UPDATE_RETRIES+1))\n                sleep "${UIPSLEEP}"\n                result=0\n            else\n                error "Update has been retried ${UPDATE_RETRIES} times, aborting"\n                log "Perhaps upstream is still updating or there\'s a stale AUIP file"\n                result=1\n            fi\n        else\n            # We are lucky, it worked. Now do step 2 and sync again, this time including\n            # the packages/releases files\n            rsync_started=$(date +%s)\n            result=0\n            run_rsync "stage2" ${RSYNC_OPTIONS2} || result=$?\n            rsync_ended=$(date +%s)\n            STATS_TOTAL_RSYNC_TIME2=$(( STATS_TOTAL_RSYNC_TIME2 + rsync_ended - rsync_started ))\n\n            log "Back from rsync with returncode ${result}"\n        fi\n    else\n        # Fake a good resultcode\n        result=0\n    fi # Sync stage 2?\n\n    set +e\n    check_rsync $result "Sync step 2 went wrong, got errorcode ${result}. Logfile: ${LOG}"\n    GO=$?\n    set -e\n    if [[ ${GO} -eq 2 ]] && [[ -e ${UPDATEREQUIRED} ]]; then\n        log "We got error ${result} from rsync, but a second push went in hence ignoring this error for now"\n    elif [[ ${GO} -ne 0 ]]; then\n        exit 4\n    fi\n\n    HOOK=(\n        HOOKNR=3\n        HOOKSCR=${HOOK3}\n    )\n    hook $HOOK\ndone\n\n# We only update our tracefile when we had a stage2 or an all sync.\n# Otherwise we would update it after stage1 already, which is wrong.\nif [[ true = ${SYNCSTAGE2} ]] || [[ true = ${SYNCALL} ]]; then\n    tracefile\n    if [[ true = ${SYNCALL} ]]; then\n        rm -f "${TO}/${TRACE_STAGE1}"\n    fi\nelif [[ true = ${SYNCSTAGE1} ]]; then\n    tracefile "${TO}/${TRACE_STAGE1}"\nfi\n\n\nHOOK=(\n    HOOKNR=4\n    HOOKSCR=${HOOK4}\n)\nhook $HOOK\n\nif [[ true = ${SYNCCALLBACK} ]]; then\n    set +e\n    callback ${CALLBACKUSER} ${CALLBACKHOST} "${CALLBACKKEY}"\n    set -e\nfi\n\n# Remove the Archive-Update-in-Progress file before we push our downstreams.\nrm -f "${LOCK}"\n\ndeclare -f -F send_mail_new_version > /dev/null && send_mail_new_version || :\n\nif [[ ${HUB} = true ]]; then\n    # Trigger slave mirrors if we had a push for stage2 or all, or if its mhop\n    if [[ true = ${SYNCSTAGE2} ]] || [[ true = ${SYNCALL} ]] || [[ true = ${SYNCMHOP} ]]; then\n        RUNMIRRORARGS=""\n        if [[ -n ${ARCHIVE} ]]; then\n            # We tell runmirrors about the archive we are running on.\n            RUNMIRRORARGS="-a ${ARCHIVE}"\n        fi\n        # We also tell runmirrors that we are running it from within ftpsync, so it can change\n        # the way it works with mhop based on that.\n        RUNMIRRORARGS="${RUNMIRRORARGS} -f"\n\n        if [[ true = ${SYNCSTAGE1} ]]; then\n            # This is true when we have a mhop sync. A normal multi-stage push sending stage1 will\n            # not get to this point.\n            # So if that happens, tell runmirrors we are doing mhop\n            RUNMIRRORARGS="${RUNMIRRORARGS} -k mhop"\n        elif [[ true = ${SYNCSTAGE2} ]]; then\n            RUNMIRRORARGS="${RUNMIRRORARGS} -k stage2"\n        elif [[ true = ${SYNCALL} ]]; then\n            RUNMIRRORARGS="${RUNMIRRORARGS} -k all"\n        fi\n        log "Trigger slave mirrors using ${RUNMIRRORARGS}"\n        ${BINDIR:+${BINDIR}/}runmirrors ${RUNMIRRORARGS}\n        log "Trigger slave done"\n\n        HOOK=(\n            HOOKNR=5\n            HOOKSCR=${HOOK5}\n        )\n        hook $HOOK\n    fi\nfi\n"""


def ftpsync(package: mirror.structure.Package) -> None:
    """Sync package"""
    
    package.set_status("SYNC")

    os.setgid(mirror.conf.gid)
    os.setuid(mirror.conf.uid)

    logger = logging.getLogger(f"mirror.package.{package.name}")
    tmp = Path(TemporaryDirectory().name)
    tmp.mkdir()

    _setup(tmp, package)

    command = [
        f"{tmp}/bin/ftpsync",
    ]
    result = subprocess.run(command, shell=True, check=True)
    if result.returncode == 0:
        package.set_status("ACTIVE")
    else:
        package.set_status("ERROR")

    



def _setup(path: Path, package: mirror.structure.Package):
    """Setup package"""
    (path / "bin").mkdir()
    (path / "etc").mkdir()

    (path / "bin" / "ftpsync").write_text(SCRIPT)
    (path / "bin" / "ftpsync").chmod(0o755)
    
    (path / "etc" / "ftpsync.conf").write_text(_config(package))


def _config(package: mirror.structure.Package) -> str:
    """Create config file"""
    config = ""
    config += f"MIRRORNAME=\"{mirror.conf.name}\"\n"
    config += f"TO=\"{package.settings.dst}\"\n"
    config += f"MAILTO=\"{package.settings.email}\"\n"
    config += f"HUB={package.settings.hub}\n"
    config += f"RSYNC_HOST=\"{package.settings.src}\"\n"
    config += f"RSYNC_PATH=\"{package.settings.path}\"\n"

    if "user" in dir(package.settings) and "password" in dir(package.settings):
        config += f"RSYNC_USER=\"{package.settings.user}\"\n"
        config += f"RSYNC_PASSWORD=\"{package.settings.password}\"\n"

    config += f"INFO_MAINTAINER=\"{package.settings.maintainer}\"\n" if "maintainer" in dir(package.settings) else ""
    config += f"INFO_SPONSOR=\"{package.settings.sponsor}\"\n" if "sponsor" in dir(package.settings) else ""
    config += f"INFO_COUNTRY={package.settings.country}\n" if "country" in dir(package.settings) else ""
    config += f"INFO_LOCATION=\"{package.settings.location}\"\n" if "location" in dir(package.settings) else ""
    config += f"INFO_THROUGHPUT={package.settings.throughput}\n" if "throughput" in dir(package.settings) else ""
    config += f"ARCH_INCLUDE=\"{package.settings.arch_include}\"\n" if "arch_include" in dir(package.settings) else ""
    config += f"ARCH_EXCLUDE=\"{package.settings.arch_exclude}\"\n" if "arch_exclude" in dir(package.settings) else ""
    config += f"LOGDIR=\"{package.settings.logdir if 'logdir' in dir(package.settings) else mirror.conf.logdir}\"\n" 

    return config




