Last active
November 23, 2024 20:08
-
-
Save obeone/39b546272ad76da294a31e3f9332122a to your computer and use it in GitHub Desktop.
transfer.sh management script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
################################################################### | |
# Script Name : transfer.sh | |
# Description : A script to manage file transfers using Transfer.sh service. | |
# It supports operations like send, receive, delete, | |
# and get information about files and directories. | |
# Args : [OPTIONS] <command> | |
# Author : GPT-3.5, GPT-4 and obeone (https://github.com/obeone) | |
# Version : 1.1 | |
# Date : 2024-02-15 | |
# License : MIT License | |
# Usage : ./transfer.sh --help | |
# Notes : Ensure you have the necessary permissions to execute | |
# and that your terminal supports ANSI color codes for | |
# the best experience. | |
################################################################### | |
# Fail on any error | |
set -e | |
# Environment defaults | |
: ${TRANSFERSH_URL:="https://transfer.sh"} | |
: ${TRANSFERSH_MAX_DAYS:=} | |
: ${TRANSFERSH_MAX_DOWNLOADS:=} | |
: ${LOG_LEVEL:="INFO"} | |
: ${AUTH_USER:=""} | |
: ${AUTH_PASS:=""} | |
COMMAND="$(basename "$0")" | |
# Color codes | |
RED='\e[31m' | |
GREEN='\e[32m' | |
YELLOW='\e[33m' | |
BLUE='\e[34m' | |
MAGENTA='\e[35m' | |
CYAN='\e[36m' | |
RESET='\e[0m' | |
BOLD='\e[1m' | |
UNDERLINE='\e[4m' | |
ITALIC='\e[3m' | |
INVERTED='\e[7m' | |
log() { | |
local level="$1" | |
shift | |
local message="$*" | |
local levels=("ERROR" "WARN" "INFO" "DEBUG") | |
local current_level="" | |
exec 3>&1 | |
for i in "${!levels[@]}"; do | |
if [[ "${levels[$i]}" == "$LOG_LEVEL" ]]; then | |
current_level="${i}" | |
break | |
fi | |
done | |
for i in "${!levels[@]}"; do | |
if [[ "$level" == "${levels[$i]}" ]] && [[ $i -le $current_level ]]; then | |
case "$level" in | |
DEBUG) | |
echo -e "${YELLOW}[DEBUG] ${message}${RESET}" >&3 | |
;; | |
INFO) | |
echo -e "${GREEN}[INFO] ${message}${RESET}" >&3 | |
;; | |
WARN) | |
echo -e "${YELLOW}[WARN] ${message}${RESET}" >&3 | |
;; | |
ERROR) | |
echo -e "${RED}[ERROR] ${message}${RESET}" >&3 | |
;; | |
esac | |
return | |
fi | |
done | |
} | |
check_requirements() { | |
for cmd in curl openssl zip; do | |
if ! command -v $cmd &> /dev/null; then | |
log ERROR "$cmd is not installed." | |
exit 1 | |
fi | |
done | |
} | |
curl_call() { | |
log DEBUG "Calling curl with the following command: curl $*" | |
command curl "$@" | |
return_code=$? | |
if [ $return_code -ne 0 ]; then | |
log ERROR "Curl command failed with return code: $return_code" | |
fi | |
return $return_code | |
} | |
display_help() { | |
case $1 in | |
send) | |
echo -e "${GREEN}Usage: $0 send [OPTIONS] <file|directory> [<file|directory>...]${RESET}" | |
echo -e "Options:" | |
echo -e " ${CYAN}-h, --help${RESET} Display help message" | |
echo -e " ${CYAN}-d, --max-downloads <value>${RESET} Set the maximum number of downloads (optional)" | |
echo -e " ${CYAN}-D, --max-days <value>${RESET} Set the maximum number of days the file is stored (optional)" | |
echo -e " ${CYAN}-k, --key <value>${RESET} Encryption key (optional)" | |
echo -e " ${CYAN}-u, --user <value>${RESET} Username for basic auth (optional)" | |
echo -e " ${CYAN}-p, --password <value>${RESET} Password for basic auth (optional)" | |
echo -e " ${CYAN}-y${RESET} Bypass confirmation" | |
echo -e " ${CYAN}TRANSFERSH_ENCRYPTION_KEY${RESET} Encryption key can also be set in environment variable TRANSFERSH_ENCRYPTION_KEY" | |
echo -e " ${CYAN}TRANSFERSH_MAX_DAYS${RESET} Set the maximum number of days the file is stored" | |
echo -e " ${CYAN}TRANSFERSH_MAX_DOWNLOADS${RESET} Set the maximum number of downloads" | |
echo -e " ${CYAN}TRANSFERSH_URL${RESET} In case of self-hosted installation, URL can be set in environment variable TRANSFERSH_URL" | |
;; | |
receive) | |
echo -e "${GREEN}Usage: $0 receive <URL> [destination]${RESET}" | |
echo -e " ${CYAN}-k, --key <value>${RESET} Decryption key (optional)" | |
echo -e " ${CYAN}-u, --unzip${RESET} Offer to unzip file on receive (optional)" | |
echo -e " ${CYAN}TRANSFERSH_ENCRYPTION_KEY${RESET} Decryption key can also be set in environment variable TRANSFERSH_ENCRYPTION_KEY" | |
;; | |
delete) | |
echo -e "${GREEN}Usage: $0 delete <X-URL-Delete>${RESET}" | |
;; | |
info) | |
echo -e "${GREEN}Usage: $0 info <URL>${RESET}" | |
;; | |
*) | |
echo -e "${GREEN}Usage: $0 [OPTIONS] <command>${RESET}" | |
echo -e "Options:" | |
echo -e " ${CYAN}-h, --help${RESET} Display help" | |
echo -e " ${CYAN}-y${RESET} Bypass confirmation" | |
echo -e " ${CYAN}--log-level <level>${RESET} Set the logging level (ERROR, WARN, INFO, DEBUG) (default: INFO)" | |
echo -e "Commands:" | |
echo -e " ${CYAN}send${RESET} Send a file or directory" | |
echo -e " ${CYAN}receive${RESET} Receive a file or directory" | |
echo -e " ${CYAN}delete${RESET} Delete a file or directory" | |
echo -e " ${CYAN}info${RESET} Get information about a file or directory" | |
;; | |
esac | |
} | |
encrypt_file() { | |
local file="$1" | |
local key="$2" | |
local outfile="$(mktemp)" | |
if openssl enc -aes-256-cbc -salt -pbkdf2 -in "$file" -out "$outfile" -pass pass:"$key"; then | |
echo "$outfile" | |
else | |
rm -f "$outfile" | |
return 1 | |
fi | |
} | |
decrypt_file() { | |
local file="$1" | |
local key="$2" | |
local outfile="$3" | |
openssl enc -d -aes-256-cbc -pbkdf2 -in "$file" -out "$outfile" -pass pass:"$key" | |
return $? | |
} | |
send_file_or_directory() { | |
local max_downloads="${TRANSFERSH_MAX_DOWNLOADS}" | |
local max_days="${TRANSFERSH_MAX_DAYS}" | |
local headers=() | |
local encryption_key="${TRANSFERSH_ENCRYPTION_KEY:-}" | |
local request_confirmation=true | |
local should_zip=false | |
local temp_file="" | |
local file_name="" | |
local auth="" | |
local auth="" | |
local should_zip=false | |
local temp_file="" | |
local file_name="" | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
-d|--max-downloads) | |
max_downloads="$2" | |
log DEBUG "Max downloads set to: $max_downloads" | |
shift 2 | |
;; | |
-D|--max-days) | |
max_days="$2" | |
log DEBUG "Max days set to: $max_days" | |
shift 2 | |
;; | |
-k|--key) | |
encryption_key="$2" | |
log DEBUG "Encryption key provided." | |
shift 2 | |
;; | |
-u|--user) | |
AUTH_USER="$2" | |
auth=1 | |
log DEBUG "Basic auth username provided." | |
shift 2 | |
;; | |
-p|--password) | |
AUTH_PASS="$2" | |
auth=1 | |
log DEBUG "Basic auth password provided." | |
shift 2 | |
;; | |
-h|--help) | |
display_help send | |
return 0 | |
;; | |
-y) | |
log DEBUG "Bypassing confirmation." | |
request_confirmation=false | |
shift | |
;; | |
*) | |
break | |
;; | |
esac | |
done | |
create_zip() { | |
temp_file="$(mktemp -u).zip" | |
log DEBUG "Creating ZIP file: $temp_file" | |
zip -r "$temp_file" "$@" | |
file_name="transfer.zip" | |
} | |
extract_url() { | |
local content="$1" | |
# Capture the delete URL | |
local url_delete=$(echo "$content" | grep -i 'x-url-delete:' | awk '{print $2}') | |
# Capture the final URL (last line of the content) | |
local url_end=$(echo "$content" | tail -n 1 | sed 's/^.*[0-9.]*%//') | |
# Return both URLs separated by a semicolon | |
echo "$url_delete;$url_end" | |
} | |
if [ -z "$encryption_key" ]; then | |
echo -ne "${CYAN}Enter encryption key (leave empty for no encryption): ${RESET}" | |
read -rs encryption_key | |
echo | |
fi | |
if [[ request_confirmation ]]; then | |
echo -e "${CYAN}You are about to send the following files/directories:${RESET}" | |
for file in "$@"; do | |
echo -e "${BLUE}$(basename "$file")${RESET}" | |
done | |
read -p $'\e[1;35mAre you sure you want to proceed? (Y/n): \e[0m' confirm | |
confirm=${confirm:-y} | |
[[ "$confirm" =~ ^[Yy]$ ]] || return 1 | |
[[ -n "$max_downloads" ]] && headers+=("-H" "Max-Downloads: $max_downloads") | |
[[ -n "$max_days" ]] && headers+=("-H" "Max-Days: $max_days") | |
fi | |
if [[ $# -gt 1 || -d "$1" ]]; then | |
should_zip=true | |
create_zip "$@" | |
else | |
temp_file="$1" | |
file_name=$(basename "$1") | |
fi | |
local response | |
local return_code | |
local auth_options=() | |
if [ -n "$auth" ]; then | |
auth_options=("--user" "$AUTH_USER:$AUTH_PASS") | |
fi | |
if [ -n "$encryption_key" ]; then | |
file_name="$file_name.enc" | |
log DEBUG "Uploading encrypted file: $file_name" | |
encrypted_file=$(encrypt_file "$temp_file" "$encryption_key") | |
if [[ $? == 1 ]]; then | |
log ERROR "Cannot encrypt" | |
exit 1 | |
fi | |
response=$(curl_call --dump-header /dev/stdout --progress-bar "${auth_options[@]}" "${headers[@]}" --upload-file "$encrypted_file" "${TRANSFERSH_URL}/$file_name" 2>&1 | grep -v 'HTTP/2') | |
rm "$encrypted_file" | |
return_code=$? | |
else | |
log DEBUG "Uploading file: $temp_file" | |
response=$(curl_call --dump-header /dev/stdout --progress-bar "${auth_options[@]}" "${headers[@]}" --upload-file "$temp_file" "${TRANSFERSH_URL}/$file_name" 2>&1 | grep -v 'HTTP/2') | |
return_code=$? | |
fi | |
log DEBUG "cURL Reponse :" | |
log DEBUG "$response" | |
if echo "$response" | grep -q "Not authorized"; then | |
log ERROR "Not authorized error occurred." | |
exit 1 | |
fi | |
if [ $return_code -ne 0 ]; then | |
log ERROR "Failed to upload file." | |
exit 1 | |
fi | |
# Call the function and capture the outputs in variables | |
IFS=";" read url_delete url <<< "$(extract_url "$response")" | |
[ "$should_zip" = true ] && rm "$temp_file" | |
if [ -n "$url" ]; then | |
echo -e "\n${GREEN}${BOLD}Upload successful.${RESET}" | |
echo "" | |
echo -e "${GREEN}${BOLD}Link to the file:${RESET} ${GREEN}${url}${RESET}" | |
echo -e "${BLUE}${BOLD}Receive command:${RESET} ${BLUE}${COMMAND} receive ${url}${RESET}" | |
echo "" | |
echo -e "${RED}${BOLD}Delete command:${RESET} ${RED}${COMMAND} delete ${url_delete}${RESET}" | |
fi | |
} | |
receive_file_or_directory() { | |
local encryption_key="${TRANSFERSH_ENCRYPTION_KEY:-}" | |
local offer_unzip="false" | |
local encrypted="false" | |
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then | |
display_help receive | |
return 0 | |
elif [ "$1" == "-k" ] || [ "$1" == "--key" ]; then | |
encryption_key="$2" | |
log DEBUG "Decryption key provided." | |
shift 2 | |
elif [ "$1" == "-u" ] || [ "$1" == "--unzip" ]; then | |
offer_unzip="true" | |
log DEBUG "Offering to unzip after download." | |
shift | |
fi | |
local url="$1" | |
local destination="${2:-.}" | |
local file_name=$(basename "$url") | |
local response | |
local return_code | |
local decryption_command | |
if [[ "$file_name" == *.enc ]]; then | |
encrypted="true" | |
if [ -z "$encryption_key" ]; then | |
read -sp "Enter decryption key: " encryption_key | |
echo | |
fi | |
fi | |
log DEBUG "Downloading file from $url to $destination" | |
local temp_download="$(mktemp)" | |
response=$(curl_call "$url" -o "$temp_download" -w "%{http_code}" -s) | |
return_code=$? | |
if [ $return_code -ne 0 ] || [ "$response" -ne 200 ]; then | |
log ERROR "Failed to download file." | |
log ERROR "cURL response: $response" | |
rm "$temp_download" | |
exit 1 | |
fi | |
if [ -z "$encryption_key" ]; then | |
mv "$temp_download" "$destination/$file_name" | |
else | |
decrypt_file "$temp_download" "$encryption_key" "$destination/${file_name%.enc}" | |
rm "$temp_download" | |
fi | |
if [ "$offer_unzip" == "true" ] && [[ "$file_name" == *.zip || "$file_name" == *.zip.enc ]]; then | |
read -p $'\e[1;35mDo you want to unzip the downloaded file? (Y/n): \e[0m' confirm_unzip | |
confirm_unzip=${confirm_unzip:-y} | |
if [[ "$confirm_unzip" =~ ^[Yy]$ ]]; then | |
unzip "$destination/$file_name" -d "$destination" | |
rm "$destination/$file_name" | |
fi | |
fi | |
log INFO "Download successful." | |
} | |
delete_file_or_directory() { | |
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then | |
display_help delete | |
return 0 | |
fi | |
local delete_url="$1" | |
log DEBUG "Deleting file at URL: $delete_url" | |
curl_call -X DELETE "$delete_url" | |
log INFO "File deleted successfully." | |
} | |
info_command() { | |
local url="$1" | |
log INFO "Retrieving information for URL: $url" | |
curl_call -I "$url" | awk '/x-remaining-days/ {print "Remaining Days: " $2} /x-remaining-downloads/ {print "Remaining Downloads: " $2} /Content-Length/ {print "File Size: " $2} /Content-Type/ {print "Mime-Type: " $2}' | |
} | |
# Main script | |
check_requirements | |
if [[ $# -eq 0 ]]; then | |
display_help | |
exit 0 | |
fi | |
while [[ $# -gt 0 ]]; do | |
case $1 in | |
-h|--help) | |
display_help | |
exit 0 | |
;; | |
--log-level) | |
LOG_LEVEL="$2" | |
log INFO "Log level set to: $LOG_LEVEL" | |
shift 2 | |
;; | |
send) | |
shift | |
send_file_or_directory "$@" | |
exit 0 | |
;; | |
receive) | |
shift | |
receive_file_or_directory "$@" | |
exit 0 | |
;; | |
delete) | |
shift | |
delete_file_or_directory "$@" | |
exit 0 | |
;; | |
info) | |
shift | |
info_command "$@" | |
exit 0 | |
;; | |
*) | |
log ERROR "Invalid option: $1" | |
display_help | |
exit 1 | |
;; | |
esac | |
done | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment