|
#!/usr/bin/env bash |
|
set -uo pipefail # Setting -e is far too much work here |
|
IFS=$'\n\t' |
|
set +o noclobber |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @file git_instructions.sh |
|
# @brief This script interacts with Git repositories to perform various |
|
# functions such as cloning, downloading files, and updating file |
|
# ownership and permissions. |
|
# |
|
# @usage |
|
# To use the script, run it as follows: |
|
# |
|
# ./git_functions.sh |
|
# |
|
# The script automatically fetches repository data, downloads files from |
|
# specified directories, and performs other Git-related operations. |
|
# |
|
# Example usage: |
|
# ./git_functions.sh |
|
# |
|
# @license |
|
# This script is distributed under the MIT License. |
|
# |
|
# @author |
|
# Lee Bussy |
|
# |
|
# @version |
|
# 1.0 |
|
# ----------------------------------------------------------------------------- |
|
|
|
declare FALLBACK_SCRIPT_NAME="${FALLBACK_SCRIPT_NAME:-template.sh}" |
|
declare REPO_ORG="${REPO_ORG:-lbussy}" |
|
declare REPO_NAME="${REPO_NAME:-ap-popup}" |
|
declare REPO_BRANCH="${REPO_BRANCH:-main}" |
|
declare GIT_TAG="${GIT_TAG:-0.0.1}" |
|
declare SEM_VER="${GIT_TAG:-0.0.1}" |
|
declare LOCAL_REPO_DIR="${LOCAL_REPO_DIR:-}" |
|
declare LOCAL_WWW_DIR="${LOCAL_WWW_DIR:-}" |
|
declare LOCAL_SCRIPTS_DIR="${LOCAL_SCRIPTS_DIR:-}" |
|
declare GIT_RAW="${GIT_RAW:-"https://raw.githubusercontent.com/$REPO_ORG/$REPO_NAME"}" |
|
declare GIT_API="${GIT_API:-"https://api.github.com/repos/$REPO_ORG/$REPO_NAME"}" |
|
declare GIT_CLONE="${GIT_CLONE:-"https://github.com/$REPO_ORG/$REPO_NAME.git"}" |
|
|
|
declare USER_HOME |
|
if [[ -n "${SUDO_USER-}" ]]; then |
|
readonly USER_HOME=$(eval echo "~$SUDO_USER") |
|
else |
|
readonly USER_HOME="$HOME" |
|
fi |
|
|
|
readonly DIRECTORIES=("man" "scripts" "conf") # Relevant directories for download. |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @brief Logs an informational message. |
|
# @details This function formats and logs messages with an "[INFO ]" prefix. |
|
# It removes extra spaces and trims leading/trailing spaces. |
|
# |
|
# @param $1 The message to log. |
|
# |
|
# @return None. |
|
# |
|
# @example |
|
# logI "Repository cloned successfully" |
|
# ----------------------------------------------------------------------------- |
|
logI() { |
|
local func_name="${FUNCNAME[0]}" |
|
local caller_name="${FUNCNAME[1]}" |
|
local caller_line="${BASH_LINENO[0]}" |
|
local message |
|
message="$*" |
|
message="${message%"${message##*[![:space:]]}"}" # Trim leading spaces |
|
message="${message#"${message%%[![:space:]]*}"}" # Trim trailing spaces |
|
message=$(echo "$message" | sed 's/ */ /g') # Replace multiple spaces with a single space |
|
echo "[INFO ] $message" |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @brief Logs an error message. |
|
# @details This function formats and logs error messages with an "[ERROR]" prefix. |
|
# It removes extra spaces and trims leading/trailing spaces. |
|
# |
|
# @param $1 The error message to log. |
|
# |
|
# @return None. |
|
# |
|
# @example |
|
# logE "Failed to clone repository" |
|
# ----------------------------------------------------------------------------- |
|
logE() { |
|
local func_name="${FUNCNAME[0]}" |
|
local caller_name="${FUNCNAME[1]}" |
|
local caller_line="${BASH_LINENO[0]}" |
|
local message |
|
message="$*" |
|
message="${message%"${message##*[![:space:]]}"}" # Trim leading spaces |
|
message="${message#"${message%%[![:space:]]*}"}" # Trim trailing spaces |
|
message=$(echo "$message" | sed 's/ */ /g') # Replace multiple spaces with a single space |
|
echo "[ERROR] $message" |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @brief Updates ownership and permissions of a file. |
|
# @details Changes the ownership of a file to match the owner of a specified home |
|
# directory and adjusts file permissions based on its type (executable |
|
# or non-executable). |
|
# |
|
# @param $1 The file path to update. |
|
# @param $2 The root directory used to determine file ownership. |
|
# |
|
# @global None. |
|
# |
|
# @throws Logs errors and returns non-zero if the file or directory is invalid |
|
# or if ownership/permission updates fail. |
|
# |
|
# @return Returns 0 on success, or 1 on failure. |
|
# |
|
# @example |
|
# update_file "/path/to/file.sh" "/home/user" |
|
# ----------------------------------------------------------------------------- |
|
update_file() { |
|
local file="$1" |
|
local home_root="$2" |
|
|
|
if [[ -z "$file" || -z "$home_root" ]]; then |
|
logE "Usage: update_file <file> <home_root>" |
|
return 1 |
|
fi |
|
|
|
if [[ ! -d "$home_root" ]]; then |
|
logE "Home root '$home_root' is not a valid directory." |
|
return 1 |
|
fi |
|
|
|
if [[ ! -f "$file" ]]; then |
|
logE "File '$file' does not exist." |
|
return 1 |
|
fi |
|
|
|
local owner |
|
owner=$(stat -c '%U' "$home_root") |
|
if [[ -z "$owner" ]]; then |
|
logE "Unable to determine the owner of the home root." |
|
return 1 |
|
fi |
|
|
|
logI "Changing ownership of '$file' to '$owner'." |
|
chown "$owner":"$owner" "$file" || { |
|
logE "Failed to change ownership." |
|
return 1 |
|
} |
|
|
|
if [[ "$file" == *.sh ]]; then |
|
logI "Setting permissions of '$file' to 700 (executable)." |
|
chmod 700 "$file" || { |
|
logE "Failed to set permissions to 700." |
|
return 1 |
|
} |
|
else |
|
logI "Setting permissions of '$file' to 600." |
|
chmod 600 "$file" || { |
|
logE "Failed to set permissions to 600." |
|
return 1 |
|
} |
|
fi |
|
|
|
logI "Ownership and permissions updated successfully for '$file'." |
|
return 0 |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @brief Downloads a single file from a Git repository's raw URL. |
|
# @details Fetches a file from the raw content URL of the repository and saves |
|
# it to the specified local directory. Ensures the destination |
|
# directory exists before downloading. |
|
# |
|
# @param $1 The relative path of the file in the repository. |
|
# @param $2 The local destination directory where the file will be saved. |
|
# |
|
# @global GIT_RAW The base URL for raw content access in the Git repository. |
|
# @global REPO_BRANCH The branch name from which the file will be fetched. |
|
# |
|
# @throws Logs an error and returns non-zero if the file download fails. |
|
# |
|
# @return None. Downloads the file to the specified directory. |
|
# |
|
# @example |
|
# download_file "path/to/file.txt" "/local/dir" |
|
# ----------------------------------------------------------------------------- |
|
download_file() { |
|
local file_path="$1" |
|
local dest_dir="$2" |
|
|
|
mkdir -p "$dest_dir" |
|
|
|
local file_name |
|
file_name=$(basename "$file_path") |
|
file_name="${file_name//\'/}" |
|
|
|
logI "Downloading from: $GIT_RAW/$REPO_BRANCH/$file_path to $dest_dir/$file_name" |
|
|
|
wget -q -O "$dest_dir/$file_name" "$GIT_RAW/$REPO_BRANCH/$file_path" || { |
|
logE "Failed to download file: $file_path to $dest_dir/$file_name" |
|
return 1 |
|
} |
|
|
|
local dest_file="$dest_dir/$file_name" |
|
mv "$dest_file" "${dest_file//\'/}" |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @brief Clones a GitHub repository to the specified local destination. |
|
# @details This function clones the repository from the provided Git URL to the |
|
# specified local destination directory. |
|
# |
|
# @global GIT_CLONE The base URL for cloning the GitHub repository. |
|
# @global USER_HOME The home directory of the user, used as the base for storing files. |
|
# @global REPO_NAME The name of the repository to clone. |
|
# |
|
# @throws Logs an error and returns non-zero if the repository cloning fails. |
|
# |
|
# @return None. Clones the repository into the local destination. |
|
# |
|
# @example |
|
# git_clone |
|
# ----------------------------------------------------------------------------- |
|
git_clone() { |
|
local dest_root="$USER_HOME/$REPO_NAME" |
|
mkdir -p "$dest_root" |
|
|
|
logI "Cloning repository from $GIT_CLONE to $dest_root" |
|
git clone "$GIT_CLONE" "$dest_root" || { |
|
logE "Failed to clone repository: $GIT_CLONE to $dest_root" |
|
return 1 |
|
} |
|
|
|
logI "Repository cloned successfully to $dest_root" |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @brief Fetches the Git tree of a specified branch from a repository. |
|
# @details Retrieves the SHA of the specified branch and then fetches the |
|
# complete tree structure of the repository, allowing recursive access |
|
# to all files and directories. |
|
# |
|
# @global GIT_API The base URL for the GitHub API, pointing to the repository. |
|
# @global REPO_BRANCH The branch name to fetch the tree from. |
|
# |
|
# @throws Prints an error message and exits if the branch SHA cannot be fetched. |
|
# |
|
# @return Outputs the JSON representation of the repository tree. |
|
# |
|
# @example |
|
# fetch_tree |
|
# ----------------------------------------------------------------------------- |
|
fetch_tree() { |
|
local branch_sha |
|
branch_sha=$(curl -s "$GIT_API/git/ref/heads/$REPO_BRANCH" | jq -r '.object.sha') |
|
|
|
if [[ -z "$branch_sha" || "$branch_sha" == "null" ]]; then |
|
logE "Failed to fetch branch SHA for branch: $REPO_BRANCH. Check repository details or API access." |
|
return 1 |
|
fi |
|
|
|
curl -s "$GIT_API/git/trees/$branch_sha?recursive=1" |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @brief Downloads files from specified directories in a repository. |
|
# @details This function retrieves a repository tree, identifies files within |
|
# specified directories, and downloads them to the local system. |
|
# |
|
# @param $1 The target directory to update. |
|
# |
|
# @global USER_HOME The home directory of the user, used as the base for storing files. |
|
# @global DIRECTORIES Array of directories in the repository to process. |
|
# |
|
# @throws Exits the script with an error if the repository tree cannot be fetched. |
|
# |
|
# @return Downloads files to the specified directory structure under $USER_HOME/apppop. |
|
# |
|
# @example |
|
# download_files_in_directories |
|
# ----------------------------------------------------------------------------- |
|
download_files_in_directories() { |
|
local dest_root="$USER_HOME/$REPO_NAME" |
|
logI "Fetching repository tree." |
|
local tree=$(fetch_tree) |
|
|
|
if [[ $(printf "%s" "$tree" | jq '.tree | length') -eq 0 ]]; then |
|
die 1 "Failed to fetch repository tree. Check repository details or ensure it is public." |
|
fi |
|
|
|
for dir in "${DIRECTORIES[@]}"; do |
|
logI "Processing directory: $dir" |
|
|
|
local files |
|
files=$(printf "%s" "$tree" | jq -r --arg TARGET_DIR "$dir/" \ |
|
'.tree[] | select(.type=="blob" and (.path | startswith($TARGET_DIR))) | .path') |
|
|
|
if [[ -z "$files" ]]; then |
|
logI "No files found in directory: $dir" |
|
continue |
|
fi |
|
|
|
local dest_dir="$dest_root/$dir" |
|
mkdir -p "$dest_dir" |
|
|
|
printf "%s\n" "$files" | while read -r file; do |
|
logI "Downloading: $file" |
|
download_file "$file" "$dest_dir" |
|
done |
|
|
|
logI "Files from $dir downloaded to: $dest_dir" |
|
done |
|
|
|
logI "Files saved in: $dest_root" |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @brief Entry point to the script. |
|
# @details This function calls the `git_clone` function to clone the repository |
|
# into the specified destination directory. |
|
# |
|
# @return None. |
|
# |
|
# @example |
|
# _main |
|
# ----------------------------------------------------------------------------- |
|
_main() { |
|
git_clone |
|
} |
|
|
|
# ----------------------------------------------------------------------------- |
|
# @brief Main function of the script. |
|
# @details This function is the script's entry point and invokes the `_main` |
|
# function with arguments passed to it. It provides a way to handle |
|
# script execution and allows for easier testing and debugging. |
|
# |
|
# @param $@ The arguments passed to the script, which are forwarded to `_main`. |
|
# |
|
# @return None. |
|
# |
|
# @example |
|
# main "$@" |
|
# ----------------------------------------------------------------------------- |
|
main() { _main "$@"; }; |
|
|
|
# Call the main function and exit. |
|
main "$@" |
|
exit $? |