#!/usr/bin/env sh
# Soar Installation Script (v1.0.0)
# Tries to be as POSIX compliant as possible (Any deviation is intentional)
# Assumptions: User has a supported downloader
# Supported Downloaders:
# aria2 (aria2c) axel bash (/dev/tcp) busybox curl http (httpie) nushell (http) perl (libww) python/python3 soar wget
# If no supported downloaders are found (happens often), a fallback to bash+sed or nushell is used
set -eu
#shellcheck disable=SC2016,SC2059
main() {
# Enable Debug?
DEBUG="${DEBUG:-}"
if [ -n "$DEBUG" ]; then
set -x
fi
# Default
DEFAULT_VERSION="latest"
SOAR_VERSION="${SOAR_VERSION:-$DEFAULT_VERSION}"
# ASCII Colors
RED="\033[0;31m"
GREEN="\033[0;32m"
BLUE="\033[0;34m"
YELLOW="\033[0;33m"
RESET="\033[0m"
# Check if running as root
IS_ROOT=""
if [ "$(id -u)" = "0" ]; then
IS_ROOT="1"
printf "${GREEN}ⓘ Running as root${RESET}\n"
fi
# Refresh command -v
if command -v hash >/dev/null 2>&1; then
hash -r >/dev/null 2>&1
fi
# Determine BIN_DIR for installation
BIN_DIR=""
if [ -n "${SOAR_INSTALL_DIR-}" ]; then
if [ -d "$SOAR_INSTALL_DIR" ] && [ -w "$SOAR_INSTALL_DIR" ]; then
BIN_DIR="$SOAR_INSTALL_DIR"
else
printf "${RED}✗ Error: SOAR_INSTALL_DIR ${BLUE}($SOAR_INSTALL_DIR)${RED} is not writable or doesn't exist${RESET}\n" >&2
exit 1
fi
elif [ -n "${INSTALL_DIR-}" ]; then
if [ -d "$INSTALL_DIR" ] && [ -w "$INSTALL_DIR" ]; then
BIN_DIR="$INSTALL_DIR"
else
printf "${RED}✗ Error: INSTALL_DIR ${BLUE}($INSTALL_DIR)${RED} is not writable or doesn't exist${RESET}\n" >&2
exit 1
fi
elif [ -n "$IS_ROOT" ]; then
BIN_DIR="/usr/local/bin"
elif [ -n "$HOME" ]; then
BIN_DIR="$HOME/.local/bin"
fi
if [ -z "$BIN_DIR" ]; then
printf "${RED}✗ Error: Could not determine installation directory${RESET}\n" >&2
exit 1
fi
# Check for a downloader, sorted by sanest choice
check_download_tool() {
if command -v curl >/dev/null 2>&1; then
printf "curl -fSL -o"
return 0
elif command -v wget >/dev/null 2>&1; then
printf "wget -O"
return 0
elif command -v soar >/dev/null 2>&1; then
printf "soar dl -o"
return 0
elif command -v aria2 >/dev/null 2>&1; then
printf "aria2 -o"
return 0
elif command -v aria2c >/dev/null 2>&1; then
printf "aria2c -o"
return 0
elif command -v axel >/dev/null 2>&1; then
printf "axel -o"
return 0
elif command -v http >/dev/null 2>&1; then
printf "http -Fdm -o"
return 0
elif command -v nu >/dev/null 2>&1; then
printf "NU_HTTP"
return 0
elif command -v GET >/dev/null 2>&1; then
printf "PERL_GET"
return 0
elif command -v python >/dev/null 2>&1; then
printf "PYTHON_GET"
return 0
elif command -v python3 >/dev/null 2>&1; then
printf "PYTHON3_GET"
return 0
elif command -v busybox >/dev/null 2>&1; then
printf "busybox wget --no-check-certificate -O"
return 0
elif command -v bash >/dev/null 2>&1 && command -v sed >/dev/null 2>&1; then
printf "BASH_DEV_TCP"
return 0
else
printf "${RED}✗ Error: Could not find a downloader (curl, wget, aria2, axel, httpie, perl, python, busybox).${RESET}\n" >&2
return 1
fi
}
# Function to download and install
install_soar() {
DOWNLOAD_TOOL=""
if ! DOWNLOAD_TOOL=$(check_download_tool); then
exit 1
fi
mkdir -p "$BIN_DIR" >/dev/null 2>&1
if [ ! -d "$BIN_DIR" ] || [ ! -w "$BIN_DIR" ]; then
printf "${RED}✗ Error: ${BLUE}$BIN_DIR${RED} is not writable or doesn't exist${RESET}\n" >&2
exit 1
fi
INSTALL_PATH="$BIN_DIR"
# Detect architecture
ARCH=$(uname -m)
case "$ARCH" in
aarch64)
ARCH="aarch64"
;;
riscv64)
ARCH="riscv64"
;;
x86_64)
ARCH="x86_64"
;;
*)
printf "${RED}Error: Unsupported architecture: ${YELLOW}$ARCH${RESET}\n" >&2
exit 1
;;
esac
# Detect OS
OS=$(uname -s)
case "$OS" in
Linux)
OS="linux"
;;
*)
printf "${RED}Error: Unsupported operating system: ${YELLOW}$OS${RESET}\n" >&2
printf "${RED}Only Linux is currently supported${RESET}\n" >&2
exit 1
;;
esac
# Get latest release URL
printf "Downloading Soar..."
case "$SOAR_VERSION" in
*nightly*)
RELEASE_URL="https://github.com/pkgforge/soar/releases/download/nightly/soar-$ARCH-$OS"
;;
*latest*)
RELEASE_URL="https://github.com/pkgforge/soar/releases/latest/download/soar-$ARCH-$OS"
;;
*)
RELEASE_URL="https://github.com/pkgforge/soar/releases/download/v$SOAR_VERSION/soar-$ARCH-$OS"
;;
esac
printf " <== $RELEASE_URL\n"
# Download and install
if [ "$DOWNLOAD_TOOL" = "NU_HTTP" ]; then
printf "[+] Using HTTP (nushell)\n"
RELEASE_URL="http://http.pkgforge.dev/$RELEASE_URL" INSTALL_PATH="$INSTALL_PATH" nu --no-config-file -c \
'http get --redirect-mode follow --insecure --raw $env.RELEASE_URL | save -f ($env.INSTALL_PATH + "/soar")'
printf "\n"
elif [ "$DOWNLOAD_TOOL" = "PERL_GET" ]; then
printf "[+] Using GET\n"
GET "$RELEASE_URL" >"$INSTALL_PATH/soar"
printf "\n"
elif [ "$DOWNLOAD_TOOL" = "PYTHON_GET" ]; then
printf "[+] Using python -c\n"
python -c "import urllib.request; urllib.request.urlretrieve('$RELEASE_URL', '$INSTALL_PATH/soar')"
printf "\n"
elif [ "$DOWNLOAD_TOOL" = "PYTHON3_GET" ]; then
printf "[+] Using python3 -c\n"
python3 -c "import urllib.request; urllib.request.urlretrieve('$RELEASE_URL', '$INSTALL_PATH/soar')"
printf "\n"
elif [ "$DOWNLOAD_TOOL" = "BASH_DEV_TCP" ]; then
printf "\n${YELLOW}⚠ Attempting to download using ${BLUE}Bash${YELLOW} (${GREEN}/dev/tcp${YELLOW}) over HTTP${RESET}\n" >&2
printf "${YELLOW}⚠ This is highly unreliable & may not Work${RESET}\n\n" >&2
RELEASE_URL="http://http.pkgforge.dev/$RELEASE_URL" INSTALL_PATH="$INSTALL_PATH" bash -c \
'
raw_http_get() {
#Get Input
url=$1
port=${2:-80}
#Actually Verify we are in bash
is_bash=0
[[ -n "${BASH}" ]] && is_bash=1
if [[ $is_bash -eq 0 ]]; then
(shopt -p >/dev/null 2>&1) && is_bash=1
fi
#Proceed
if [ $is_bash -eq 1 ]; then
#Parse Input
url=${url#http://}
url=${url#https://}
host=${url%%/*}
if [[ "$url" = "$host" ]]; then
path="/"
else
path="/${url#$host/}"
fi
#Download
exec 3<>/dev/tcp/$host/$port
echo -e "GET $path HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n" >&3
if command -v dd >/dev/null 2>&1; then
dd bs=1K <&3
elif command -v cat >/dev/null 2>&1; then
cat <&3
fi
exec 3>&-
else
echo "Error: No method available to make HTTP requests. Requires Bash with /dev/tcp" >&2
return 1
fi
}
raw_http_get "${RELEASE_URL}" > "${INSTALL_PATH}/soar"
if [[ -s "${INSTALL_PATH}/soar" ]]; then
#Removes HTTP Headers
sed "1,/^\r\{0,1\}$/d" -i "${INSTALL_PATH}/soar"
fi
'
printf "\n"
else
printf "[+] Using $DOWNLOAD_TOOL\n"
$DOWNLOAD_TOOL "$INSTALL_PATH/soar" "$RELEASE_URL"
fi
# Check
if [ ! -f "$INSTALL_PATH/soar" ]; then
if [ "$DOWNLOAD_TOOL" = "BASH_DEV_TCP" ]; then
printf "${RED}Error: Download failed.${YELLOW} Install ${BLUE}curl/wget${YELLOW} & try again${RESET}\n"
else
printf "${RED}Error: Download failed${RESET}\n"
fi
exit 1
fi
# Make executable
chmod +x "$INSTALL_PATH/soar"
# Check for valid elf in case sed failed
if [ "$DOWNLOAD_TOOL" = "BASH_DEV_TCP" ]; then
if ! "$INSTALL_PATH/soar" --version >/dev/null 2>&1; then
printf "${RED}Error: Failed to properly extract soar.${YELLOW} Install ${BLUE}curl/wget${YELLOW} & try again${RESET}\n"
rm -fv "$INSTALL_PATH/soar"
printf "\n"
exit 1
fi
fi
# Check & Print Docs
"$INSTALL_PATH/soar" --version || printf "${RED}Error: Failed to properly download soar${RESET}"
printf "\n${GREEN}✓ Soar has been installed to: ${BLUE}$INSTALL_PATH/soar${RESET}\n"
printf "${YELLOW}ⓘ Documentation: ${BLUE}https://soar.qaidvoid.dev${RESET}\n"
# Check if in PATH
if command -v expr >/dev/null 2>&1; then
if expr ":$PATH:" : ".*:$BIN_DIR:" >/dev/null ||
expr ":$PATH:" : ".*:$(expr "$BIN_DIR" : '\(.*\)/$'):" >/dev/null; then
:
else
printf "\n${YELLOW}⚠ ${BLUE}$INSTALL_PATH${RED} is NOT in your ${BLUE}PATH${RESET}\n"
printf "${YELLOW}ⓘ Put this in your ${BLUE}SHELL/Profile${YELLOW}:${RESET}\n"
printf "\n${GREEN} export PATH=\"\$PATH:$INSTALL_PATH\"${RESET}\n\n"
fi
else
printf "${YELLOW}ⓘ Make sure ${BLUE}$INSTALL_PATH${YELLOW} is in your ${BLUE}PATH.${RESET}\n"
fi
if [ -n "$IS_ROOT" ]; then
printf "${YELLOW}ⓘ To synchronize all repos, run: ${GREEN}soar sync --system${RESET}\n"
else
printf "${YELLOW}ⓘ To synchronize all repos, run: ${GREEN}soar sync${RESET}\n"
fi
# Check Current Config
if [ -n "$IS_ROOT" ]; then
SOAR_ENV_OUT="$($INSTALL_PATH/soar env --system 2>/dev/null)"
else
SOAR_ENV_OUT="$($INSTALL_PATH/soar env 2>/dev/null)"
fi
if [ -n "$SOAR_ENV_OUT" ]; then
if command -v awk >/dev/null 2>&1 && command -v expr >/dev/null 2>&1; then
SOAR_BIN_PATH="$(printf "$SOAR_ENV_OUT" | awk -F= '/^SOAR_BIN=/{print $2}')"
if [ -n "$SOAR_BIN_PATH" ]; then
if expr ":$PATH:" : ".*:$SOAR_BIN_PATH:" >/dev/null ||
expr ":$PATH:" : ".*:$(expr "$SOAR_BIN_PATH" : '\(.*\)/$'):" >/dev/null; then
:
else
printf "\n${YELLOW}⚠ ${BLUE}$SOAR_BIN_PATH${RED} is NOT in your ${BLUE}\$PATH${RESET}\n"
printf "${YELLOW}ⓘ Put this in your ${BLUE}SHELL/Profile${YELLOW}:${RESET}\n"
printf "\n${GREEN} export PATH=\"\$PATH:$INSTALL_PATH:$SOAR_BIN_PATH\"${RESET}\n\n"
fi
fi
fi
fi
# Print Current config
printf "\n${YELLOW}ⓘ Current Soar Configuration:${RESET}\n"
if [ -n "$IS_ROOT" ]; then
"$INSTALL_PATH/soar" env --system
else
"$INSTALL_PATH/soar" env
fi
}
# Run Installation
install_soar
# Disable Debug?
if [ -z "$DEBUG" ]; then
:
elif [ -n "$DEBUG" ]; then
set +x
fi
}
# Call main function
main