#!/bin/bash # # # ####[ Variables ]####################################################################### ## Colors for output YELLOW="$(printf '\033[0;33m')" GREEN="$(printf '\033[0;32m')" BLUE="$(printf '\033[0;34m')" CYAN="$(printf '\033[0;36m')" RED="$(printf '\033[1;31m')" NC="$(printf '\033[0m')" # No color. readonly YELLOW GREEN BLUE CYAN RED NC ## Toastielab project details readonly API_URL="https://toastielab.dev/api" ## other constants. readonly BOT_EXECUTABLE="EllieBot" readonly BIN_DIR="elliebot-bin" readonly BIN_DIR_OLD="elliebot-bin.old" readonly CREDS_FILE="creds.yml" readonly CREDS_EXAMPLE_FILE="creds_example.yml" # Packages `ffmpeg` and `ffprobe` are highly recommended dependencies for `yt-dlp`. # NOTE: Do not add `python`, `python3`, or `python-is-python3` to this list, as they are # checked separately. readonly REQUIRED_TOOLS=("jq" "yt-dlp") ####[ Functions ]####################################################################### #### # Display the main menu. display_menu() { clear echo "${BLUE}===== EllieBot Release Installer =====${NC}" echo "1. Install" echo "2. Run" echo "3. Edit credentials" echo "4. Exit" echo -n "${CYAN}Enter your choice:${NC} " } #### # Identify the system architecture, and return a string that represents # # RETURNS: # - linux-x64: For x86_64. # - linux-arm64: For aarch64 or arm64. # - unsupported: For all other architectures. get_arch() { case $(uname -m) in x86_64) echo "linux-x64" ;; aarch64|arm64) echo "linux-arm64" ;; *) echo "unsupported" ;; esac } #### # Perform a download operation using either 'wget' or 'curl'. # # NEW GLOBALS: # - METHOD: The method used to download files ('wget' or 'curl'). # # PARAMETERS: # - $1: url (Required) # - The full URL to download the item from. # - $2: output_file (Optional, Default: "") # - The name of the file to save the item to. # - IMPORTANT: If no output file is specified, the content at the URL will be # output to stdout. # - $3: error_message (Optional, Default: "ERROR: Failed to download the item") # - The message to display if the download fails. # - $4: is_silent (Optional, Default: false) # - If true, the download will be silent. # - Valid Options: # - true # - false # # RETURNS: # - 0: On successful download. # - 1: On failure to download the item. dl() { local url="$1" local output_file="${2:-}" local error_message="${3:-ERROR: Failed to download the item}" local is_silent="${4:-false}" local flags=() ## Sets a global constant to indicate whether 'wget' or 'curl' is should be used to ## download files. Additionally, set any applicable flags that are to be used. if [[ -z $METHOD ]]; then if hash curl &>/dev/null; then METHOD="curl" # Set a silent flag if the user wants to download silently. [[ $is_silent == true ]] && flags+=("-s") # If an output file is specified, add the '-o' flag. [[ -n $output_file ]] && flags+=("-L" "-o" "$output_file") elif hash wget &>/dev/null; then METHOD="wget" # Set a silent flag if the user wants to download silently. [[ $is_silent == true ]] && flags+=("-q") ## If an output file is specified, add the '-O' flag with the file name. if [[ -n $output_file ]]; then flags+=("-O" "$output_file") ## If no output file is specified, add the '-O-' flag to output to stdout. else flags+=("-O-") fi else echo "${RED}ERROR: Neither 'wget' nor 'curl' is installed${NC}" >&2 echo "${CYAN}Please install one of them and try again${NC}" >&2 return 1 fi fi ## Download the file. "$METHOD" "${flags[@]}" "$url" || { echo "${RED}$error_message${NC}" >&2 return 1 } return 0 } #### # Download EllieBot's archive. # # PARAMETERS: # - $1: url (Required) # - The full URL to download the archive from. # - $2: output (Required) # - The name of the file to save the archive to. # # RETURNS: # - 0: On successful download. # - 1: On failure to download the archive. download_archive() { local url="$1" local output="$2" dl "$url" "$output" "ERROR: Failed to download the archive" || return 1 return 0 } # TODO: Still needs to move data from the old directory to the new one. #### # Downloads the latest release, extracts it, and sets up EllieBot's directory. If # the directory already exists, it will be renamed to `elliebot-bin.old`. If # `elliebot-bin.old` already exists, it will be deleted. # # PARAMETERS: # - $1: version (Required) # - The release version to download. # # RETURNS: # - 1: On failure of some operation. install_software() { local version="$1" local archive_dir_name local archive_name="elliebot-v${version}.tar" local arch; arch=$(get_arch) local tar_url="${API_URL}/packages/EllieBotDevs/generic/EllieBot-build/${version}/${version}-${arch}-build.tar" ## NOTE: We could move this outside of the function, such that when the script is ## executed, the architecture is determined once, then exit the script if the ## architecture is unsupported. if [[ $arch == "unsupported" ]]; then echo "${RED}ERROR: Unsupported architecture${NC}" >&2 return 1 fi echo "${BLUE}Downloading '${version}' for '${arch}'...${NC}" download_archive "$tar_url" "$archive_name" || return 1 echo "${BLUE}Extracting...${NC}" tar -xf "$archive_name" || { echo "${RED}ERROR: Failed to extract the release${NC}" >&2 return 1 } archive_dir_name=$(tar -tf "$archive_name" | head -1 | cut -f1 -d"/") ## If `elliebot-bin.old` already exists, delete it, so that we can rename the ## current `elliebot-bin` to it. if [[ -d $BIN_DIR_OLD ]]; then echo "${BLUE}Removing '$BIN_DIR_OLD'...${NC}" rm -rf "$BIN_DIR_OLD" fi ## If the directory already exists, rename it to `elliebot-bin.old`. if [[ -d $BIN_DIR ]]; then echo "${BLUE}Renaming '$BIN_DIR' to '$BIN_DIR_OLD'...${NC}" mv "$BIN_DIR" "$BIN_DIR_OLD" fi echo "${BLUE}Renaming '$archive_dir_name' to '$BIN_DIR'...${NC}" if [[ -d $archive_dir_name ]]; then mv "$archive_dir_name" "$BIN_DIR" else echo "${RED}ERROR: Unarchived directory '$archive_dir_name' not found${NC}" >&2 return 1 fi rm "$archive_name" chmod +x "${BIN_DIR}/${BOT_EXECUTABLE}" echo "${GREEN}Installation complete!${NC}" } #### # Display a list of available versions, and prompt the user to select one to install. install_submenu() { local versions # NOTE: This works because the `jq` command outputs a newline-separated list. mapfile -t versions < <( dl "${API_URL}/releases" "" "ERROR: Failed to get releases" \ true | jq -r '.[].tag_name' | sort -V -r ) echo "${CYAN}Select version to install:${NC}" select version in "${versions[@]}"; do if [[ -n $version ]]; then install_software "$version" break else echo "${RED}ERROR: Invalid selection${NC}" fi done } #### # Check if the 'token' in 'creds.yml' is set. # # NOTE: This function is not foolproof, as it only checks if the 'token' is set to an # empty string. It does not check if the token is valid. # # RETURNS: # - 0: If the 'token' is not set. # - 1: If the 'token' is set. is_token_set() { if [[ ! -f $BIN_DIR/$CREDS_FILE ]]; then return 0 elif grep -Eq '^token: '\'\''' "$BIN_DIR/$CREDS_FILE"; then return 1 else return 0 fi } #### # Verify that the bot is installed, the token in the 'creds.yml' file is set, and then # run the bot. # # RETURNS: # - 1: If the bot is not installed, the executable is not found, or the token is not # set. run_bot() { ## Ensure that the bot is installed. if [[ ! -d $BIN_DIR ]]; then echo "${RED}ERROR: EllieBot not installed. Please install it first.${NC}" >&2 return 1 fi ## Ensures that the executable exists. if [[ ! -f $BIN_DIR/$BOT_EXECUTABLE ]]; then echo "${RED}ERROR: EllieBot executable not found${NC}" >&2 return 1 fi ## Create the creds file if it doesn't exist. if [[ ! -f $BIN_DIR/$CREDS_FILE ]]; then cp -f "$BIN_DIR/$CREDS_EXAMPLE_FILE" "$BIN_DIR/$CREDS_FILE" fi ## Ensure that the token is set. Do note that it won't say if the token is invalid. if ! is_token_set; then echo "${YELLOW}WARNING: 'token' is not set in '$CREDS_FILE'. Please add your" \ "token and try again.${NC}" >&2 return 1 fi echo "${BLUE}Attempting to run EllieBot...${NC}" pushd "$BIN_DIR" >/dev/null || return 1 ./$BOT_EXECUTABLE popd >/dev/null || return 1 } #### # Verify that Python 3 is installed, and that the version is 3.9 or higher. # # RETURNS: # - 0: If the verification passes. # - 1: If the verification fails. python_verification() { local verification_passed=true ## Verify that Python 3 is installed. if ! hash python3 &>/dev/null; then echo "${YELLOW}WARNING: Python 3 is not installed${NC}" >&2 verification_passed=false fi ## Some systems, specifically Linux distributions like Ubuntu, don't have `python` ## aliased to `python3`. This check is to ensure that `python` is available. if ! hash python &>/dev/null; then echo "${YELLOW}WARNING: The 'python' command is not available${NC}" >&2 echo "${CYAN}You may need to install 'python-is-python3'${NC}" >&2 verification_passed=false fi ## Some systems still have python2 available and installed. We need to ensure that ## the version of python is 3.9 or higher. if ! python -c "import sys; assert sys.version_info >= (3, 9)" &>/dev/null; then echo "${YELLOW}WARNING: Python 3.9 or higher is required${NC}" >&2 verification_passed=false fi [[ $verification_passed == false ]] && return 1 return 0 } #### # Check if external tools that often need to be installed are available. If any are # missing, print an error message and exit. # # EXITS: # - 1: If any of the required tools are not installed. verify_tools() { local all_tools_installed=true for tool in "${REQUIRED_TOOLS[@]}"; do if ! hash "$tool" &>/dev/null; then all_tools_installed=false echo "${YELLOW}WARNING: '$tool' is not installed${NC}" >&2 fi done python_verification if [[ $all_tools_installed == false ]]; then echo "${CYAN}Please install the required tools and try again${NC}" >&2 exit 1 fi } # TODO: Still needs to be edited/expanded upon. edit_creds() { local creds_file="${BIN_DIR}/creds.yml" if [[ ! -d $BIN_DIR ]]; then echo "Please install the bot first." return 1 fi if [[ ! -f $creds_file ]]; then cp $BIN_DIR/creds_example.yml $creds_file fi local file="$1" local editors=("nano" "micro" "code" "vim" "emacs" "gedit") if [ ! -f "$file" ]; then echo "ERROR: File '$file' does not exist." >&2 return 1 fi for editor in "${editors[@]}"; do if command -v "$editor" >/dev/null 2>&1; then "$editor" "$file" return 0 fi done echo "ERROR: No known text editors (${editors[*]}) are available on this system." >&2 return 1 } ####[ Main ]############################################################################ verify_tools while true; do display_menu read -r choice case $choice in 1) install_submenu ;; 2) run_bot ;; 3) edit_creds ;; 4) echo "${GREEN}Exiting...${NC}"; exit 0 ;; *) echo "${RED}ERROR: Invalid option${NC}" ;; esac echo "${CYAN}Press [Enter] to continue...${NC}" read -r done