Skip to content

Instantly share code, notes, and snippets.

@weehongkoh
Last active March 24, 2026 02:17
Show Gist options
  • Select an option

  • Save weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82 to your computer and use it in GitHub Desktop.

Select an option

Save weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82 to your computer and use it in GitHub Desktop.
Download Latest Zsh, Go, Python, GCC and .NET with Bash Script

Bash Script Installer for Zsh, Go, Python, GNU GCC and .NET

The "Bash Script Installer" simplifies the setup of Zsh, Go, Python, GNU GCC, and .NET on Unix-based systems, providing a user-friendly, automated installation process for developers.

How to use

Copy and paste to the terminal

# Download ZSH
bash -c "$(curl -fsSL https://gist.githubusercontent.com/weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82/raw/zsh_installer.sh)"
bash -c "$(curl -fsSL https://gist.githubusercontent.com/weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82/raw/zsh_ubuntu.sh)"

# Download ZSH - MacOS
zsh -c "$(curl -fsSL https://gist.githubusercontent.com/weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82/raw/zsh_macos.sh)"

# Download Latest Go
bash -c "$(curl -fsSL https://gist.githubusercontent.com/weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82/raw/go_installer.sh)"

# Download Latest Python 3
bash -c "$(curl -fsSL https://gist.githubusercontent.com/weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82/raw/python_installer.sh)"

# Download GNU GCC
bash -c "$(curl -fsSL https://gist.githubusercontent.com/weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82/raw/gcc_installer.sh)"

# Download GNU Make
bash -c "$(curl -fsSL https://gist.githubusercontent.com/weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82/raw/make_installer.sh)"

