summaryrefslogtreecommitdiff
path: root/src/lib/config.sync.source.sh
blob: 35a7185d3866d775232f16f55596fbd38d7b8ed1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
resolve_sync_destinations() {
    # destinations_ref is a nameref output filled by resolve_config_array.
    # shellcheck disable=SC2034
    local -n destinations_ref="$1"; shift
    local sync_decl

    # A list of rsync destinations is inherently a list, and the only
    # unambiguous spelling is a Bash array: SYNC_DESTINATIONS=( '/path/with
    # spaces/' ) preserves embedded spaces, whereas a scalar string would be
    # word-split (e.g. "/path/with spaces/" -> two broken destinations passed
    # to rsync). So we reject scalar declarations outright instead of silently
    # word-splitting them. (TAR_OPTS deliberately keeps scalar word-splitting:
    # turning "-c -v" into separate options is the desired behaviour there.)
    #
    # declare -p fails only when SYNC_DESTINATIONS was never set; that is a
    # legitimate "unset" case handled by the || true below, so we skip the
    # array-vs-scalar check entirely when the variable does not exist.
    if sync_decl=$(declare -p SYNC_DESTINATIONS 2>/dev/null); then
        case "$sync_decl" in
            declare\ -a*) ;;
            *)
                config_error \
                    'SYNC_DESTINATIONS must be an array, e.g. SYNC_DESTINATIONS=( '\''dest1'\'' '\''dest2'\'' )'
                return 1
                ;;
        esac
    fi

    # Parse SYNC_DESTINATIONS (always an array at this point) via the shared
    # config-array helper. Unlike TAR_OPTS there is no default: an unset or
    # empty value simply yields an empty destinations array for callers to
    # validate. The helper returns non-zero when SYNC_DESTINATIONS was never
    # declared; we ignore that (|| true) since the already-empty array is
    # exactly the desired result, and the bare non-zero return would otherwise
    # trip set -e.
    resolve_config_array SYNC_DESTINATIONS destinations_ref || true
}

sync_dist() {
    local destination
    local -a rsync_args=(-av)
    local -a sync_destinations=()
    local -a succeeded=()
    local -a failed=()
    local -i status=0

    resolve_sync_destinations sync_destinations

    if [ "$SYNC_DELETE" = yes ]; then
        rsync_args+=(--delete)
    fi

    # Mirror to every destination with per-destination isolation: a timeout or
    # rsync error on one mirror must NOT abort the others (under the top-level
    # "set -euo pipefail" a bare failing rsync would otherwise kill the loop and
    # silently skip the remaining destinations). We wrap each rsync in
    # run_with_timeout (SYNC_TIMEOUT) like every other external call, and use the
    # project's localized "set +e" idiom (see refresh_splash / bash-best-
    # practices) to capture each destination's status instead of aborting. The
    # "if ! run_with_timeout ...; then" form is avoided here because its non-zero
    # branch would still leave $? ambiguous for the summary; the explicit status
    # capture keeps the per-destination result unambiguous.
    for destination in "${sync_destinations[@]}"; do
        log_info "Syncing $DIST_DIR/ to $destination"
        set +e
        run_with_timeout "rsync to $destination" "$SYNC_TIMEOUT" \
            rsync "${rsync_args[@]}" "$DIST_DIR/" "$destination"
        status=$?
        set -e
        if (( status == 0 )); then
            succeeded+=("$destination")
        else
            # run_with_timeout already prints a timeout/error line; add a
            # per-destination notice so the operator sees which mirror failed.
            failed+=("$destination")
            log_warning "Sync to $destination failed (status $status)"
        fi
    done

    # Log a clear pass/fail summary and return non-zero if ANY destination
    # failed, while having ATTEMPTED all of them.
    if (( ${#succeeded[@]} > 0 )); then
        log_info "Sync succeeded for: ${succeeded[*]}"
    fi
    if (( ${#failed[@]} > 0 )); then
        log_warning "Sync failed for: ${failed[*]}"
        return 1
    fi
    return 0
}