#!/bin/bash
apply_http_proxy() {
if [ -z "${WORK_HOME:-}" ]; then
return 0
fi
local USER_CONFIG="${WORK_HOME}/user_config.sh"
if [ -f "$USER_CONFIG" ]; then
. "$USER_CONFIG" 2>/dev/null || true
if [ -n "${HTTP_PROXY:-}" ]; then
export HTTP_PROXY
export http_proxy="$HTTP_PROXY"
fi
if [ -n "${HTTPS_PROXY:-}" ]; then
export HTTPS_PROXY
export https_proxy="$HTTPS_PROXY"
fi
fi
}
get_uv_index_args_from_user_config() {
UV_INDEX_ARGS=()
if [ -z "${WORK_HOME:-}" ]; then
return 0
fi
local user_cfg uv_idx uv_trusted_hosts host
user_cfg="${WORK_HOME}/user_config.sh"
if [ ! -f "$user_cfg" ]; then
return 0
fi
source "$user_cfg" 2>/dev/null || true
if [ -n "${UV_INDEX:-}" ]; then
uv_idx="$(echo "$UV_INDEX" | xargs)"
if [ -n "$uv_idx" ]; then
UV_INDEX_ARGS=(--index "$uv_idx")
log "INFO" "uv uses --index from user_config.sh: $uv_idx"
fi
fi
if [ -n "${UV_TRUSTED_HOST:-}" ]; then
uv_trusted_hosts="${UV_TRUSTED_HOST//,/ }"
for host in $uv_trusted_hosts; do
host="$(echo "$host" | xargs)"
if [ -n "$host" ]; then
UV_INDEX_ARGS+=(--allow-insecure-host "$host")
fi
done
if [ ${#UV_INDEX_ARGS[@]} -gt 0 ]; then
log "INFO" "uv uses --allow-insecure-host from user_config.sh: ${UV_TRUSTED_HOST}"
fi
fi
}
get_user_config_value() {
local key="${1:-}"
local user_cfg="${2:-}"
local line val
if [ -z "$key" ] || [[ ! "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
return 1
fi
if [ -z "$user_cfg" ]; then
if [ -n "${WORK_HOME:-}" ]; then
user_cfg="${WORK_HOME}/user_config.sh"
else
user_cfg="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/user_config.sh"
fi
fi
if [ ! -f "$user_cfg" ]; then
printf '%s\n' ""
return 0
fi
line=$(grep -E "^[[:space:]]*${key}=" "$user_cfg" 2>/dev/null | head -n 1)
if [ -z "$line" ]; then
printf '%s\n' ""
return 0
fi
val=$(echo "$line" | cut -d'=' -f2- | tr -d '"' | tr -d "'" | xargs)
printf '%s\n' "$val"
return 0
}
load_db_host_port_from_user_config() {
DB_HOST="$(get_user_config_value DB_HOST)"
local _configured_db_port
_configured_db_port="$(get_user_config_value DB_PORT)"
if [ -z "$DB_HOST" ]; then
DB_HOST="127.0.0.1"
fi
if [[ "$_configured_db_port" =~ ^[0-9]+$ ]] && [ "$_configured_db_port" -ge 1 ] && [ "$_configured_db_port" -le 65535 ]; then
DB_PORT="$_configured_db_port"
else
DB_PORT="3306"
fi
}
detect_os() {
OS_TYPE="unknown"
PKG_MANAGER="unknown"
OS_ID="unknown"
OS_VERSION="unknown"
if [ -f /etc/os-release ]; then
. /etc/os-release
OS_ID="${ID:-unknown}"
OS_VERSION="${VERSION_ID:-unknown}"
case "${ID:-}" in
ubuntu|debian)
OS_TYPE="debian"
if command -v apt &> /dev/null; then
PKG_MANAGER="apt"
elif command -v apt-get &> /dev/null; then
PKG_MANAGER="apt-get"
else
PKG_MANAGER="apt"
fi
;;
centos|rhel|rocky|almalinux)
OS_TYPE="rhel"
if command -v dnf &> /dev/null; then
PKG_MANAGER="dnf"
elif command -v yum &> /dev/null; then
PKG_MANAGER="yum"
else
PKG_MANAGER="yum"
fi
;;
fedora)
OS_TYPE="fedora"
if command -v dnf &> /dev/null; then
PKG_MANAGER="dnf"
else
PKG_MANAGER="dnf"
fi
;;
opensuse*|sles)
OS_TYPE="suse"
if command -v zypper &> /dev/null; then
PKG_MANAGER="zypper"
else
PKG_MANAGER="zypper"
fi
;;
arch|manjaro)
OS_TYPE="arch"
if command -v pacman &> /dev/null; then
PKG_MANAGER="pacman"
else
PKG_MANAGER="pacman"
fi
;;
*)
case "${ID_LIKE:-}" in
*debian*)
OS_TYPE="debian"
if command -v apt &> /dev/null; then
PKG_MANAGER="apt"
elif command -v apt-get &> /dev/null; then
PKG_MANAGER="apt-get"
else
PKG_MANAGER="apt"
fi
;;
*rhel*|*fedora*)
OS_TYPE="rhel"
if command -v dnf &> /dev/null; then
PKG_MANAGER="dnf"
elif command -v yum &> /dev/null; then
PKG_MANAGER="yum"
else
PKG_MANAGER="yum"
fi
;;
*)
OS_TYPE="unknown"
;;
esac
;;
esac
else
if command -v apt &> /dev/null || command -v apt-get &> /dev/null; then
OS_TYPE="debian"
if command -v apt &> /dev/null; then
PKG_MANAGER="apt"
else
PKG_MANAGER="apt-get"
fi
elif command -v dnf &> /dev/null; then
OS_TYPE="rhel"
PKG_MANAGER="dnf"
elif command -v yum &> /dev/null; then
OS_TYPE="rhel"
PKG_MANAGER="yum"
elif command -v zypper &> /dev/null; then
OS_TYPE="suse"
PKG_MANAGER="zypper"
elif command -v pacman &> /dev/null; then
OS_TYPE="arch"
PKG_MANAGER="pacman"
fi
fi
if [ "${DEBUG:-0}" = "1" ]; then
echo "Detected: OS_ID=$OS_ID OS_VERSION=$OS_VERSION OS_TYPE=$OS_TYPE PKG_MANAGER=$PKG_MANAGER"
fi
}
get_install_command() {
local packages="$*"
if [ "$OS_TYPE" = "unknown" ] || [ "$PKG_MANAGER" = "unknown" ]; then
detect_os
fi
case "$PKG_MANAGER" in
apt|apt-get)
echo "sudo $PKG_MANAGER update -y && sudo $PKG_MANAGER install -y $packages"
;;
dnf)
echo "sudo dnf install -y $packages"
;;
yum)
echo "sudo yum install -y $packages"
;;
zypper)
echo "sudo zypper install -y $packages"
;;
pacman)
echo "sudo pacman -S --noconfirm $packages"
;;
*)
echo ""
return 1
;;
esac
}
install_packages() {
local packages="$*"
if [ -z "$packages" ]; then
return 1
fi
if [ "$OS_TYPE" = "unknown" ] || [ "$PKG_MANAGER" = "unknown" ]; then
detect_os
fi
case "$PKG_MANAGER" in
apt|apt-get)
sudo $PKG_MANAGER update -y
sudo $PKG_MANAGER install -y $packages
;;
dnf)
sudo dnf install -y $packages
;;
yum)
sudo yum install -y $packages
;;
zypper)
sudo zypper install -y $packages
;;
pacman)
sudo pacman -S --noconfirm $packages
;;
*)
echo "Error: Unsupported package manager: $PKG_MANAGER"
return 1
;;
esac
}
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() {
local LEVEL=$1
shift
local MSG="$*"
local TIMESTAMP
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
local LOG_MSG="[${TIMESTAMP}] [${LEVEL}] ${MSG}"
case "$LEVEL" in
ERROR)
echo -e "${RED}${LOG_MSG}${NC}" >&2
;;
WARN)
echo -e "${YELLOW}${LOG_MSG}${NC}"
;;
SUCCESS)
echo -e "${GREEN}${LOG_MSG}${NC}"
;;
INFO)
echo -e "${BLUE}${LOG_MSG}${NC}"
;;
*)
echo "${LOG_MSG}"
;;
esac
echo "${LOG_MSG}" >> "$LOG_FILE" 2>/dev/null || true
}
save_progress() {
local STEP=$1
echo "$STEP" > "$PROGRESS_FILE" 2>/dev/null || true
log "INFO" "Progress saved: $STEP"
}
read_progress() {
if [ -f "$PROGRESS_FILE" ]; then
cat "$PROGRESS_FILE" 2>/dev/null || echo ""
else
echo ""
fi
}
clear_progress() {
rm -f "$PROGRESS_FILE" 2>/dev/null || true
}
should_skip_step() {
local current_step="$1"
local last_progress="$2"
if [[ -z "$last_progress" ]]; then
return 1
fi
local current_index=-1
local last_index=-1
local i
for i in "${!INSTALL_STEPS[@]}"; do
if [[ "${INSTALL_STEPS[$i]}" == "$current_step" ]]; then
current_index=$i
fi
if [[ "${INSTALL_STEPS[$i]}" == "$last_progress" ]]; then
last_index=$i
fi
done
if [[ $current_index -eq -1 ]] || [[ $last_index -eq -1 ]]; then
return 1
fi
if [[ $last_index -ge $current_index ]]; then
return 0
else
return 1
fi
}
retry_execute() {
local MAX_RETRIES=${1:-3}
local RETRY_DELAY=${2:-5}
local STEP_NAME="$3"
shift 3
local COMMAND="$*"
local ATTEMPT=1
local LAST_ERROR=""
while [ $ATTEMPT -le $MAX_RETRIES ]; do
log "INFO" "[Attempt $ATTEMPT/$MAX_RETRIES] Executing: $STEP_NAME"
if eval "$COMMAND" 2>&1; then
log "SUCCESS" "$STEP_NAME completed successfully"
return 0
else
LAST_ERROR=$?
log "WARN" "$STEP_NAME failed (exit code: $LAST_ERROR), retrying in ${RETRY_DELAY}s..."
if [ $ATTEMPT -lt $MAX_RETRIES ]; then
sleep $RETRY_DELAY
fi
ATTEMPT=$((ATTEMPT + 1))
fi
done
log "ERROR" "$STEP_NAME failed after $MAX_RETRIES attempts"
return $LAST_ERROR
}
error_exit() {
local MSG="$1"
local RECOVERY_HINT="${2:-}"
log "ERROR" "========================================="
log "ERROR" "Deployment failed: $MSG"
if [ -n "$RECOVERY_HINT" ]; then
log "ERROR" ""
log "ERROR" "Recovery suggestion:"
echo -e "${YELLOW}$RECOVERY_HINT${NC}" >&2
fi
log "ERROR" "========================================="
log "ERROR" "See log for details: $LOG_FILE"
log "ERROR" "Progress saved; re-run the script to resume deployment"
exit 1
}
load_environments() {
if [ -s "$HOME/.nvm/nvm.sh" ]; then
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 2>/dev/null || true
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" 2>/dev/null || true
fi
if [ -s "$HOME/miniconda3/etc/profile.d/conda.sh" ]; then
source "$HOME/miniconda3/etc/profile.d/conda.sh" 2>/dev/null || true
elif [ -s "$HOME/anaconda3/etc/profile.d/conda.sh" ]; then
source "$HOME/anaconda3/etc/profile.d/conda.sh" 2>/dev/null || true
elif [ -s "/opt/conda/etc/profile.d/conda.sh" ]; then
source "/opt/conda/etc/profile.d/conda.sh" 2>/dev/null || true
fi
if [ -d "$HOME/.local/bin" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
fi
if [ -d "/root/.local/bin" ] && [[ ":$PATH:" != *":/root/.local/bin:"* ]]; then
export PATH="/root/.local/bin:$PATH"
fi
}
check_command() {
local CMD="$1"
load_environments
if ! command -v "$CMD" &> /dev/null; then
case "$CMD" in
node|npm)
if [ -s "$HOME/.nvm/nvm.sh" ]; then
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" 2>/dev/null || true
fi
;;
uv)
if [ -f "$HOME/.local/bin/uv" ] && [[ ":$PATH:" != *":$HOME/.local/bin:"* ]]; then
export PATH="$HOME/.local/bin:$PATH"
elif [ -f "/root/.local/bin/uv" ] && [[ ":$PATH:" != *":/root/.local/bin:"* ]]; then
export PATH="/root/.local/bin:$PATH"
fi
;;
python3.11)
if [ -s "$HOME/miniconda3/envs/py311/bin/python3.11" ]; then
load_environments
if [ ! -f "/usr/local/bin/python3.11" ] && [ -f "$HOME/miniconda3/envs/py311/bin/python3.11" ]; then
log "WARN" "python3.11 is in conda env but symlink not found"
fi
fi
;;
esac
if ! command -v "$CMD" &> /dev/null; then
error_exit "Required command '$CMD' not found, please install it first" \
"Install $CMD manually and re-run the script"
fi
fi
}
check_file() {
local FILE=$1
local CREATE_IF_NOT_EXIST=${2:-false}
if [ ! -f "$FILE" ]; then
if [ "$CREATE_IF_NOT_EXIST" = true ]; then
log "WARN" "File $FILE not found, creating empty file"
mkdir -p "$(dirname "$FILE")" && touch "$FILE" || {
error_exit "Failed to create $FILE" \
"Check directory permissions: $(dirname "$FILE")"
}
else
error_exit "File $FILE not found, cannot continue" \
"Ensure code is fetched or create the file manually"
fi
fi
}
check_dir() {
local DIR=$1
if [ ! -d "$DIR" ]; then
error_exit "Directory $DIR not found, cannot continue" \
"Check code fetch or create directory: mkdir -p $DIR"
fi
}
check_port() {
local name="$1"
local val="$2"
if ! [[ "$val" =~ ^[0-9]+$ ]] || [ "$val" -lt 1 ] || [ "$val" -gt 65535 ]; then
error_exit "Option --${name} must be an integer 1-65535, got: $val"
fi
}
check_script_permission() {
local SCRIPT=$1
if [ -f "$SCRIPT" ] && [ ! -x "$SCRIPT" ]; then
log "WARN" "Script $SCRIPT missing execute permission, fixing..."
chmod +x "$SCRIPT" || {
log "WARN" "Cannot add execute permission for $SCRIPT, will run with bash"
return 1
}
log "SUCCESS" "Execute permission added for $SCRIPT"
fi
}