#!/usr/bin/env bash set -euo pipefail script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$script_dir/lib/common.sh" repo_root="$(backup_repo_root)" if [[ -f "$repo_root/.env" ]]; then env_override_keys=( WORKS_DRIVE_TARGET WORKS_DRIVE_SHARED_DRIVE_ID WORKS_DRIVE_PARENT_FILE_ID WORKS_DRIVE_USER_ID WORKS_DRIVE_GROUP_ID WORKS_DRIVE_SHARED_FOLDER_ID WORKS_DRIVE_ACCESS_TOKEN WORKS_DRIVE_ACCESS_TOKEN_FILE WORKS_DRIVE_ACCESS_TOKEN_CMD WORKS_DRIVE_AUTH_MODE WORKS_DRIVE_OAUTH_SCOPE WORKS_DRIVE_SPLIT_SIZE WORKS_DRIVE_MAX_SINGLE_FILE_BYTES WORKS_DRIVE_FORCE_SPLIT WORKS_DRIVE_OVERWRITE WORKS_DRIVE_DRY_RUN WORKS_DRIVE_UPLOAD_REPORTS WORKS_DRIVE_REPORT_FOLDER_NAME WORKS_DRIVE_CURL_BIN WORKS_DRIVE_ARCHIVE_DIR WORKS_DRIVE_SHAREDRIVE_ID WORKS_DRIVE_SHAREDRIVE_BACKUP_DIR WORKS_DRIVE_OAUTH_CLIENT_ID WORKS_DRIVE_OAUTH_CLIENT_SECRET WORKS_DRIVE_OAUTH_CLIENT_SERVICE_ACCOUNT WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY_FILE WORKS_DRIVE_OAUTH_REFRESH_TOKEN WORKS_SHAREDRIVE_ID WORKS_SHAREDRIVE_BACKUP_DIR WORKS_ADMIN_API_BASE_URL WORKS_ADMIN_OAUTH_TOKEN_URL ) declare -A env_override_values=() env_override_set=() for env_key in "${env_override_keys[@]}"; do if [[ -n "${!env_key:-}" ]]; then env_override_set+=("$env_key") env_override_values["$env_key"]="${!env_key}" fi done set -a # shellcheck source=/dev/null source "$repo_root/.env" set +a for env_key in "${env_override_set[@]}"; do printf -v "$env_key" '%s' "${env_override_values[$env_key]}" export "$env_key" done fi backup_path="${BACKUP:-${1:-}}" [[ -n "$backup_path" ]] || backup_die "BACKUP is required. Example: make upload-cloud BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ" backup_require_path "$backup_path" WORKS_DRIVE_SHARED_DRIVE_ID="${WORKS_DRIVE_SHARED_DRIVE_ID:-${WORKS_DRIVE_SHAREDRIVE_ID:-${WORKS_SHAREDRIVE_ID:-}}}" WORKS_DRIVE_PARENT_FILE_ID="${WORKS_DRIVE_PARENT_FILE_ID:-${WORKS_DRIVE_SHAREDRIVE_BACKUP_DIR:-${WORKS_SHAREDRIVE_BACKUP_DIR:-}}}" dry_run="${WORKS_DRIVE_DRY_RUN:-false}" target="${WORKS_DRIVE_TARGET:-sharedrive}" auth_mode="${WORKS_DRIVE_AUTH_MODE:-auto}" api_base_url="${WORKS_ADMIN_API_BASE_URL:-https://www.worksapis.com}" curl_bin="${WORKS_DRIVE_CURL_BIN:-curl}" archive_dir="${WORKS_DRIVE_ARCHIVE_DIR:-/tmp/baron-sso-backup-upload}" split_size="${WORKS_DRIVE_SPLIT_SIZE:-9000M}" force_split="${WORKS_DRIVE_FORCE_SPLIT:-false}" max_single_file_bytes="${WORKS_DRIVE_MAX_SINGLE_FILE_BYTES:-0}" overwrite="${WORKS_DRIVE_OVERWRITE:-false}" upload_scope="${WORKS_DRIVE_OAUTH_SCOPE:-file}" upload_reports="${WORKS_DRIVE_UPLOAD_REPORTS:-true}" report_folder_name="${WORKS_DRIVE_REPORT_FOLDER_NAME:-reports}" report_dir="$backup_path/reports" case "$auth_mode" in auto | service-account | refresh-token) ;; *) backup_die "unknown WORKS_DRIVE_AUTH_MODE: $auth_mode. Expected auto, service-account, or refresh-token." ;; esac if [[ -f "$backup_path" ]]; then report_dir="$(dirname "$backup_path")" fi mkdir -p "$archive_dir" "$report_dir" backup_require_command jq urlencode_path() { jq -nr --arg value "$1" '$value|@uri' } json_string() { jq -nr --arg value "$1" '$value' } bytes_from_size() { local raw="$1" local number local unit if [[ "$raw" =~ ^([0-9]+)([KkMmGgTt]?)$ ]]; then number="${BASH_REMATCH[1]}" unit="${BASH_REMATCH[2]}" case "$unit" in K | k) printf '%s\n' $((number * 1024)) ;; M | m) printf '%s\n' $((number * 1024 * 1024)) ;; G | g) printf '%s\n' $((number * 1024 * 1024 * 1024)) ;; T | t) printf '%s\n' $((number * 1024 * 1024 * 1024 * 1024)) ;; *) printf '%s\n' "$number" ;; esac return fi backup_die "invalid size value: $raw" } split_curl_response() { local response="$1" local __body_var="$2" local __status_var="$3" local status local body status="$(tail -n 1 <<<"$response")" if [[ "$status" =~ ^[0-9][0-9][0-9]$ ]]; then body="$(sed '$d' <<<"$response")" else status="200" body="$response" fi printf -v "$__body_var" '%s' "$body" printf -v "$__status_var" '%s' "$status" } redact_for_log() { sed -E 's/("(access_token|refresh_token|assertion|client_secret|Authorization)"[[:space:]]*:[[:space:]]*)"[^"]*"/\1"REDACTED"/Ig' } resolve_target_upload_endpoint() { local parent_file_id="${1:-${WORKS_DRIVE_PARENT_FILE_ID:-}}" local encoded_parent="" if [[ -n "$parent_file_id" ]]; then encoded_parent="$(urlencode_path "$parent_file_id")" fi case "$target" in sharedrive) [[ -n "${WORKS_DRIVE_SHARED_DRIVE_ID:-}" ]] || backup_die "WORKS_DRIVE_SHARED_DRIVE_ID is required when WORKS_DRIVE_TARGET=sharedrive." local shared_drive_id shared_drive_id="$(urlencode_path "$WORKS_DRIVE_SHARED_DRIVE_ID")" if [[ -n "$encoded_parent" ]]; then printf '%s/v1.0/sharedrives/%s/files/%s\n' "$api_base_url" "$shared_drive_id" "$encoded_parent" else printf '%s/v1.0/sharedrives/%s/files\n' "$api_base_url" "$shared_drive_id" fi ;; mydrive) local user_id="${WORKS_DRIVE_USER_ID:-me}" user_id="$(urlencode_path "$user_id")" if [[ -n "$encoded_parent" ]]; then printf '%s/v1.0/users/%s/drive/files/%s\n' "$api_base_url" "$user_id" "$encoded_parent" else printf '%s/v1.0/users/%s/drive/files\n' "$api_base_url" "$user_id" fi ;; group) [[ -n "${WORKS_DRIVE_GROUP_ID:-}" ]] || backup_die "WORKS_DRIVE_GROUP_ID is required when WORKS_DRIVE_TARGET=group." local group_id group_id="$(urlencode_path "$WORKS_DRIVE_GROUP_ID")" if [[ -n "$encoded_parent" ]]; then printf '%s/v1.0/groups/%s/folder/files/%s\n' "$api_base_url" "$group_id" "$encoded_parent" else printf '%s/v1.0/groups/%s/folder/files\n' "$api_base_url" "$group_id" fi ;; sharedfolder) [[ -n "${WORKS_DRIVE_SHARED_FOLDER_ID:-}" ]] || backup_die "WORKS_DRIVE_SHARED_FOLDER_ID is required when WORKS_DRIVE_TARGET=sharedfolder." local user_id="${WORKS_DRIVE_USER_ID:-me}" local shared_folder_id user_id="$(urlencode_path "$user_id")" shared_folder_id="$(urlencode_path "$WORKS_DRIVE_SHARED_FOLDER_ID")" if [[ -n "$encoded_parent" ]]; then printf '%s/v1.0/users/%s/drive/sharedfolders/%s/files/%s\n' "$api_base_url" "$user_id" "$shared_folder_id" "$encoded_parent" else printf '%s/v1.0/users/%s/drive/sharedfolders/%s/files\n' "$api_base_url" "$user_id" "$shared_folder_id" fi ;; *) backup_die "unknown WORKS_DRIVE_TARGET: $target" ;; esac } resolve_target_children_endpoint() { local parent_file_id="${1:-${WORKS_DRIVE_PARENT_FILE_ID:-}}" local upload_endpoint upload_endpoint="$(resolve_target_upload_endpoint "$parent_file_id")" printf '%s/children\n' "$upload_endpoint" } resolve_target_create_folder_endpoint() { local parent_file_id="${1:-${WORKS_DRIVE_PARENT_FILE_ID:-}}" local upload_endpoint upload_endpoint="$(resolve_target_upload_endpoint "$parent_file_id")" printf '%s/createfolder\n' "$upload_endpoint" } base64url() { openssl base64 -A | tr '+/' '-_' | tr -d '=' } build_jwt_assertion() { backup_require_command openssl local client_id="${WORKS_DRIVE_OAUTH_CLIENT_ID:-}" local service_account="${WORKS_DRIVE_OAUTH_CLIENT_SERVICE_ACCOUNT:-}" local private_key="${WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY:-}" local private_key_file="${WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY_FILE:-}" local now local exp local header local payload local signing_input local key_file="" local temp_key_file="" [[ -n "$client_id" ]] || backup_die "WORKS_DRIVE_OAUTH_CLIENT_ID is required for service-account token mode." [[ -n "$service_account" ]] || backup_die "WORKS_DRIVE_OAUTH_CLIENT_SERVICE_ACCOUNT is required for service-account token mode." if [[ -n "$private_key" ]]; then temp_key_file="$(mktemp /tmp/baron-sso-works-key.XXXXXX)" printf '%s\n' "$private_key" >"$temp_key_file" key_file="$temp_key_file" elif [[ -n "$private_key_file" ]]; then if [[ "$private_key_file" != /* ]]; then private_key_file="$repo_root/$private_key_file" fi backup_require_path "$private_key_file" || return 1 key_file="$private_key_file" else backup_die "WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY or WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY_FILE is required for service-account token mode." fi now="$(date +%s)" exp="$((now + 3600))" header="$(printf '{"alg":"RS256","typ":"JWT"}' | base64url)" payload="$(jq -cn \ --arg iss "$client_id" \ --arg sub "$service_account" \ --argjson iat "$now" \ --argjson exp "$exp" \ '{iss:$iss, sub:$sub, iat:$iat, exp:$exp}' | base64url)" signing_input="${header}.${payload}" printf '%s' "$signing_input" \ | openssl dgst -sha256 -sign "$key_file" -binary \ | base64url \ | while IFS= read -r signature; do printf '%s.%s\n' "$signing_input" "$signature" done if [[ -n "$temp_key_file" ]]; then rm -f "$temp_key_file" fi } request_service_account_token() { local client_id="${WORKS_DRIVE_OAUTH_CLIENT_ID:-}" local client_secret="${WORKS_DRIVE_OAUTH_CLIENT_SECRET:-}" local token_url="${WORKS_ADMIN_OAUTH_TOKEN_URL:-https://auth.worksmobile.com/oauth2/v2.0/token}" local assertion local response local response_body local http_status [[ -n "$client_secret" ]] || backup_die "WORKS_DRIVE_OAUTH_CLIENT_SECRET is required for service-account token mode." assertion="$(build_jwt_assertion)" response="$("$curl_bin" -sS -w $'\n%{http_code}' -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \ --data-urlencode "assertion=$assertion" \ --data-urlencode "client_id=$client_id" \ --data-urlencode "client_secret=$client_secret" \ --data-urlencode "scope=$upload_scope" \ "$token_url")" split_curl_response "$response" response_body http_status if [[ "$http_status" -lt 200 || "$http_status" -ge 300 ]]; then backup_die "WORKS token request failed (HTTP $http_status): $(printf '%s' "$response_body" | redact_for_log)" fi jq -er '.access_token' <<<"$response_body" } request_refresh_access_token() { local client_id="${WORKS_DRIVE_OAUTH_CLIENT_ID:-}" local client_secret="${WORKS_DRIVE_OAUTH_CLIENT_SECRET:-}" local refresh_token="${WORKS_DRIVE_OAUTH_REFRESH_TOKEN:-}" local token_url="${WORKS_ADMIN_OAUTH_TOKEN_URL:-https://auth.worksmobile.com/oauth2/v2.0/token}" local response local response_body local http_status [[ -n "$client_id" ]] || backup_die "WORKS_DRIVE_OAUTH_CLIENT_ID is required for refresh-token mode." [[ -n "$client_secret" ]] || backup_die "WORKS_DRIVE_OAUTH_CLIENT_SECRET is required for refresh-token mode." [[ -n "$refresh_token" ]] || backup_die "WORKS_DRIVE_OAUTH_REFRESH_TOKEN is required for refresh-token mode." response="$("$curl_bin" -sS -w $'\n%{http_code}' -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=refresh_token" \ --data-urlencode "refresh_token=$refresh_token" \ --data-urlencode "client_id=$client_id" \ --data-urlencode "client_secret=$client_secret" \ "$token_url")" split_curl_response "$response" response_body http_status if [[ "$http_status" -lt 200 || "$http_status" -ge 300 ]]; then backup_die "WORKS refresh token request failed (HTTP $http_status): $(printf '%s' "$response_body" | redact_for_log)" fi jq -er '.access_token' <<<"$response_body" } service_account_credentials_configured() { [[ -n "${WORKS_DRIVE_OAUTH_CLIENT_ID:-}" ]] \ && [[ -n "${WORKS_DRIVE_OAUTH_CLIENT_SECRET:-}" ]] \ && [[ -n "${WORKS_DRIVE_OAUTH_CLIENT_SERVICE_ACCOUNT:-}" ]] \ && { [[ -n "${WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY:-}" ]] || [[ -n "${WORKS_DRIVE_OAUTH_CLIENT_PRIVATE_KEY_FILE:-}" ]]; } } resolve_access_token() { if [[ -n "${WORKS_DRIVE_ACCESS_TOKEN:-}" ]]; then printf '%s\n' "$WORKS_DRIVE_ACCESS_TOKEN" return fi if [[ -n "${WORKS_DRIVE_ACCESS_TOKEN_FILE:-}" ]]; then backup_require_path "$WORKS_DRIVE_ACCESS_TOKEN_FILE" sed -n '1p' "$WORKS_DRIVE_ACCESS_TOKEN_FILE" return fi if [[ -n "${WORKS_DRIVE_ACCESS_TOKEN_CMD:-}" ]]; then sh -c "$WORKS_DRIVE_ACCESS_TOKEN_CMD" return fi case "$auth_mode" in service-account) request_service_account_token ;; refresh-token) request_refresh_access_token ;; auto) if service_account_credentials_configured; then request_service_account_token return fi if [[ -n "${WORKS_DRIVE_OAUTH_REFRESH_TOKEN:-}" ]]; then request_refresh_access_token return fi request_service_account_token ;; esac } package_backup_path() { local source_path="$1" local source_name local target_base if [[ -f "$source_path" ]]; then [[ "$source_path" == *.zst ]] || backup_die "file BACKUP must be a .zst archive. Pass a backup directory to package it as .tar.zst automatically." printf '%s\n' "$source_path" return fi source_name="$(basename "$source_path")" target_base="$archive_dir/${source_name}" backup_require_command tar backup_require_command zstd tar --zstd -cf "${target_base}.tar.zst" -C "$(dirname "$source_path")" "$source_name" printf '%s\n' "${target_base}.tar.zst" } build_upload_file_list() { local package_file="$1" local file_size local split_bytes local split_dir local split_prefix backup_require_command stat backup_require_command split file_size="$(stat -c '%s' "$package_file")" split_bytes="$(bytes_from_size "$split_size")" if [[ "$force_split" == "true" || ( "$max_single_file_bytes" != "0" && "$file_size" -gt "$max_single_file_bytes" ) ]]; then split_dir="$archive_dir/split-$(basename "$package_file")" rm -rf "$split_dir" mkdir -p "$split_dir" split_prefix="$split_dir/$(basename "$package_file").part-" split -b "$split_bytes" -d -a 4 "$package_file" "$split_prefix" find "$split_dir" -maxdepth 1 -type f -name "$(basename "$split_prefix")*" | sort return fi printf '%s\n' "$package_file" } create_upload_url() { local access_token="$1" local endpoint="$2" local file_path="$3" local file_name local file_size local payload local response local response_body local http_status file_name="$(basename "$file_path")" file_size="$(stat -c '%s' "$file_path")" payload="$(jq -cn \ --arg fileName "$file_name" \ --argjson fileSize "$file_size" \ --argjson overwrite "$overwrite" \ '{fileName:$fileName, fileSize:$fileSize, overwrite:$overwrite}')" response="$("$curl_bin" -sS -w $'\n%{http_code}' -X POST \ -H "Authorization: Bearer $access_token" \ -H "Content-Type: application/json; charset=UTF-8" \ -d "$payload" \ "$endpoint")" split_curl_response "$response" response_body http_status if [[ "$http_status" -lt 200 || "$http_status" -ge 300 ]]; then backup_die "WORKS upload URL request failed (HTTP $http_status): $(printf '%s' "$response_body" | redact_for_log)" fi jq -er '.uploadUrl' <<<"$response_body" } upload_file_to_url() { local access_token="$1" local upload_url="$2" local file_path="$3" local file_name local response local response_body local http_status file_name="$(basename "$file_path")" response="$("$curl_bin" -sS -w $'\n%{http_code}' -X POST \ -H "Authorization: Bearer $access_token" \ -F "Filedata=@${file_path};filename=${file_name};type=application/octet-stream" \ "$upload_url")" split_curl_response "$response" response_body http_status if [[ "$http_status" -lt 200 || "$http_status" -ge 300 ]]; then backup_die "WORKS file upload failed (HTTP $http_status): $(printf '%s' "$response_body" | redact_for_log)" fi printf '%s\n' "$response_body" } list_child_folders() { local access_token="$1" local endpoint="$2" local response local response_body local http_status response="$("$curl_bin" -sS -w $'\n%{http_code}' -X GET \ -H "Authorization: Bearer $access_token" \ "$endpoint")" split_curl_response "$response" response_body http_status if [[ "$http_status" -lt 200 || "$http_status" -ge 300 ]]; then backup_die "WORKS folder list request failed (HTTP $http_status): $(printf '%s' "$response_body" | redact_for_log)" fi printf '%s\n' "$response_body" } create_child_folder() { local access_token="$1" local endpoint="$2" local folder_name="$3" local payload local response local response_body local http_status payload="$(jq -cn --arg fileName "$folder_name" '{fileName:$fileName}')" response="$("$curl_bin" -sS -w $'\n%{http_code}' -X POST \ -H "Authorization: Bearer $access_token" \ -H "Content-Type: application/json; charset=UTF-8" \ -d "$payload" \ "$endpoint")" split_curl_response "$response" response_body http_status if [[ "$http_status" -lt 200 || "$http_status" -ge 300 ]]; then backup_die "WORKS folder create request failed (HTTP $http_status): $(printf '%s' "$response_body" | redact_for_log)" fi jq -er '.fileId // .id' <<<"$response_body" } resolve_report_folder_id() { local access_token="$1" local children_endpoint local create_folder_endpoint local children_json local folder_id children_endpoint="$(resolve_target_children_endpoint)" create_folder_endpoint="$(resolve_target_create_folder_endpoint)" children_json="$(list_child_folders "$access_token" "$children_endpoint")" folder_id="$(jq -er --arg name "$report_folder_name" ' [ (.files // .children // .items // [])[] | select((.fileName // .name) == $name) | select(((.fileType // .type // "") | ascii_downcase) == "folder") | .fileId // .id ][0] // empty ' <<<"$children_json" 2>/dev/null || true)" if [[ -n "$folder_id" ]]; then printf '%s\n' "$folder_id" return fi create_child_folder "$access_token" "$create_folder_endpoint" "$report_folder_name" } discover_markdown_reports() { if [[ -f "$backup_path" || ! -d "$report_dir" ]]; then return fi find "$report_dir" -maxdepth 1 -type f -name '*.md' | sort } timestamp_report_file_for_upload() { local source_file="$1" local file_name local base_name local stamped_dir local stamped_file file_name="$(basename "$source_file")" base_name="${file_name%.md}" stamped_dir="$archive_dir/reports" mkdir -p "$stamped_dir" stamped_file="$stamped_dir/${base_name}-${report_upload_timestamp}.md" cp "$source_file" "$stamped_file" printf '%s\n' "$stamped_file" } write_upload_report() { local report_file="$1" local uploaded_json="$2" local uploaded_reports_json="${3:-[]}" jq -n \ --arg createdAt "$(backup_utc_now)" \ --arg backup "$backup_path" \ --arg target "$target" \ --arg endpoint "$upload_endpoint" \ --argjson files "$uploaded_json" \ --argjson reportFiles "$uploaded_reports_json" \ '{ created_at: $createdAt, backup: $backup, target: $target, upload_endpoint: $endpoint, files: $files, report_files: $reportFiles }' >"$report_file" } upload_endpoint="$(resolve_target_upload_endpoint)" report_upload_timestamp="$(backup_timestamp)" if [[ "$dry_run" == "true" ]]; then backup_log "Dry run: would upload BACKUP=$backup_path to WORKS Drive target=$target endpoint=$upload_endpoint" if [[ "$upload_reports" == "true" ]]; then backup_log "Dry run: would upload markdown reports from $report_dir to WORKS Drive folder=$report_folder_name" fi exit 0 fi backup_require_command "$curl_bin" package_file="$(package_backup_path "$backup_path")" mapfile -t upload_files < <(build_upload_file_list "$package_file") access_token="$(resolve_access_token)" uploaded_items="[]" for file_path in "${upload_files[@]}"; do backup_require_path "$file_path" backup_log "Creating WORKS Drive upload URL for $(basename "$file_path")" upload_url="$(create_upload_url "$access_token" "$upload_endpoint" "$file_path")" backup_log "Uploading $(basename "$file_path") to WORKS Drive" upload_response="$(upload_file_to_url "$access_token" "$upload_url" "$file_path")" upload_response_json="$(jq -c '.' <<<"${upload_response:-{}}" 2>/dev/null || printf '{}')" uploaded_items="$(jq \ --arg fileName "$(basename "$file_path")" \ --arg filePath "$file_path" \ --argjson fileSize "$(stat -c '%s' "$file_path")" \ --arg status "uploaded" \ --arg response "$upload_response_json" \ '. + [{file_name:$fileName, file_path:$filePath, file_size:$fileSize, status:$status, response:($response | fromjson? // {})}]' \ <<<"$uploaded_items")" done uploaded_report_items="[]" if [[ "$upload_reports" == "true" ]]; then mapfile -t markdown_reports < <(discover_markdown_reports) if [[ "${#markdown_reports[@]}" -gt 0 ]]; then backup_log "Resolving WORKS Drive report folder: $report_folder_name" report_folder_id="$(resolve_report_folder_id "$access_token")" report_upload_endpoint="$(resolve_target_upload_endpoint "$report_folder_id")" for report_path_item in "${markdown_reports[@]}"; do backup_require_path "$report_path_item" stamped_report_path="$(timestamp_report_file_for_upload "$report_path_item")" backup_log "Creating WORKS Drive upload URL for $(basename "$stamped_report_path")" upload_url="$(create_upload_url "$access_token" "$report_upload_endpoint" "$stamped_report_path")" backup_log "Uploading $(basename "$stamped_report_path") to WORKS Drive reports folder" upload_response="$(upload_file_to_url "$access_token" "$upload_url" "$stamped_report_path")" upload_response_json="$(jq -c '.' <<<"${upload_response:-{}}" 2>/dev/null || printf '{}')" uploaded_report_items="$(jq \ --arg fileName "$(basename "$stamped_report_path")" \ --arg sourcePath "$report_path_item" \ --arg filePath "$stamped_report_path" \ --argjson fileSize "$(stat -c '%s' "$stamped_report_path")" \ --arg status "uploaded" \ --arg response "$upload_response_json" \ '. + [{file_name:$fileName, source_path:$sourcePath, file_path:$filePath, file_size:$fileSize, status:$status, response:($response | fromjson? // {})}]' \ <<<"$uploaded_report_items")" done fi fi report_file="$report_dir/cloud-upload.json" write_upload_report "$report_file" "$uploaded_items" "$uploaded_report_items" backup_log "Upload complete: $report_file"