# Download .NET
bash -c "$(curl -fsSL https://gist.githubusercontent.com/weehongkoh/e56d8161fa47e3ac8416ad8340ee3f82/raw/dotnet_installer.sh)"
#!/bin/bash
# .NET SDK Installation Wrapper
# Wraps the official Microsoft dotnet-install.sh script
# Reference: https://learn.microsoft.com/en-us/dotnet/core/install/linux-scripted-manual#scripted-install
set -euo pipefail
# Configuration
readonly SCRIPT_NAME="$(basename "$0")"
readonly TEMP_DIR=$(mktemp -d)
readonly MS_SCRIPT_URL="https://dot.net/v1/dotnet-install.sh"
# Colors (Defined using printf to work with 'cat' and standard 'echo')
readonly RED=$(printf '\033[0;31m')
readonly GREEN=$(printf '\033[0;32m')
readonly YELLOW=$(printf '\033[1;33m')
readonly BLUE=$(printf '\033[0;34m')
readonly PURPLE=$(printf '\033[0;35m')
readonly NC=$(printf '\033[0m') # No Color
# Global variables
DOTNET_VERSION=""
CHANNEL="STS" # Default to Standard Term Support (Latest release)
INSTALL_DIR="$HOME/.dotnet"
ARCHITECTURE="" # Let official script auto-detect unless specified
FORCE_INSTALL=false
SKIP_CLEANUP=false
CLEAN_INSTALL=false
DRY_RUN=false
# Logging functions (Removed -e as variables now contain real escape codes)
log_info() { echo "${BLUE}[INFO]${NC} $1" >&2; }
log_success() { echo "${GREEN}[SUCCESS]${NC} $1" >&2; }
log_warn() { echo "${YELLOW}[WARN]${NC} $1" >&2; }
log_error() { echo "${RED}[ERROR]${NC} $1" >&2; }
log_step() { echo "${PURPLE}[STEP]${NC} $1" >&2; }
# Cleanup
cleanup() {
local exit_code=$?
if [[ "$SKIP_CLEANUP" != "true" ]]; then
rm -rf "$TEMP_DIR"
fi
exit $exit_code
}
trap cleanup EXIT
# Show usage
show_usage() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]
Install .NET SDK using the official Microsoft script.
Options:
-h, --help Show this help message
-v, --version VERSION Install specific version (e.g., 8.0.100).
If omitted, installs latest STS (Standard Term Support).
-c, --channel CHANNEL Specify channel: STS (default), LTS, or specific (e.g., 8.0).
-d, --dir DIRECTORY Installation directory (default: ~/.dotnet)
-a, --arch ARCH Architecture: x64, arm64 (auto-detected if omitted)
--clean Remove existing installation directory before installing
--dry-run Show what would happen without installing
Examples:
$SCRIPT_NAME # Install latest version
$SCRIPT_NAME -v 8.0.100 # Install specific SDK version
$SCRIPT_NAME --channel LTS # Install latest Long Term Support
$SCRIPT_NAME -d /opt/dotnet # Install to custom location
EOF
}
# Parse arguments
parse_arguments() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) show_usage; exit 0 ;;
-v|--version) DOTNET_VERSION="$2"; shift 2 ;;
-c|--channel) CHANNEL="$2"; shift 2 ;;
-d|--dir) INSTALL_DIR="$2"; shift 2 ;;
-a|--arch) ARCHITECTURE="$2"; shift 2 ;;
--clean) CLEAN_INSTALL=true; shift ;;
--dry-run) DRY_RUN=true; shift ;;
*) log_error "Unknown option: $1"; show_usage; exit 1 ;;
esac
done
}
# Check requirements
check_requirements() {
local cmds=("curl" "bash")
for cmd in "${cmds[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
log_error "Required command not found: $cmd"
return 1
fi
done
}
# Clean existing installation
clean_existing_installation() {
if [[ "$CLEAN_INSTALL" == "true" ]] && [[ -d "$INSTALL_DIR" ]]; then
log_step "Removing existing installation at $INSTALL_DIR..."
if [[ "$DRY_RUN" == "true" ]]; then
log_info "[DRY-RUN] Would remove $INSTALL_DIR"
else
rm -rf "$INSTALL_DIR"
log_success "Cleaned $INSTALL_DIR"
fi
fi
}
# Download and run official script
install_dotnet() {
log_step "Preparing official installer..."
local install_script="$TEMP_DIR/dotnet-install.sh"
# 1. Download official script
log_info "Downloading $MS_SCRIPT_URL..."
if ! curl -fL -o "$install_script" "$MS_SCRIPT_URL"; then
log_error "Failed to download official installer script."
return 1
fi
chmod +x "$install_script"
# 2. Construct arguments
local args=("--install-dir" "$INSTALL_DIR")
# If explicit version is set, use it. Otherwise use Channel logic.
if [[ -n "$DOTNET_VERSION" ]]; then
args+=("--version" "$DOTNET_VERSION")
log_info "Target Version: $DOTNET_VERSION"
else
args+=("--channel" "$CHANNEL")
log_info "Target Channel: $CHANNEL"
fi
if [[ -n "$ARCHITECTURE" ]]; then
args+=("--architecture" "$ARCHITECTURE")
fi
if [[ "$DRY_RUN" == "true" ]]; then
args+=("--dry-run")
fi
# 3. Execute
log_step "Executing official installer..."
log_info "Running: ./dotnet-install.sh ${args[*]}"
if ! "$install_script" "${args[@]}"; then
log_error "Official installer script failed."
return 1
fi
log_success "Installer finished successfully."
}
# Configure environment (Official script DOES NOT do this)
configure_environment() {
if [[ "$DRY_RUN" == "true" ]]; then return 0; fi
log_step "Configuring shell environment..."
local shell_configs=("$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile")
local path_export="export PATH=\"\$PATH:$INSTALL_DIR\""
local root_export="export DOTNET_ROOT=\"$INSTALL_DIR\""
for config_file in "${shell_configs[@]}"; do
if [[ -f "$config_file" ]]; then
# Only add if not already present
if ! grep -q "DOTNET_ROOT" "$config_file"; then
log_info "Updating $config_file..."
{
echo ""
echo "# .NET SDK configuration (added by $SCRIPT_NAME)"
echo "$root_export"
echo "$path_export"
} >> "$config_file"
else
log_info ".NET already configured in $config_file"
fi
fi
done
# Export for current session immediate use
export DOTNET_ROOT="$INSTALL_DIR"
export PATH="$PATH:$INSTALL_DIR"
}
# Verify
verify_installation() {
if [[ "$DRY_RUN" == "true" ]]; then return 0; fi
log_step "Verifying installation..."
local dotnet_cmd="$INSTALL_DIR/dotnet"
if [[ ! -x "$dotnet_cmd" ]]; then
log_error ".NET executable not found at $dotnet_cmd"
return 1
fi
local version_output
version_output=$("$dotnet_cmd" --version)
log_success "Detected .NET Version: $version_output"
}
show_completion() {
if [[ "$DRY_RUN" == "true" ]]; then
log_info "Dry run complete."
return 0
fi
cat << EOF
${GREEN}╔══════════════════════════════════════════════════════════════╗
║ .NET SDK Installation Complete! ║
╚══════════════════════════════════════════════════════════════╝${NC}
${YELLOW}Next Steps:${NC}
1. Restart your terminal logic:
${BLUE}source ~/.bashrc${NC} (or ~/.zshrc)
2. Or manually set path for this session:
${BLUE}export PATH="\$PATH:$INSTALL_DIR"${NC}
3. Verify:
${BLUE}dotnet --info${NC}
EOF
}
# Main
main() {
parse_arguments "$@"
check_requirements
clean_existing_installation
install_dotnet
configure_environment
verify_installation
show_completion
}
main "$@"
#!/bin/bash
set -e
sudo apt-get update -y && sudo apt-get upgrade -y
sudo apt-get install -y bzip2 curl
# Get latest GCC version string from your service
version="$(curl -fsSL https://version-release-watchdog.onrender.com/gcc)"
if [[ -z "$version" ]]; then
echo "Failed to get GCC version from watchdog service."
exit 1
fi
echo "Latest GCC version from watchdog: $version"
url="https://mirrorservice.org/sites/sourceware.org/pub/gcc/releases/gcc-${version}/gcc-${version}.tar.gz"
echo "Downloading: $url"
# Quote the URL to avoid word-splitting
curl -fLO "$url"
tar -xzf "gcc-${version}.tar.gz"
cd "gcc-${version}"
./contrib/download_prerequisites
cd ..
mkdir -p build
cd build
../gcc-"${version}"/configure --enable-languages=c,c++ --disable-multilib
make -j"$(nproc)"
sudo make install
cd ..
rm -rf build "gcc-${version}" "gcc-${version}.tar.gz"
echo "✅ GCC ${version} installed successfully."
#!/bin/bash
# 1. Detect OS and Architecture
os=$(uname -s | tr '[:upper:]' '[:lower:]')
arch=$(uname -m)
case $arch in
x86_64) arch="amd64" ;;
aarch64|arm64) arch="arm64" ;;
i386|i686) arch="386" ;;
*) echo "Unsupported architecture: $arch"; exit 1 ;;
esac
# 2. Determine Shell Config File
if [[ "$SHELL" == */zsh ]]; then
shell_profile="$HOME/.zshrc"
shell_name="zsh"
elif [[ "$SHELL" == */bash ]]; then
shell_profile="$HOME/.bashrc"
shell_name="bash"
else
shell_profile="$HOME/.profile"
shell_name="sh"
fi
# 3. Fetch Latest Version via JSON API (Bypassing plain text version endpoint)
echo "Fetching latest stable Go version..."
go_version=$(curl -s https://go.dev/dl/?mode=json | grep -oE '"version": "go[0-9.]+"' | head -1 | cut -d'"' -f4)
if [ -z "$go_version" ]; then
echo "Error: Could not detect Go version."
exit 1
fi
go_package="$go_version.$os-$arch.tar.gz"
echo "Target detected: $go_version for $os/$arch"
# 4. Handle GOPATH
if [ -z "$GOPATH" ]; then
read -p "Enter GOPATH (Default: $HOME/go): " go_path
go_path=${go_path:-"$HOME/go"}
else
go_path=$GOPATH
fi
# 5. Download and Install
echo "Downloading $go_package..."
temp_dir=$(mktemp -d)
curl -sL "https://go.dev/dl/$go_package" -o "$temp_dir/$go_package"
echo "Installing Go to /usr/local/go (requires sudo)..."
sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf "$temp_dir/$go_package"
rm -rf "$temp_dir"
# 6. Setup Environment Variables
echo "Updating $shell_profile..."
# Add /usr/local/go/bin to PATH if not present
if ! grep -q "/usr/local/go/bin" "$shell_profile"; then
echo 'export PATH=$PATH:/usr/local/go/bin' >> "$shell_profile"
fi
# Update or Add GOPATH
if grep -q "export GOPATH=" "$shell_profile"; then
# Use a different delimiter for sed to handle slashes in paths
sed -i.bak "s|export GOPATH=.*|export GOPATH=$go_path|" "$shell_profile"
else
echo "export GOPATH=$go_path" >> "$shell_profile"
echo 'export PATH=$PATH:$GOPATH/bin' >> "$shell_profile"
fi
# 7. Create Workspaces
mkdir -p "$go_path"/{src,pkg,bin}
echo "Go workspace folders created at $go_path"
echo -e "\nSuccessfully installed $go_version!"
echo "Please run: source $shell_profile or restart your terminal."
#!/bin/bash
# GNU Make Installation Script
# Downloads, compiles, and installs the latest version of GNU Make
set -euo pipefail # Exit on error, undefined vars, pipe failures
# Configuration
readonly SCRIPT_NAME="$(basename "$0")"
readonly TEMP_DIR="$(mktemp -d)"
readonly VERSION_API="https://version-release-watchdog.onrender.com/make"
readonly GNU_MAKE_BASE_URL="https://ftp.gnu.org/gnu/make"
# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color
# Logging functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1" >&2
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1" >&2
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
# Cleanup function
cleanup() {
local exit_code=$?
log_info "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"
if [[ $exit_code -ne 0 ]]; then
log_error "Script failed with exit code $exit_code"
fi
exit "$exit_code"
}
# Set up cleanup trap
trap cleanup EXIT
# Check if running as root
check_sudo() {
if [[ $EUID -eq 0 ]]; then
log_warn "Running as root. Consider using sudo only when needed."
fi
}
# Update system packages
update_system() {
log_info "Updating system packages..."
if ! sudo apt-get update -y; then
log_error "Failed to update package list"
return 1
fi
if ! sudo apt-get upgrade -y; then
log_error "Failed to upgrade packages"
return 1
fi
log_success "System packages updated"
}
# Install build dependencies
install_dependencies() {
log_info "Installing build dependencies..."
local deps=(
build-essential
curl
wget
tar
)
if ! sudo apt-get install -y "${deps[@]}"; then
log_error "Failed to install build dependencies"
return 1
fi
log_success "Build dependencies installed"
}
# Get latest make version
get_latest_version() {
log_info "Fetching latest Make version..."
local response
if ! response=$(curl -s -w "\n%{http_code}" "$VERSION_API"); then
log_error "Failed to fetch version information"
return 1
fi
local http_code
http_code="$(echo "$response" | tail -n1)"
local version_info
version_info="$(echo "$response" | sed '$d')"
if [[ "$http_code" != "200" ]]; then
log_error "API returned HTTP $http_code"
return 1
fi
if [[ -z "$version_info" ]]; then
log_error "Empty version response"
return 1
fi
echo "$version_info"
}
# Download make source
download_make() {
local version="$1"
local filename="make-${version}.tar.gz"
local url="${GNU_MAKE_BASE_URL}/${filename}"
log_info "Downloading Make $version from $url"
cd "$TEMP_DIR"
if ! curl -fL -o "$filename" "$url"; then
log_error "Failed to download Make source"
return 1
fi
# Verify download
if [[ ! -f "$filename" ]] || [[ ! -s "$filename" ]]; then
log_error "Downloaded file is missing or empty"
return 1
fi
log_success "Downloaded $filename"
echo "$filename"
}
# Extract and build make
build_make() {
local filename="$1"
local version="${filename%.tar.gz}"
version="${version#make-}"
log_info "Extracting and building Make..."
cd "$TEMP_DIR"
# Extract
if ! tar -zxf "$filename"; then
log_error "Failed to extract $filename"
return 1
fi
# Build
cd "make-${version}"
log_info "Configuring build..."
if ! ./configure --prefix=/usr/local; then
log_error "Configuration failed"
return 1
fi
log_info "Building Make (using $(nproc) cores)..."
if ! make -j"$(nproc)"; then
log_error "Build failed"
return 1
fi
log_success "Build completed"
}
# Install make
install_make() {
log_info "Installing Make..."
if ! sudo make install; then
log_error "Installation failed"
return 1
fi
log_success "Make installed successfully"
}
# Verify installation
verify_installation() {
log_info "Verifying installation..."
local installed_version
if ! installed_version="$(make --version | head -n1)"; then
log_error "Make command not found after installation"
return 1
fi
log_success "Verification completed"
log_info "Installed: $installed_version"
}
# Main function
main() {
log_info "Starting GNU Make installation..."
check_sudo
update_system
install_dependencies
local version
version="$(get_latest_version)"
log_info "Latest version: $version"
local filename
filename="$(download_make "$version")"
build_make "$filename"
install_make
verify_installation
log_success "GNU Make installation completed successfully!"
}
# Script entry point – ALWAYS run main
main "$@"
#!/bin/bash
# Exit immediately if a command fails
set -e
# 1. Determine shell profile
if [ -n "$ZSH_VERSION" ]; then
shell_profile="$HOME/.zshrc"
elif [ -n "$BASH_VERSION" ]; then
shell_profile="$HOME/.bashrc"
elif [[ "$SHELL" == *"fish"* ]]; then
shell_profile="${XDG_CONFIG_HOME:-$HOME/.config}/fish/config.fish"
else
shell_profile="$HOME/.profile"
fi
echo "Detected shell profile: $shell_profile"
# 2. Install dependencies
echo "Installing build dependencies..."
sudo apt-get update && sudo apt-get install -y \
wget build-essential zlib1g-dev libssl-dev libncurses-dev libsqlite3-dev \
libreadline-dev libtk8.6 libgdm-dev libpcap-dev pkg-config curl \
libffi-dev libbz2-dev liblzma-dev jq
# 3. Fetch latest STABLE version
echo "Attempting to fetch latest version from Official Python API..."
# Primary: Official Python API
PYTHON_VERSION=$(curl -s "https://www.python.org/api/v2/downloads/release/?is_published=true" | \
jq -r '.[] | select(.name | test("^Python 3\\.[0-9]+\\.[0-9]+$")) | .name' | \
sed 's/Python //' | sort -V | tail -n 1)
# Secondary: Watchdog Fallback
if [[ -z "$PYTHON_VERSION" || "$PYTHON_VERSION" == "null" ]]; then
echo "Official API failed. Falling back to Watchdog API..."
WATCHDOG_RESPONSE=$(curl -s https://version-release-watchdog.onrender.com/python)
if [ -n "$WATCHDOG_RESPONSE" ]; then
# Use your original sed logic for the watchdog format
PYTHON_VERSION=$(echo "$WATCHDOG_RESPONSE" | sed -E 's/[0-9]{3}$//')
fi
fi
# Tertiary: Hardcoded Fallback
if [[ -z "$PYTHON_VERSION" || "$PYTHON_VERSION" == "null" ]]; then
echo "All APIs failed. Falling back to 3.14.3"
PYTHON_VERSION="3.14.3"
fi
echo "Targeting Python version: $PYTHON_VERSION"
# Major.Minor version for aliases (e.g., 3.14)
EXTRACT_VERSION=$(echo "$PYTHON_VERSION" | cut -d. -f1,2)
# 4. Check if this version is already installed
if command -v "python$EXTRACT_VERSION" >/dev/null 2>&1; then
# Grab the patch version of the installed instance for a precise check
CURRENT_INSTALLED=$(python$EXTRACT_VERSION --version | awk '{print $2}')
if [[ "$CURRENT_INSTALLED" == "$PYTHON_VERSION" ]]; then
read -p "Python $PYTHON_VERSION is already installed. Reinstall? (y/N) " confirm
if [[ $confirm != [yY] ]]; then
echo "Exiting."
exit 0
fi
fi
fi
# 5. Download and Build
echo "Downloading Python $PYTHON_VERSION..."
cd /tmp
if ! curl -fOL "https://www.python.org/ftp/python/$PYTHON_VERSION/Python-$PYTHON_VERSION.tgz"; then
echo "Error: File not found on FTP server for version $PYTHON_VERSION."
exit 1
fi
echo "Extracting and Configuring..."
tar -xf "Python-$PYTHON_VERSION.tgz"
cd "Python-$PYTHON_VERSION"
./configure --enable-optimizations --with-ensurepip=install --enable-loadable-sqlite-extensions
echo "Compiling (using $(nproc) cores)..."
make -j$(nproc)
echo "Installing (altinstall)..."
sudo make altinstall
# 6. Alias Configuration
echo "Configuring aliases in $HOME/.alias..."
touch "$HOME/.alias"
sed -i "/python$EXTRACT_VERSION/d" "$HOME/.alias"
sed -i "/pip$EXTRACT_VERSION/d" "$HOME/.alias"
{
echo "alias python$EXTRACT_VERSION='/usr/local/bin/python$EXTRACT_VERSION'"
echo "alias python='/usr/local/bin/python$EXTRACT_VERSION'"
echo "alias pip='/usr/local/bin/pip$EXTRACT_VERSION'"
} >> "$HOME/.alias"
# 7. Ensure Shell Profile sources .alias
if ! grep -q "source \$HOME/.alias" "$shell_profile"; then
echo "Adding source command to $shell_profile..."
echo -e "\n# Load custom aliases\n[[ -f \$HOME/.alias ]] && source \$HOME/.alias" >> "$shell_profile"
fi
# 8. Cleanup
echo "Cleaning up build files..."
rm -rf "/tmp/Python-$PYTHON_VERSION" "/tmp/Python-$PYTHON_VERSION.tgz"
echo "------------------------------------------------"
echo "Installation complete!"
echo "Python version: $(/usr/local/bin/python$EXTRACT_VERSION --version)"
echo "Please run: source $shell_profile"
echo "------------------------------------------------"
#!/bin/bash
# Zsh Installation Script with Oh-My-Zsh and Plugins
# Installs latest Zsh from source, Oh-My-Zsh, plugins, and themes
set -euo pipefail # Exit on error, undefined vars, pipe failures
# Configuration
readonly SCRIPT_NAME="${BASH_SOURCE[0]:-${0##*/}}"
readonly TEMP_DIR=$(mktemp -d)
readonly VERSION_API="https://version-release-watchdog.onrender.com/zsh"
readonly ZSH_BASE_URL="https://sourceforge.net/projects/zsh/files/zsh"
readonly OMZ_INSTALL_URL="https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh"
# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly PURPLE='\033[0;35m'
readonly NC='\033[0m' # No Color
# Global variables
ZSH_VERSION=""
INSTALL_HOMEBREW=false
SKIP_PACKAGES=false
SKIP_GIT_CONFIG=false
SKIP_SHELL_CHANGE=false
CUSTOM_CONFIG=false
SKIP_XCLIP=false
# Configuration files from gist
declare -A CONFIG_FILES=(
[".alias"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/alias"
[".func"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/func"
[".pathrc"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/pathrc"
[".sourcerc"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/sourcerc"
[".vimrc"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/vimrc"
[".zshrc"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/zshrc"
)
# Logging functions
log_info() {
echo -e "${BLUE}[INFO]${NC} $1" >&2
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1" >&2
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
log_step() {
echo -e "${PURPLE}[STEP]${NC} $1" >&2
}
# Detect operating system
detect_os() {
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
if [[ -f /etc/os-release ]]; then
. /etc/os-release
echo "$ID"
elif [[ -f /etc/debian_version ]]; then
echo "debian"
elif [[ -f /etc/redhat-release ]]; then
echo "rhel"
else
echo "linux"
fi
elif [[ "$OSTYPE" == "darwin"* ]]; then
echo "macos"
elif [[ "$OSTYPE" == "cygwin" ]]; then
echo "cygwin"
elif [[ "$OSTYPE" == "msys" ]]; then
echo "msys"
else
echo "unknown"
fi
}
# Check if xclip is installed
is_xclip_installed() {
command -v xclip >/dev/null 2>&1
}
# Show usage information
show_usage() {
cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]
Install Zsh from source with Oh-My-Zsh, plugins, and themes.
Options:
-h, --help Show this help message
-v, --version VERSION Specify Zsh version to install
--with-homebrew Install Homebrew (interactive prompt by default)
--skip-packages Skip system package installation
--skip-git Skip Git configuration
--skip-shell-change Don't change default shell to Zsh
--skip-xclip Skip xclip installation prompt (Ubuntu only)
--custom-config Use custom configuration files from gist
--git-name NAME Set Git user name (default: prompt)
--git-email EMAIL Set Git user email (default: prompt)
Examples:
$SCRIPT_NAME # Interactive installation
$SCRIPT_NAME --with-homebrew --custom-config # Install with Homebrew and custom configs
$SCRIPT_NAME -v 5.8.1 --skip-packages # Install specific version, skip packages
$SCRIPT_NAME --skip-xclip # Skip xclip installation
EOF
}
# Cleanup function
cleanup() {
local exit_code=$?
log_info "Cleaning up temporary files..."
rm -rf "$TEMP_DIR"
if [[ $exit_code -ne 0 ]]; then
log_error "Script failed with exit code $exit_code"
fi
exit $exit_code
}
# Set up cleanup trap
trap cleanup EXIT
# Parse command line arguments
parse_arguments() {
local custom_version=""
local git_name=""
local git_email=""
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_usage
exit 0
;;
-v|--version)
custom_version="$2"
shift 2
;;
--with-homebrew)
INSTALL_HOMEBREW=true
shift
;;
--skip-packages)
SKIP_PACKAGES=true
shift
;;
--skip-git)
SKIP_GIT_CONFIG=true
shift
;;
--skip-shell-change)
SKIP_SHELL_CHANGE=true
shift
;;
--skip-xclip)
SKIP_XCLIP=true
shift
;;
--custom-config)
CUSTOM_CONFIG=true
shift
;;
--git-name)
git_name="$2"
shift 2
;;
--git-email)
git_email="$2"
shift 2
;;
*)
log_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done
# Export variables
export CUSTOM_ZSH_VERSION="$custom_version"
export GIT_USER_NAME="$git_name"
export GIT_USER_EMAIL="$git_email"
}
# Check system requirements
check_requirements() {
log_info "Checking system requirements..."
local os_type
os_type=$(detect_os)
log_info "Detected OS: $os_type"
# Check if we're on a supported system
if [[ "$os_type" != "ubuntu" && "$os_type" != "debian" ]]; then
if ! command -v apt-get >/dev/null 2>&1; then
log_error "This script requires apt-get (Debian/Ubuntu-based system)"
return 1
fi
fi
# Check if running as root
if [[ $EUID -eq 0 ]]; then
log_error "This script should not be run as root"
return 1
fi
# Check sudo access
if ! sudo -n true 2>/dev/null; then
log_info "This script requires sudo access. You may be prompted for your password."
fi
log_success "System requirements check passed"
}
# Update system packages
update_system() {
if [[ "$SKIP_PACKAGES" == "true" ]]; then
log_info "Skipping system package updates"
return 0
fi
log_step "Updating system packages..."
if ! sudo apt-get update -y; then
log_error "Failed to update package list"
return 1
fi
if ! sudo apt-get upgrade -y; then
log_error "Failed to upgrade packages"
return 1
fi
log_success "System packages updated"
}
# Install xclip for Ubuntu systems
install_xclip() {
local os_type
os_type=$(detect_os)
# Only install xclip on Ubuntu/Debian systems
if [[ "$os_type" != "ubuntu" && "$os_type" != "debian" ]]; then
log_info "xclip installation is only supported on Ubuntu/Debian systems (detected: $os_type)"
return 0
fi
# Skip if packages are being skipped or xclip installation is explicitly skipped
if [[ "$SKIP_PACKAGES" == "true" || "$SKIP_XCLIP" == "true" ]]; then
log_info "Skipping xclip installation"
return 0
fi
# Check if xclip is already installed
if is_xclip_installed; then
log_info "xclip is already installed"
return 0
fi
log_step "Checking xclip installation..."
# Prompt user for xclip installation
local response
cat << EOF
${YELLOW}xclip utility information:${NC}
xclip is a command line utility that allows you to copy and paste
text to/from the X11 clipboard from the terminal. It's useful for
copying command outputs or file contents directly to your clipboard.
Example usage:
echo "Hello World" | xclip -selection clipboard
cat file.txt | xclip -sel c
EOF
read -p "Do you want to install xclip? (y/N): " response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
log_info "Skipping xclip installation"
return 0
fi
log_info "Installing xclip..."
if ! sudo apt-get install -y xclip; then
log_error "Failed to install xclip"
return 1
fi
log_success "xclip installed successfully"
# Show usage tip
cat << EOF
${GREEN}xclip installed!${NC} You can now use commands like:
${BLUE}echo "text" | xclip -selection clipboard${NC} # Copy to clipboard
${BLUE}xclip -selection clipboard -o${NC} # Paste from clipboard
EOF
}
# Install required packages
install_packages() {
if [[ "$SKIP_PACKAGES" == "true" ]]; then
log_info "Skipping package installation"
return 0
fi
log_step "Installing required packages..."
local packages=(
"vim"
"git"
"zip"
"unzip"
"curl"
"wget"
"build-essential"
"libncurses5-dev"
"libncursesw5-dev"
"libtinfo-dev"
"libgdbm-dev"
"libpcre3-dev"
"xz-utils"
)
if ! sudo apt-get install -y "${packages[@]}"; then
log_error "Failed to install required packages"
return 1
fi
log_success "Required packages installed"
}
# Configure Git
configure_git() {
if [[ "$SKIP_GIT_CONFIG" == "true" ]]; then
log_info "Skipping Git configuration"
return 0
fi
log_step "Configuring Git..."
# Set system-wide editor
sudo git config --system core.editor "vim"
# Get user name and email
local git_name="$GIT_USER_NAME"
local git_email="$GIT_USER_EMAIL"
if [[ -z "$git_name" ]]; then
read -p "Enter your Git user name: " git_name
if [[ -z "$git_name" ]]; then
log_warn "Git user name not provided, skipping Git user configuration"
return 0
fi
fi
if [[ -z "$git_email" ]]; then
read -p "Enter your Git email: " git_email
if [[ -z "$git_email" ]]; then
log_warn "Git email not provided, skipping Git user configuration"
return 0
fi
fi
# Set global Git configuration
git config --global user.name "$git_name"
git config --global user.email "$git_email"
log_success "Git configured with user: $git_name <$git_email>"
}
# Install Homebrew
install_homebrew() {
if [[ "$INSTALL_HOMEBREW" != "true" ]]; then
local response
read -p "Do you want to install Homebrew? (y/N): " response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
log_info "Skipping Homebrew installation"
return 0
fi
fi
log_step "Installing Homebrew..."
if command -v brew >/dev/null 2>&1; then
log_info "Homebrew is already installed"
return 0
fi
# Install Homebrew
if ! /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"; then
log_error "Failed to install Homebrew"
return 1
fi
# Add Homebrew to PATH
local brew_paths=(
"/home/linuxbrew/.linuxbrew/bin/brew"
"/opt/homebrew/bin/brew"
)
for brew_path in "${brew_paths[@]}"; do
if [[ -x "$brew_path" ]]; then
eval "$($brew_path shellenv)"
log_success "Homebrew installed and configured"
return 0
fi
done
log_warn "Homebrew installed but not found in expected locations"
log_info "Please restart your terminal or add Homebrew to your PATH manually"
}
# Get Zsh version
get_zsh_version() {
if [[ -n "$CUSTOM_ZSH_VERSION" ]]; then
ZSH_VERSION="$CUSTOM_ZSH_VERSION"
log_info "Using specified Zsh version: $ZSH_VERSION"
else
log_step "Fetching latest Zsh version..."
local response
if ! response=$(curl -s -w "\n%{http_code}" "$VERSION_API"); then
log_error "Failed to fetch Zsh version from API"
return 1
fi
local http_code=$(echo "$response" | tail -n1)
local version_info=$(echo "$response" | sed '$d')
if [[ "$http_code" != "200" ]]; then
log_error "API returned HTTP $http_code"
return 1
fi
if [[ -z "$version_info" ]]; then
log_error "Empty version response"
return 1
fi
ZSH_VERSION="$version_info"
log_info "Latest Zsh version: $ZSH_VERSION"
fi
# Validate version format
if ! [[ "$ZSH_VERSION" =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
log_error "Invalid Zsh version format: $ZSH_VERSION"
return 1
fi
}
# Download and build Zsh
install_zsh() {
log_step "Installing Zsh $ZSH_VERSION from source..."
cd "$TEMP_DIR"
# Download Zsh source
local filename="zsh-${ZSH_VERSION}.tar.xz"
local url="${ZSH_BASE_URL}/${ZSH_VERSION}/${filename}/download"
log_info "Downloading Zsh source..."
if ! curl -fL --progress-bar -o "$filename" "$url"; then
log_error "Failed to download Zsh source"
return 1
fi
# Extract
log_info "Extracting Zsh source..."
if ! tar -xf "$filename"; then
log_error "Failed to extract Zsh source"
return 1
fi
# Build and install
cd "zsh-${ZSH_VERSION}"
log_info "Configuring Zsh build..."
if ! ./configure --prefix=/usr/local --enable-multibyte --enable-unicode9; then
log_error "Zsh configuration failed"
return 1
fi
log_info "Building Zsh (using $(nproc) cores)..."
if ! make -j"$(nproc)"; then
log_error "Zsh build failed"
return 1
fi
log_info "Installing Zsh..."
if ! sudo make install; then
log_error "Zsh installation failed"
return 1
fi
log_success "Zsh $ZSH_VERSION installed successfully"
}
# Configure Zsh as default shell
configure_zsh_shell() {
if [[ "$SKIP_SHELL_CHANGE" == "true" ]]; then
log_info "Skipping shell change"
return 0
fi
log_step "Configuring Zsh as default shell..."
local zsh_path
zsh_path=$(command -v zsh)
if [[ -z "$zsh_path" ]]; then
log_error "Zsh not found in PATH"
return 1
fi
# Add Zsh to valid shells if not present
if ! grep -Fxq "$zsh_path" /etc/shells; then
log_info "Adding Zsh to /etc/shells..."
echo "$zsh_path" | sudo tee -a /etc/shells >/dev/null
fi
# Change default shell
log_info "Changing default shell to Zsh..."
if ! sudo chsh -s "$zsh_path" "$USER"; then
log_error "Failed to change default shell"
return 1
fi
log_success "Default shell changed to Zsh"
}
# Install Oh-My-Zsh
install_oh_my_zsh() {
log_step "Installing Oh-My-Zsh..."
if [[ -d "$HOME/.oh-my-zsh" ]]; then
log_info "Oh-My-Zsh is already installed"
return 0
fi
# Download and install Oh-My-Zsh non-interactively
export RUNZSH=no
export KEEP_ZSHRC=yes
if ! sh -c "$(curl -fsSL $OMZ_INSTALL_URL)"; then
log_error "Failed to install Oh-My-Zsh"
return 1
fi
log_success "Oh-My-Zsh installed"
}
# Install Zsh plugins
install_zsh_plugins() {
log_step "Installing Zsh plugins..."
local custom_dir="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
# Install zsh-autosuggestions
local autosuggestions_dir="$custom_dir/plugins/zsh-autosuggestions"
if [[ ! -d "$autosuggestions_dir" ]]; then
log_info "Installing zsh-autosuggestions..."
if ! git clone https://github.com/zsh-users/zsh-autosuggestions "$autosuggestions_dir"; then
log_error "Failed to install zsh-autosuggestions"
return 1
fi
else
log_info "zsh-autosuggestions already installed"
fi
# Install zsh-syntax-highlighting
local highlighting_dir="$custom_dir/plugins/zsh-syntax-highlighting"
if [[ ! -d "$highlighting_dir" ]]; then
log_info "Installing zsh-syntax-highlighting..."
if ! git clone https://github.com/zsh-users/zsh-syntax-highlighting "$highlighting_dir"; then
log_error "Failed to install zsh-syntax-highlighting"
return 1
fi
else
log_info "zsh-syntax-highlighting already installed"
fi
log_success "Zsh plugins installed"
}
# Install Spaceship theme
install_spaceship_theme() {
log_step "Installing Spaceship theme..."
local custom_dir="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
local theme_dir="$custom_dir/themes/spaceship-prompt"
local theme_link="$custom_dir/themes/spaceship.zsh-theme"
if [[ ! -d "$theme_dir" ]]; then
log_info "Downloading Spaceship theme..."
if ! git clone https://github.com/spaceship-prompt/spaceship-prompt.git "$theme_dir" --depth=1; then
log_error "Failed to install Spaceship theme"
return 1
fi
else
log_info "Spaceship theme already installed"
fi
# Create symlink if it doesn't exist
if [[ ! -L "$theme_link" ]]; then
ln -sf "$theme_dir/spaceship.zsh-theme" "$theme_link"
fi
log_success "Spaceship theme installed"
}
# Download configuration files
download_config_files() {
if [[ "$CUSTOM_CONFIG" != "true" ]]; then
local response
read -p "Do you want to download custom configuration files? (y/N): " response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
log_info "Skipping custom configuration files"
return 0
fi
fi
log_step "Downloading configuration files..."
# Backup existing files
local backup_dir="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$backup_dir"
for filename in "${!CONFIG_FILES[@]}"; do
local filepath="$HOME/$filename"
# Backup existing file
if [[ -f "$filepath" ]]; then
log_info "Backing up existing $filename to $backup_dir"
cp "$filepath" "$backup_dir/"
fi
# Download new file
log_info "Downloading $filename..."
if ! curl -fsSL "${CONFIG_FILES[$filename]}" -o "$filepath"; then
log_warn "Failed to download $filename"
else
log_info "Downloaded $filename"
fi
done
log_success "Configuration files downloaded (backups in $backup_dir)"
}
# Update Zsh configuration
update_zsh_config() {
local zshrc="$HOME/.zshrc"
if [[ ! -f "$zshrc" ]]; then
log_warn "No .zshrc file found, creating basic configuration"
cat > "$zshrc" << 'EOF'
# Path to your oh-my-zsh installation.
export ZSH="$HOME/.oh-my-zsh"
# Set name of the theme to load
ZSH_THEME="spaceship"
# Add wisely, as too many plugins slow down shell startup.
plugins=(git zsh-autosuggestions zsh-syntax-highlighting)
source $ZSH/oh-my-zsh.sh
EOF
return 0
fi
# Update theme if not already set to spaceship
if ! grep -q 'ZSH_THEME="spaceship"' "$zshrc"; then
sed -i 's/ZSH_THEME=".*"/ZSH_THEME="spaceship"/' "$zshrc"
log_info "Updated ZSH_THEME to spaceship"
fi
# Update plugins
local current_plugins
current_plugins=$(grep -E '^plugins=' "$zshrc" | head -1)
if [[ -n "$current_plugins" ]]; then
if ! echo "$current_plugins" | grep -q "zsh-autosuggestions"; then
sed -i 's/plugins=(\(.*\))/plugins=(\1 zsh-autosuggestions)/' "$zshrc"
log_info "Added zsh-autosuggestions to plugins"
fi
if ! echo "$current_plugins" | grep -q "zsh-syntax-highlighting"; then
sed -i 's/plugins=(\(.*\))/plugins=(\1 zsh-syntax-highlighting)/' "$zshrc"
log_info "Added zsh-syntax-highlighting to plugins"
fi
fi
log_success "Zsh configuration updated"
}
# Switch to Zsh automatically after installation
switch_to_zsh() {
if [[ "$SKIP_SHELL_CHANGE" == "true" ]]; then
log_info "Skipping immediate shell switch"
return 0
fi
log_step "Switching to Zsh automatically..."
local zsh_path
zsh_path=$(command -v zsh)
if [[ -z "$zsh_path" ]]; then
log_error "Zsh not found in PATH"
return 1
fi
# Check if we're already in Zsh
if [[ "$SHELL" == "$zsh_path" ]] && [[ -n "${ZSH_VERSION:-}" ]]; then
log_info "Already running in Zsh"
return 0
fi
log_success "Starting new Zsh session..."
# Replace current shell process with Zsh
exec "$zsh_path" -l
# This line will never be reached if exec succeeds
log_error "Failed to switch to Zsh"
return 1
}
# Show completion message
show_completion_message() {
local os_type
os_type=$(detect_os)
cat << EOF
${GREEN}╔══════════════════════════════════════════════════════════════╗
║ Zsh Installation Complete! ║
╚══════════════════════════════════════════════════════════════╝${NC}
${YELLOW}What was installed:${NC}
✓ Zsh $ZSH_VERSION (compiled from source)
✓ Oh-My-Zsh framework
✓ zsh-autosuggestions plugin
✓ zsh-syntax-highlighting plugin
✓ Spaceship theme
EOF
# Show xclip info if installed
if [[ "$os_type" == "ubuntu" || "$os_type" == "debian" ]] && is_xclip_installed; then
echo "✓ xclip clipboard utility"
fi
cat << EOF
${YELLOW}Useful commands:${NC}
- ${BLUE}omz update${NC} # Update Oh-My-Zsh
- ${BLUE}omz plugin list${NC} # List available plugins
- ${BLUE}omz theme list${NC} # List available themes
EOF
# Show xclip commands if available
if [[ "$os_type" == "ubuntu" || "$os_type" == "debian" ]] && is_xclip_installed; then
cat << EOF
- ${BLUE}echo "text" | xclip -sel c${NC} # Copy to clipboard
- ${BLUE}xclip -sel c -o${NC} # Paste from clipboard
EOF
fi
cat << EOF
${YELLOW}Configuration files:${NC}
- Zsh config: ~/.zshrc
- Oh-My-Zsh: ~/.oh-my-zsh/
EOF
}
# Main function
main() {
log_info "Starting Zsh installation..."
parse_arguments "$@"
check_requirements
update_system
install_packages
install_xclip # Install xclip after basic packages
configure_git
install_homebrew
get_zsh_version
install_zsh
configure_zsh_shell
install_oh_my_zsh
install_zsh_plugins
install_spaceship_theme
download_config_files
update_zsh_config
show_completion_message
log_success "Zsh installation completed successfully!"
# Automatically switch to Zsh
switch_to_zsh
# This line will only execute if switch_to_zsh fails
echo "Enjoy your new Zsh setup! 🚀"
}
# Script entry point - Fixed to handle bash -c execution
if [[ "${BASH_SOURCE[0]:-}" == "${0}" ]] || [[ -z "${BASH_SOURCE[0]:-}" ]]; then
main "$@"
fi
#!/bin/zsh
set -e
echo "Installing Oh-My-Zsh..."
# Install Oh-My-Zsh non-interactively
export RUNZSH=no
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
echo "Oh-My-Zsh installed."
# Dotfiles to download
config_files=(".alias" ".func" ".pathrc" ".sourcerc" ".vimrc" ".zshrc")
config_urls=(
"https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/alias"
"https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/func_macos"
"https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/pathrc_macos"
"https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/sourcerc"
"https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/vimrc"
"https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/zshrc"
)
# Create a backup directory for existing dotfiles
backup_dir="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$backup_dir"
echo "Downloading config files..."
# Loop over config files
for i in {1..$#config_files}; do
filename=${config_files[i]}
url=${config_urls[i]}
filepath="$HOME/$filename"
echo "Processing $filename..."
# Backup existing file if it exists
if [[ -f "$filepath" ]]; then
echo "Backing up $filename to $backup_dir"
mv "$filepath" "$backup_dir/"
fi
# Download the file
if curl -fsSL "$url" -o "$filepath"; then
echo "$filename downloaded."
else
echo "Failed to download $filename"
fi
done
echo "Config files downloaded."
# Install zsh-autosuggestions plugin
plugin_dir="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-autosuggestions"
if [[ ! -d "$plugin_dir" ]]; then
echo "Installing zsh-autosuggestions plugin..."
git clone https://github.com/zsh-users/zsh-autosuggestions "$plugin_dir"
echo "zsh-autosuggestions installed."
else
echo "zsh-autosuggestions already installed."
fi
# Install Spaceship prompt theme
theme_dir="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/spaceship-prompt"
theme_link="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/spaceship.zsh-theme"
if [[ ! -d "$theme_dir" ]]; then
echo "Installing Spaceship theme..."
git clone https://github.com/spaceship-prompt/spaceship-prompt.git "$theme_dir" --depth=1
ln -sf "$theme_dir/spaceship.zsh-theme" "$theme_link"
echo "Spaceship theme installed."
else
echo "Spaceship theme already installed."
fi
# Set Timezone
# We check if timedatectl exists to prevent errors on macOS or containers without systemd
if command -v timedatectl >/dev/null; then
echo "Setting timezone to Asia/Singapore..."
# Uses sudo; || true ensures script doesn't exit if user cancels sudo password
sudo timedatectl set-timezone Asia/Singapore || echo "Warning: Failed to set timezone (check sudo permissions)."
else
echo "Skipping timezone set: 'timedatectl' command not found."
fi
echo ""
echo "✅ Done! Restart your terminal or run: exec zsh"
# Automatically reload Zsh
exec zsh
#!/bin/bash
set -euo pipefail
# =============================
# COLORS & LOGGING
# =============================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() { echo -e "${BLUE}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
# =============================
# FLAGS & CONFIG
# =============================
SKIP_PACKAGES=false
SKIP_GIT_CONFIG=false
SKIP_SHELL_CHANGE=false
# This flag is for non-interactive mode.
# Interactive menu options will override this.
CUSTOM_CONFIG=false
SKIP_XCLIP=false
INSTALL_HOMEBREW=false
declare -A CONFIG_FILES=(
[".alias"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/alias"
[".func"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/func"
[".pathrc"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/pathrc"
[".sourcerc"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/sourcerc"
[".vimrc"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/vimrc"
[".zshrc"]="https://gist.githubusercontent.com/weehongkoh/72bdb76beacacf2ca3dd39a72395b9ee/raw/zshrc"
)
# =============================
# OS DETECTION
# =============================
detect_os() {
if [ -f /etc/os-release ]; then
. /etc/os-release
echo "$ID"
else
echo "unknown"
fi
}
# =============================
# REQUIREMENTS
# =============================
check_requirements() {
[[ $EUID -eq 0 ]] && err "Do not run as root" && exit 1
command -v sudo >/dev/null || { err "sudo required"; exit 1; }
}
# =============================
# INSTALLATION FUNCTIONS
# =============================
update_system() {
$SKIP_PACKAGES && return
log "Updating system..."
sudo apt-get update -y
sudo apt-get upgrade -y
ok "System updated"
}
install_packages() {
$SKIP_PACKAGES && return
log "Installing core packages..."
sudo apt-get install -y \
zsh git vim curl wget unzip zip build-essential xz-utils
ok "Packages installed"
}
set_timezone() {
log "Setting timezone to Asia/Singapore..."
sudo timedatectl set-timezone Asia/Singapore
ok "Timezone set to Asia/Singapore"
}
install_xclip() {
local os
os=$(detect_os)
[[ "$os" != "ubuntu" && "$os" != "debian" ]] && return 0
$SKIP_XCLIP && return 0
command -v xclip >/dev/null && ok "xclip already installed" && return 0
read -r -p "Install xclip? (y/N): " r
case "$r" in
[Yy]) sudo apt-get install -y xclip; ok "xclip installed" ;;
*) log "Skipping xclip installation" ;;
esac
}
configure_git() {
$SKIP_GIT_CONFIG && return
log "Configuring Git..."
read -p "Git name (leave empty to skip): " name
read -p "Git email (leave empty to skip): " email
[[ -n "$name" ]] && git config --global user.name "$name"
[[ -n "$email" ]] && git config --global user.email "$email"
ok "Git configured"
}
install_homebrew() {
! $INSTALL_HOMEBREW && return
command -v brew >/dev/null && ok "Homebrew already installed" && return
log "Installing Homebrew..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
ok "Homebrew installed"
}
configure_shell() {
$SKIP_SHELL_CHANGE && return
log "Changing default shell to zsh..."
local zsh_path
zsh_path=$(command -v zsh)
grep -qx "$zsh_path" /etc/shells || echo "$zsh_path" | sudo tee -a /etc/shells >/dev/null
sudo chsh -s "$zsh_path" "$USER"
ok "Shell changed (requires logout/login to take effect)"
}
install_oh_my_zsh() {
[[ -d "$HOME/.oh-my-zsh" ]] && ok "Oh My Zsh already installed" && return
log "Installing Oh My Zsh..."
# Keep zshrc ensures we don't blow away configs if they exist
RUNZSH=no CHSH=no KEEP_ZSHRC=yes \
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
ok "Oh My Zsh installed"
}
install_plugins() {
log "Installing plugins..."
local dir="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins"
[[ -d "$dir/zsh-autosuggestions" ]] || git clone https://github.com/zsh-users/zsh-autosuggestions "$dir/zsh-autosuggestions"
[[ -d "$dir/zsh-syntax-highlighting" ]] || git clone https://github.com/zsh-users/zsh-syntax-highlighting "$dir/zsh-syntax-highlighting"
ok "Plugins installed"
}
install_theme() {
log "Installing Spaceship theme..."
local themes="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes"
local dir="$themes/spaceship-prompt"
[[ -d "$dir" ]] || git clone https://github.com/spaceship-prompt/spaceship-prompt.git "$dir" --depth=1
ln -sf "$dir/spaceship.zsh-theme" "$themes/spaceship.zsh-theme"
ok "Theme installed"
}
download_configs() {
# Guard clause removed so menu selection works
log "Downloading custom config files..."
local backup="$HOME/.config_backup_$(date +%Y%m%d_%H%M%S)"
mkdir -p "$backup"
for f in "${!CONFIG_FILES[@]}"; do
if [[ -f "$HOME/$f" ]]; then
cp "$HOME/$f" "$backup/"
fi
log "Fetching $f ..."
curl -fsSL "${CONFIG_FILES[$f]}" -o "$HOME/$f" || warn "Failed to download $f"
done
ok "Configs downloaded (Backup at $backup)"
}
update_zshrc() {
local zshrc="$HOME/.zshrc"
log "Updating .zshrc..."
# Create zshrc if missing to prevent errors
if [[ ! -f "$zshrc" ]]; then
warn ".zshrc not found, creating new one..."
touch "$zshrc"
fi
# Use || true to prevent 'set -e' from exiting if grep finds nothing
grep -q 'ZSH_THEME="spaceship"' "$zshrc" || sed -i 's/ZSH_THEME=".*"/ZSH_THEME="spaceship"/' "$zshrc"
grep -q 'zsh-autosuggestions' "$zshrc" || sed -i 's/plugins=(/plugins=(zsh-autosuggestions /' "$zshrc"
grep -q 'zsh-syntax-highlighting' "$zshrc" || sed -i 's/plugins=(/plugins=(zsh-syntax-highlighting /' "$zshrc"
ok ".zshrc updated with plugins and theme"
}
switch_shell() {
$SKIP_SHELL_CHANGE && return
log "Starting Zsh session..."
echo -e "${YELLOW}Type 'exit' to return to this installer menu.${NC}"
echo "----------------------------------------"
# Run zsh as a subprocess, not exec
zsh -l
echo "----------------------------------------"
ok "Returned from Zsh session"
}
# =============================
# INTERACTIVE MENU
# =============================
show_menu() {
echo "==========================================="
echo "Zsh Installer - Choose what to do"
echo "==========================================="
echo " 1) Update system packages"
echo " 2) Install core packages (zsh, git, vim, etc.)"
echo " 3) Set Timezone (Asia/Singapore)"
echo " 4) Install xclip"
echo " 5) Configure Git"
echo " 6) Install Homebrew"
echo " 7) Configure shell (chsh - sets default shell)"
echo " 8) Install Oh My Zsh"
echo " 9) Install plugins (autosuggestions, syntax highlighting)"
echo "10) Install Spaceship theme"
echo "11) Download custom configs (~/.alias, .vimrc, etc.)"
echo "12) Update ~/.zshrc for plugins & theme"
echo "13) Switch to Zsh (Temporary Sub-shell)"
echo "14) Quit"
echo "==========================================="
echo "You can enter multiple numbers at once (e.g., 2,3,7,8)"
}
run_choices() {
local input
read -p "Select: " input
input="${input//,/ }" # replace commas with spaces
for choice in $input; do
case "$choice" in
1) update_system ;;
2) install_packages ;;
3) set_timezone ;;
4) install_xclip ;;
5) configure_git ;;
6) install_homebrew ;;
7) configure_shell ;;
8) install_oh_my_zsh ;;
9) install_plugins ;;
10) install_theme ;;
11) download_configs ;;
12) update_zshrc ;;
13) switch_shell ;;
14) log "Exiting..."; exit 0 ;;
*) warn "Skipping invalid option: $choice" ;;
esac
echo
done
}
# =============================
# MAIN
# =============================
main() {
check_requirements
while true; do
show_menu
run_choices
read -p "Do you want to run more options? (y/n): " again
[[ "$again" =~ ^[Yy]$ ]] || break
done
ok "Zsh installation/configuration complete!"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment