Last active
May 22, 2025 12:47
-
-
Save divyam234/56ab07642dc40b05bbfb95df87aab685 to your computer and use it in GitHub Desktop.
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
#!/bin/bash | |
set -euo pipefail | |
INSTALL_DIR="/usr/local" | |
GOROOT_VAL="${INSTALL_DIR}/go" | |
GOPATH_VAL="${HOME}/go" | |
GO_VERSION_INFO_URL="https://go.dev/VERSION?m=text" | |
GO_DOWNLOAD_URL_BASE="https://dl.google.com/go/" | |
PROFILE_FILES=() | |
GREEN='\033[0;32m' | |
YELLOW='\033[0;33m' | |
RED='\033[0;31m' | |
NC='\033[0m' # No Color | |
print_info() { echo -e "${GREEN}[INFO]${NC} $1" >&2; } | |
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" >&2; } | |
print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" >&2; } | |
print_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; } | |
SUDO_CMD="" | |
check_sudo() { | |
if [[ "$EUID" -ne 0 ]]; then | |
print_warning "This script will attempt to use sudo for operations requiring root privileges (e.g., writing to ${INSTALL_DIR})." | |
if command -v sudo &>/dev/null; then | |
SUDO_CMD="sudo" | |
print_info "Sudo will be used for privileged operations. You may be prompted for your password by those commands." | |
else | |
# If sudo command is not found, SUDO_CMD remains empty. | |
# Privileged operations will likely fail, and the script should error out at that point. | |
print_warning "sudo command not found. Installation to ${INSTALL_DIR} will likely fail without root privileges." | |
print_warning "Please run this script as root or install sudo if installation to system directories is desired." | |
fi | |
else | |
print_info "Running as root. Sudo not required for privileged operations." | |
SUDO_CMD="" | |
fi | |
} | |
OS="" | |
ARCH="" | |
get_os_arch() { | |
case "$(uname -s)" in | |
Linux*) OS='linux';; | |
Darwin*) OS='darwin';; | |
*) print_error "Unsupported OS: $(uname -s)";; | |
esac | |
case "$(uname -m)" in | |
x86_64) ARCH='amd64';; | |
arm64) ARCH='arm64';; | |
aarch64) ARCH='arm64';; | |
*) print_error "Unsupported architecture: $(uname -m)";; | |
esac | |
print_info "Detected OS: ${OS}, Architecture: ${ARCH}" | |
} | |
get_latest_go_version() { | |
local raw_version_output | |
if command -v curl &>/dev/null; then | |
raw_version_output=$(curl -sSL "${GO_VERSION_INFO_URL}") | |
elif command -v wget &>/dev/null; then | |
raw_version_output=$(wget -qO- "${GO_VERSION_INFO_URL}") | |
else | |
print_error "curl or wget is required to fetch the Go version. Please install one." | |
fi | |
local latest_version_name | |
latest_version_name=$(echo "${raw_version_output}" | head -n 1) # Take only the first line | |
if [[ -z "$latest_version_name" || ! "$latest_version_name" == go* ]]; then | |
print_error "Could not determine the latest Go version. Expected 'goX.Y.Z', got: '${latest_version_name}' (from raw output: '${raw_version_output}')" | |
fi | |
echo "${latest_version_name#go}" | |
} | |
get_current_go_version() { | |
local managed_go_exe="${GOROOT_VAL}/bin/go" | |
if [[ -x "$managed_go_exe" ]]; then | |
local version_string | |
version_string=$("$managed_go_exe" version 2>&1) | |
if [[ "$version_string" == *"go version go"* ]]; then | |
echo "$version_string" | awk '{print $3}' | sed 's/go//' | |
return 0 | |
fi | |
fi | |
echo "" | |
} | |
download_go() { | |
local version="$1" | |
local filename="go${version}.${OS}-${ARCH}.tar.gz" | |
local download_url="${GO_DOWNLOAD_URL_BASE}${filename}" | |
local temp_tarball | |
if ! temp_tarball=$(mktemp "/tmp/go_download_${version}_XXXXXX.tar.gz"); then | |
print_error "Failed to create temporary file for download (template: /tmp/go_download_${version}_XXXXXX.tar.gz)." | |
fi | |
if command -v curl &>/dev/null; then | |
if ! curl --fail -sL -o "${temp_tarball}" "${download_url}"; then | |
rm -f "${temp_tarball}" # Clean up failed download (no sudo needed for /tmp by default) | |
print_error "curl failed to download Go (URL: ${download_url})." | |
fi | |
elif command -v wget &>/dev/null; then | |
if ! wget -qO "${temp_tarball}" "${download_url}"; then | |
rm -f "${temp_tarball}" # Clean up | |
print_error "wget failed to download Go (URL: ${download_url})." | |
fi | |
else | |
rm -f "${temp_tarball}" # Clean up | |
print_error "curl or wget is required to download Go. Please install one." | |
fi | |
if [[ ! -s "${temp_tarball}" ]]; then | |
rm -f "${temp_tarball}" # Clean up | |
print_error "Failed to download Go tarball or downloaded file is empty." | |
fi | |
echo "${temp_tarball}" | |
} | |
install_go() { | |
local tarball_path="$1" | |
local filename | |
filename=$(basename "$tarball_path") | |
if [[ -d "${GOROOT_VAL}" ]]; then | |
print_warning "Existing Go installation found at ${GOROOT_VAL}. Removing it first." | |
if ! ${SUDO_CMD} rm -rf "${GOROOT_VAL}"; then | |
local err_msg="Failed to remove existing Go installation at ${GOROOT_VAL}" | |
if [[ -n "${SUDO_CMD}" ]]; then err_msg+=" using sudo."; else err_msg+=" (root privileges may be required)."; fi | |
print_error "${err_msg}" | |
fi | |
fi | |
if ! ${SUDO_CMD} mkdir -p "${INSTALL_DIR}"; then | |
local err_msg="Failed to create install directory ${INSTALL_DIR}" | |
if [[ -n "${SUDO_CMD}" ]]; then | |
err_msg+=" using sudo. Check sudo permissions and that the path is writable by root." | |
elif [[ "$EUID" -ne 0 ]]; then # Not root and SUDO_CMD is empty (sudo not found or not used) | |
err_msg+=". Root privileges (sudo) are likely required." | |
else # Running as root (EUID == 0) and mkdir failed | |
err_msg+=" (as root). Check path, permissions, and available space." | |
fi | |
print_error "${err_msg}" | |
fi | |
if ! ${SUDO_CMD} tar -C "${INSTALL_DIR}" -xzf "${tarball_path}"; then | |
local err_msg="Failed to extract Go tarball to ${INSTALL_DIR}" | |
if [[ -n "${SUDO_CMD}" ]]; then err_msg+=" using sudo."; else err_msg+=" (root privileges may be required)."; fi | |
print_error "${err_msg}" | |
fi | |
if [[ ! -x "${GOROOT_VAL}/bin/go" ]]; then | |
print_error "Go binary not found at ${GOROOT_VAL}/bin/go after extraction. Installation might have failed or GOROOT_VAL is incorrect." | |
fi | |
rm -f "${tarball_path}" | |
} | |
# Function to detect shell and identify profile files | |
detect_shell_and_profiles() { | |
local calling_shell_name="" | |
if ! calling_shell_name=$(basename "$(ps -p $PPID -o comm= 2>/dev/null)"); then | |
print_warning "Could not determine calling shell via PPID. Will try other methods." | |
calling_shell_name="" | |
fi | |
if [[ -z "$calling_shell_name" && -n "$SHELL" ]]; then | |
calling_shell_name=$(basename "$SHELL") | |
fi | |
if [[ -z "$calling_shell_name" ]]; then | |
print_warning "Fallback: Trying to determine current script's execution shell name." | |
if ! calling_shell_name=$(basename "$(ps -p $$ -o comm= 2>/dev/null)"); then | |
print_error "Critically unable to determine shell type. Cannot proceed with environment setup." | |
fi | |
if [[ -z "$calling_shell_name" ]]; then | |
print_error "Critically unable to determine shell type (empty from ps $$). Cannot proceed." | |
fi | |
fi | |
if [[ -z "${HOME}" || ! -d "${HOME}" ]]; then | |
print_error "\$HOME environment variable is not set or not a directory. Cannot determine profile file paths." | |
fi | |
PROFILE_FILES=() | |
local lower_shell_name="${calling_shell_name,,}" | |
if [[ "$lower_shell_name" == "bash" ]]; then | |
PROFILE_FILES+=("${HOME}/.bashrc") | |
if [[ -f "${HOME}/.bash_profile" ]]; then | |
PROFILE_FILES+=("${HOME}/.bash_profile") | |
fi | |
elif [[ "$lower_shell_name" == "zsh" ]]; then | |
local zdotdir="${ZDOTDIR:-$HOME}" | |
if [[ ! -d "$zdotdir" ]]; then | |
print_warning "ZDOTDIR ('${zdotdir}') is not a directory. Defaulting to ${HOME} for Zsh files." | |
zdotdir="${HOME}" | |
if [[ ! -d "$zdotdir" ]]; then | |
print_error "Cannot find a valid directory for Zsh profile files ('${zdotdir}' or '${HOME}')." | |
fi | |
fi | |
PROFILE_FILES+=("${zdotdir}/.zshrc") | |
if [[ -f "${zdotdir}/.zprofile" ]]; then | |
PROFILE_FILES+=("${zdotdir}/.zprofile") | |
fi | |
elif [[ "$lower_shell_name" == "fish" ]]; then | |
PROFILE_FILES+=("${HOME}/.config/fish/config.fish") | |
print_warning "Fish shell detected. The Go environment variables need to be set with Fish syntax." | |
print_warning "This script will add standard 'export' commands to '${HOME}/.config/fish/config.fish'." | |
print_warning "You may need to manually adjust it to use 'set -gx VAR val' and 'fish_add_path'." | |
elif [[ -f "${HOME}/.profile" ]]; then | |
PROFILE_FILES+=("${HOME}/.profile") | |
else | |
print_warning "Could not identify a specific profile for shell '${calling_shell_name}'. Will use/create ${HOME}/.profile." | |
PROFILE_FILES+=("${HOME}/.profile") | |
fi | |
if [[ ${#PROFILE_FILES[@]} -gt 0 ]]; then | |
PROFILE_FILES=($(echo "${PROFILE_FILES[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')) | |
fi | |
if [[ ${#PROFILE_FILES[@]} -eq 0 ]]; then | |
print_error "No suitable shell profile file(s) could be identified for '${calling_shell_name}'. Cannot set up environment variables." | |
fi | |
} | |
setup_env_vars() { | |
detect_shell_and_profiles | |
mkdir -p "${GOPATH_VAL}/bin" "${GOPATH_VAL}/pkg" "${GOPATH_VAL}/src" | |
local go_env_marker_start="# BEGIN GO ENV VARS (Managed by Go Installer Script)" | |
local go_env_marker_end="# END GO ENV VARS (Managed by Go Installer Script)" | |
local env_vars_block | |
read -r -d '' env_vars_block << EOM || true | |
${go_env_marker_start} | |
export GOROOT='${GOROOT_VAL}' | |
export GOPATH='${GOPATH_VAL}' | |
if [[ ":\$PATH:" != *":${GOROOT_VAL}/bin:"* ]]; then | |
export PATH="\$PATH:${GOROOT_VAL}/bin" | |
fi | |
if [[ ":\$PATH:" != *":${GOPATH_VAL}/bin:"* ]]; then | |
export PATH="\$PATH:${GOPATH_VAL}/bin" | |
fi | |
${go_env_marker_end} | |
EOM | |
for profile_file in "${PROFILE_FILES[@]}"; do | |
if [[ ! -f "$profile_file" ]]; then | |
print_warning "Profile file ${profile_file} not found. Creating it." | |
if ! touch "$profile_file"; then | |
print_error "Failed to create profile file ${profile_file}. Check permissions." | |
continue | |
fi | |
fi | |
local backup_file="${profile_file}.bak_go_install_$(date +%s)" | |
if ! cp "${profile_file}" "${backup_file}"; then | |
print_error "Failed to create backup of ${profile_file} to ${backup_file}. Skipping this file." | |
continue | |
fi | |
if grep -qF "${go_env_marker_start}" "${profile_file}"; then | |
if ! awk -v start="${go_env_marker_start}" -v end="${go_env_marker_end}" \ | |
'BEGIN {p=1} $0 == start {p=0; next} $0 == end {p=1; next} p {print}' \ | |
"${backup_file}" > "${profile_file}"; then | |
print_error "Failed to remove old Go env block from ${profile_file}. Restoring from ${backup_file}." | |
if ! mv "${backup_file}" "${profile_file}"; then | |
print_error "CRITICAL: Failed to restore ${profile_file} from ${backup_file}. Manual check required." | |
fi | |
continue | |
fi | |
fi | |
if ! echo -e "\n${env_vars_block}\n" >> "${profile_file}"; then | |
print_error "Failed to append Go env block to ${profile_file}. Restoring from ${backup_file}." | |
if ! mv "${backup_file}" "${profile_file}"; then | |
print_error "CRITICAL: Failed to restore ${profile_file} from ${backup_file}. Manual check required." | |
fi | |
continue | |
fi | |
rm -f "${backup_file}" | |
done | |
print_warning "IMPORTANT: For changes to take effect, you need to source your shell profile(s) or open a new terminal." | |
for profile_file in "${PROFILE_FILES[@]}"; do | |
print_warning " Example: source ${profile_file}" | |
done | |
export GOROOT="${GOROOT_VAL}" | |
export GOPATH="${GOPATH_VAL}" | |
if [[ ":$PATH:" != *":${GOROOT_VAL}/bin:"* ]]; then export PATH="$PATH:${GOROOT_VAL}/bin"; fi | |
if [[ ":$PATH:" != *":${GOPATH_VAL}/bin:"* ]]; then export PATH="$PATH:${GOPATH_VAL}/bin"; fi | |
} | |
main() { | |
check_sudo | |
get_os_arch | |
LATEST_GO_VERSION=$(get_latest_go_version) | |
if [[ -z "$LATEST_GO_VERSION" ]]; then print_error "Failed to get latest Go version."; fi | |
print_info "Latest available Go version: ${LATEST_GO_VERSION}" | |
CURRENT_GO_VERSION=$(get_current_go_version) | |
if [[ -z "$CURRENT_GO_VERSION" ]]; then | |
print_info "Go is not currently installed in ${GOROOT_VAL}." | |
read -r -p "Install Go version ${LATEST_GO_VERSION}? (y/N): " confirm | |
if [[ "${confirm,,}" == "y" || "${confirm,,}" == "yes" ]]; then | |
DOWNLOADED_TARBALL_PATH=$(download_go "${LATEST_GO_VERSION}") | |
install_go "${DOWNLOADED_TARBALL_PATH}" | |
setup_env_vars | |
print_success "Go ${LATEST_GO_VERSION} installed." | |
else | |
print_info "Installation cancelled."; exit 0 | |
fi | |
else | |
print_info "Current Go version at ${GOROOT_VAL}: ${CURRENT_GO_VERSION}" | |
if [[ "$CURRENT_GO_VERSION" == "$LATEST_GO_VERSION" ]]; then | |
print_success "Go is up to date (Version: ${CURRENT_GO_VERSION})." | |
setup_env_vars | |
else | |
print_warning "Newer Go version available: ${LATEST_GO_VERSION} (current: ${CURRENT_GO_VERSION})." | |
read -r -p "Update to Go ${LATEST_GO_VERSION}? (y/N): " confirm | |
if [[ "${confirm,,}" == "y" || "${confirm,,}" == "yes" ]]; then | |
DOWNLOADED_TARBALL_PATH=$(download_go "${LATEST_GO_VERSION}") | |
install_go "${DOWNLOADED_TARBALL_PATH}" | |
setup_env_vars | |
print_success "Go updated to ${LATEST_GO_VERSION}." | |
else | |
print_info "Update cancelled. Verifying env vars for current version ${CURRENT_GO_VERSION}..." | |
setup_env_vars | |
fi | |
fi | |
fi | |
if command -v go &>/dev/null && [[ -x "${GOROOT_VAL}/bin/go" ]]; then | |
effective_go_version=$("${GOROOT_VAL}/bin/go" version 2>&1) | |
print_success "Go installation verified: ${effective_go_version}" | |
else | |
print_warning "Go command not immediately available or '${GOROOT_VAL}/bin/go' check failed." | |
print_warning "This is often normal. Please source your profile file(s) or open a new terminal." | |
fi | |
print_info "Script finished." | |
} | |
( main "$@" ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment