forked from baron/baron-sso
백업/복구로직 변경, 깜빡임 버그 해결
This commit is contained in:
642
scripts/backup/upload_cloud.sh
Executable file
642
scripts/backup/upload_cloud.sh
Executable file
@@ -0,0 +1,642 @@
|
||||
#!/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_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 [[ -v "$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}"
|
||||
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"
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
if [[ -n "${WORKS_DRIVE_OAUTH_REFRESH_TOKEN:-}" ]]; then
|
||||
request_refresh_access_token
|
||||
return
|
||||
fi
|
||||
|
||||
request_service_account_token
|
||||
}
|
||||
|
||||
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"
|
||||
Reference in New Issue
Block a user