Skip to content

Instantly share code, notes, and snippets.

@divyam234
Last active May 22, 2025 12:47
Show Gist options
  • Save divyam234/56ab07642dc40b05bbfb95df87aab685 to your computer and use it in GitHub Desktop.
Save divyam234/56ab07642dc40b05bbfb95df87aab685 to your computer and use it in GitHub Desktop.
#!/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