Skip to content

Instantly share code, notes, and snippets.

@thimslugga
Last active June 30, 2026 14:32
Show Gist options
  • Select an option

  • Save thimslugga/fbb45aa24ea1394e4f85d894f978a049 to your computer and use it in GitHub Desktop.

Select an option

Save thimslugga/fbb45aa24ea1394e4f85d894f978a049 to your computer and use it in GitHub Desktop.
Setup WSL on Windows 11
#!/bin/bash
set -euo pipefail
# =====================================
# Setup Ubuntu WSL
# Installs development environment + dotfiles configuration
# Platform: Windows Subsystem for Linux (WSL 2)
# Tested on: Ubuntu 26.04/24.04
# =====================================
echo "========================================"
echo " Setup Ubuntu WSL"
echo "========================================"
echo ""
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[SKIP]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_step() { echo -e "\n${BLUE}[$1/18]${NC} $2"; }
# Trap to show where script failed
trap 'log_error "Script failed at line $LINENO. Command: $BASH_COMMAND"; exit 1' ERR
usage() {
cat <<EOF
Usage: ${0##*/} [ options ] ....
EOF
}
# Helper to check if a command exists
command_exists() {
command -v "$1" &> /dev/null
}
# Check if running in WSL
if ! grep -qEi "(Microsoft|WSL)" /proc/version &> /dev/null; then
log_warn "This doesn't appear to be WSL. This script is optimized for WSL."
read -p "Continue anyway? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
fi
# Parse dotfiles directory argument
DOTFILES_DIR=""
if [ -n "${1:-}" ]; then
DOTFILES_DIR="$(cd "$1" && pwd)"
if [ ! -d "$DOTFILES_DIR" ]; then
log_error "Dotfiles directory not found: $1"
exit 1
fi
log_info "Will use dotfiles from: $DOTFILES_DIR"
else
log_warn "No dotfiles directory provided. Will create default configs."
log_info "Usage: $0 <path-to-dotfiles-directory>"
fi
echo ""
log_info "This will install and configure:"
log_info " • System packages (via apt)"
log_info " • Programming languages (Python, Java, Go, Rust, Ruby, Lua, Node.js)"
log_info " • Tmux with custom widgets and TPM"
log_info " • Neovim with plugins"
log_info " • Zsh with oh-my-zsh and PowerLevel10k"
log_info " • Dotfiles configuration (if provided)"
echo ""
read -p "Continue? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
log_step 1 "Fixing locale..."
if locale -a 2>/dev/null | grep -q "en_US.utf8"; then
log_warn "Locale already configured"
else
sudo sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
sudo locale-gen
log_info "Locale configured"
fi
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
log_step 2 "Updating system..."
sudo apt update -y
sudo apt upgrade -y
log_step 3 "Installing base developer tools..."
TOOLS="curl wget unzip zip git build-essential software-properties-common gnupg ripgrep fd-find fzf tree htop bc xclip bat lsd git-delta jq"
MISSING_TOOLS=""
for tool in $TOOLS; do
if ! dpkg -l | grep -q "^ii $tool "; then
MISSING_TOOLS="$MISSING_TOOLS $tool"
fi
done
if [ -n "$MISSING_TOOLS" ]; then
sudo apt install -y $MISSING_TOOLS
log_info "Installed:$MISSING_TOOLS"
else
log_warn "All base tools already installed"
fi
# Create symlinks for Ubuntu-named packages
mkdir -p ~/.local/bin
if [ ! -e ~/.local/bin/fd ] && [ -e /usr/bin/fdfind ]; then
ln -sf /usr/bin/fdfind ~/.local/bin/fd
log_info "Created fd symlink"
fi
if [ ! -e ~/.local/bin/bat ] && [ -e /usr/bin/batcat ]; then
ln -sf /usr/bin/batcat ~/.local/bin/bat
log_info "Created bat symlink"
fi
log_step 4 "Installing Python 3..."
if command_exists python3; then
log_warn "Python3 already installed: $(python3 --version)"
else
sudo apt install -y python3 python3-pip python3-venv
log_info "Python3 installed"
fi
# Install pynvim if not present
if python3 -c "import pynvim" 2>/dev/null; then
log_warn "pynvim already installed"
else
# Use apt instead of pip for Ubuntu 24.04+ (PEP 668)
if dpkg -l | grep -q "^ii python3-pynvim "; then
log_warn "pynvim already installed via apt"
else
sudo apt install -y python3-pynvim
log_info "pynvim installed via apt"
fi
fi
log_step 5 "Installing Java 11, 17, 21 via SDKMAN..."
export SDKMAN_DIR="$HOME/.sdkman"
if [ -d "$SDKMAN_DIR" ]; then
log_warn "SDKMAN already installed"
else
log_info "Installing SDKMAN..."
curl -s "https://get.sdkman.io" | bash
fi
# Source SDKMAN
if [[ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]]; then
set +u
export SDKMAN_OFFLINE_MODE=false
source "$SDKMAN_DIR/bin/sdkman-init.sh"
fi
# Install Java versions
install_java() {
local version=$1
if sdk list java 2>/dev/null | grep -q "${version}.*-tem.*installed"; then
log_warn "Java $version already installed"
else
log_info "Installing Java $version..."
yes | sdk install java "${version}-tem" || true
fi
}
install_java "11.0.25"
install_java "17.0.13"
install_java "21.0.5"
sdk default java 21.0.5-tem 2>/dev/null || true
set -u
log_info "Java: $(java --version 2>&1 | head -1)"
log_step 6 "Installing Go..."
if command_exists go; then
log_warn "Go already installed: $(go version)"
else
sudo apt install -y golang-go
log_info "Go installed: $(go version)"
fi
log_step 7 "Installing Rust..."
if [ -d "$HOME/.cargo" ] && command_exists cargo; then
log_warn "Rust already installed: $(cargo --version)"
else
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
export PATH="$HOME/.cargo/bin:$PATH"
log_info "Rust installed: $(cargo --version)"
fi
log_step 8 "Installing Ruby and Lua..."
if command_exists ruby; then
log_warn "Ruby already installed: $(ruby --version)"
else
sudo apt install -y ruby-full
log_info "Ruby installed"
fi
if command_exists lua5.4; then
log_warn "Lua already installed: $(lua5.4 -v 2>&1)"
else
sudo apt install -y lua5.4 liblua5.4-dev luarocks
log_info "Lua installed"
fi
log_step 9 "Installing Node.js and TypeScript..."
export NVM_DIR="$HOME/.nvm"
if [ -d "$NVM_DIR" ]; then
log_warn "nvm already installed"
else
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
log_info "nvm installed"
fi
# Source nvm
if [ -s "$NVM_DIR/nvm.sh" ]; then
set +u
\. "$NVM_DIR/nvm.sh"
set -u
fi
if command_exists node; then
log_warn "Node.js already installed: $(node --version)"
else
set +u
nvm install --lts
nvm use --lts
set -u
log_info "Node.js installed: $(node --version)"
fi
if command_exists tsc; then
log_warn "TypeScript already installed: $(tsc --version)"
else
npm install -g typescript typescript-language-server
log_info "TypeScript installed"
fi
log_step 10 "Installing Zsh and Oh-My-Zsh..."
if command_exists zsh; then
log_warn "Zsh already installed: $(zsh --version)"
else
sudo apt install -y zsh
log_info "Zsh installed"
fi
# Install Oh-My-Zsh
if [ -d "$HOME/.oh-my-zsh" ]; then
log_warn "Oh-My-Zsh already installed"
else
log_info "Installing Oh-My-Zsh..."
if [ -f "$HOME/.zshrc" ]; then
cp "$HOME/.zshrc" "$HOME/.zshrc.pre-oh-my-zsh"
log_info "Backed up existing .zshrc"
fi
RUNZSH=no CHSH=no sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended
log_info "Oh-My-Zsh installed"
fi
# Install Powerlevel10k theme
ZSH_CUSTOM="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
if [ -d "$ZSH_CUSTOM/themes/powerlevel10k" ]; then
log_warn "Powerlevel10k already installed"
else
log_info "Installing Powerlevel10k..."
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git "$ZSH_CUSTOM/themes/powerlevel10k"
log_info "Powerlevel10k installed"
fi
# Install zsh-autosuggestions
if [ -d "$ZSH_CUSTOM/plugins/zsh-autosuggestions" ]; then
log_warn "zsh-autosuggestions already installed"
else
log_info "Installing zsh-autosuggestions..."
git clone https://github.com/zsh-users/zsh-autosuggestions "$ZSH_CUSTOM/plugins/zsh-autosuggestions"
log_info "zsh-autosuggestions installed"
fi
# Install zsh-syntax-highlighting
if [ -d "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting" ]; then
log_warn "zsh-syntax-highlighting already installed"
else
log_info "Installing zsh-syntax-highlighting..."
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting"
log_info "zsh-syntax-highlighting installed"
fi
log_step 11 "Installing Tmux and TPM..."
if command_exists tmux; then
log_warn "Tmux already installed: $(tmux -V)"
else
sudo apt install -y tmux
log_info "Tmux installed"
fi
# Install TPM
if [ -d "$HOME/.tmux/plugins/tpm" ]; then
log_warn "TPM already installed"
else
log_info "Installing TPM..."
git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm
log_info "TPM installed"
fi
log_step 12 "Installing Neovim..."
NVIM_VERSION="v0.11.5"
NVIM_ARCHIVE="nvim-linux-x86_64.tar.gz"
NVIM_DIR="nvim-linux-x86_64"
if command_exists nvim; then
CURRENT_VERSION=$(nvim --version | head -1 | awk '{print $2}')
if [ "$CURRENT_VERSION" = "$NVIM_VERSION" ]; then
log_warn "Neovim already installed: $CURRENT_VERSION"
else
log_info "Upgrading Neovim from $CURRENT_VERSION to $NVIM_VERSION"
cd /tmp
curl -LO "https://github.com/neovim/neovim/releases/download/${NVIM_VERSION}/${NVIM_ARCHIVE}"
sudo rm -rf /opt/${NVIM_DIR}
sudo tar -C /opt -xzf ${NVIM_ARCHIVE}
sudo ln -sf /opt/${NVIM_DIR}/bin/nvim /usr/local/bin/nvim
rm -f ${NVIM_ARCHIVE}
fi
else
cd /tmp
curl -LO "https://github.com/neovim/neovim/releases/download/${NVIM_VERSION}/${NVIM_ARCHIVE}"
sudo rm -rf /opt/${NVIM_DIR}
sudo tar -C /opt -xzf ${NVIM_ARCHIVE}
sudo ln -sf /opt/${NVIM_DIR}/bin/nvim /usr/local/bin/nvim
rm -f ${NVIM_ARCHIVE}
log_info "Neovim installed: $(nvim --version | head -1)"
fi
log_step 13 "Setting up dotfiles configuration..."
if [ -n "$DOTFILES_DIR" ]; then
# Use provided dotfiles directory
log_info "Using dotfiles from: $DOTFILES_DIR"
# Backup existing configs
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
if [ -f "$HOME/.zshrc" ]; then
cp "$HOME/.zshrc" "$HOME/.zshrc.backup.$TIMESTAMP"
log_info "Backed up .zshrc"
fi
if [ -f "$HOME/.tmux.conf" ]; then
cp "$HOME/.tmux.conf" "$HOME/.tmux.conf.backup.$TIMESTAMP"
log_info "Backed up .tmux.conf"
fi
if [ -f "$HOME/.gitconfig" ]; then
cp "$HOME/.gitconfig" "$HOME/.gitconfig.backup.$TIMESTAMP"
log_info "Backed up .gitconfig"
fi
# Link dotfiles
if [ -f "$DOTFILES_DIR/zsh/zshrc" ]; then
ln -sf "$DOTFILES_DIR/zsh/zshrc" "$HOME/.zshrc"
log_info "Linked .zshrc"
fi
if [ -f "$DOTFILES_DIR/zsh/p10k.zsh" ]; then
ln -sf "$DOTFILES_DIR/zsh/p10k.zsh" "$HOME/.p10k.zsh"
log_info "Linked .p10k.zsh"
fi
if [ -f "$DOTFILES_DIR/tmux/tmux.conf" ]; then
ln -sf "$DOTFILES_DIR/tmux/tmux.conf" "$HOME/.tmux.conf"
log_info "Linked .tmux.conf"
fi
if [ -f "$DOTFILES_DIR/git/gitconfig" ]; then
ln -sf "$DOTFILES_DIR/git/gitconfig" "$HOME/.gitconfig"
log_info "Linked .gitconfig"
fi
# Copy tmux custom widgets if they exist
if [ -d "$DOTFILES_DIR/tmux/.tmux/custom-widgets" ]; then
cp -r "$DOTFILES_DIR/tmux/.tmux/custom-widgets" "$HOME/.tmux/"
log_info "Copied tmux custom widgets"
fi
# Copy cheatsheets
if [ -d "$DOTFILES_DIR/cheatsheets" ]; then
for cheatsheet in "$DOTFILES_DIR/cheatsheets"/*; do
if [ -f "$cheatsheet" ]; then
cp "$cheatsheet" "$HOME/.$(basename "$cheatsheet")"
fi
done
log_info "Copied cheatsheets"
fi
# Neovim config
if [ -d "$DOTFILES_DIR/nvim/nvim" ]; then
if [ -d "$HOME/.config/nvim" ]; then
log_warn "Neovim config already exists. Skipping."
log_info "Dotfiles neovim config available at: $DOTFILES_DIR/nvim/nvim"
else
mkdir -p "$HOME/.config"
ln -sf "$DOTFILES_DIR/nvim/nvim" "$HOME/.config/nvim"
log_info "Linked neovim config"
fi
fi
else
# Create minimal default configs
log_info "Creating default configuration files..."
# Create minimal .zshrc if doesn't exist
if [ ! -f "$HOME/.zshrc" ]; then
cat > ~/.zshrc << 'ZSHEOF'
# Locale settings
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
# Aliases
alias ll='ls -la --color=auto'
alias v='nvim'
alias vim='nvim'
alias t='tmux'
alias python='python3'
# Enable Powerlevel10k instant prompt
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi
# Oh My Zsh
export ZSH="$HOME/.oh-my-zsh"
ZSH_THEME="powerlevel10k/powerlevel10k"
plugins=(git docker kubectl npm node zsh-autosuggestions zsh-syntax-highlighting)
source $ZSH/oh-my-zsh.sh
# Powerlevel10k
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
# PATH
export PATH="$HOME/.local/bin:$PATH"
export PATH="/opt/nvim-linux64/bin:$PATH"
# SDKMAN
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]] && source "$SDKMAN_DIR/bin/sdkman-init.sh"
# Rust
export PATH="$HOME/.cargo/bin:$PATH"
# nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
ZSHEOF
log_info "Created default .zshrc"
fi
fi
log_step "13b" "Creating Linux-compatible tmux widgets..."
WIDGETS_DIR="$HOME/.tmux/custom-widgets"
mkdir -p "$WIDGETS_DIR"
# CPU Widget - uses /proc/stat (Linux)
if [ ! -f "$WIDGETS_DIR/cpu-widget.sh" ] || grep -q "top -l" "$WIDGETS_DIR/cpu-widget.sh" 2>/dev/null; then
cat > "$WIDGETS_DIR/cpu-widget.sh" << 'CPUEOF'
#!/bin/bash
# Tokyo Night CPU Widget for Linux/WSL
# Get CPU usage from /proc/stat (two samples, 0.5s apart)
read -r _ user1 nice1 system1 idle1 iowait1 irq1 softirq1 steal1 _ < /proc/stat
sleep 0.5
read -r _ user2 nice2 system2 idle2 iowait2 irq2 softirq2 steal2 _ < /proc/stat
total1=$((user1 + nice1 + system1 + idle1 + iowait1 + irq1 + softirq1 + steal1))
total2=$((user2 + nice2 + system2 + idle2 + iowait2 + irq2 + softirq2 + steal2))
idle_diff=$((idle2 + iowait2 - idle1 - iowait1))
total_diff=$((total2 - total1))
if [ "$total_diff" -gt 0 ]; then
cpu_usage=$(( (total_diff - idle_diff) * 100 / total_diff ))
else
cpu_usage=0
fi
# Color based on usage (Tokyo Night)
if [ "$cpu_usage" -lt 50 ]; then
color="#a6e3a1" # green
elif [ "$cpu_usage" -lt 80 ]; then
color="#f9e2af" # yellow
else
color="#f38ba8" # red
fi
echo "#[fg=${color},bg=#1a1b26] 󰻠 CPU ${cpu_usage}% "
CPUEOF
chmod +x "$WIDGETS_DIR/cpu-widget.sh"
log_info "Created CPU widget (Linux)"
else
log_warn "CPU widget already exists"
fi
# RAM Widget - uses free (Linux)
if [ ! -f "$WIDGETS_DIR/ram-widget.sh" ] || grep -q "vm_stat\|1e1e2e" "$WIDGETS_DIR/ram-widget.sh" 2>/dev/null; then
cat > "$WIDGETS_DIR/ram-widget.sh" << 'RAMEOF'
#!/bin/bash
# Tokyo Night RAM Widget for Linux/WSL
# Get RAM usage
ram_percent=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100.0}')
# Color based on usage (Tokyo Night)
if [ "$ram_percent" -lt 60 ]; then
color="#a6e3a1" # green
elif [ "$ram_percent" -lt 80 ]; then
color="#f9e2af" # yellow
else
color="#f38ba8" # red
fi
echo "#[fg=${color},bg=#1a1b26] 󰘚 RAM ${ram_percent}% "
RAMEOF
chmod +x "$WIDGETS_DIR/ram-widget.sh"
log_info "Created RAM widget (Linux)"
else
log_warn "RAM widget already exists"
fi
# Network Widget - uses /proc/net/dev (Linux)
if [ ! -f "$WIDGETS_DIR/network-widget.sh" ] || grep -q "ifconfig\|1e1e2e" "$WIDGETS_DIR/network-widget.sh" 2>/dev/null; then
cat > "$WIDGETS_DIR/network-widget.sh" << 'NETEOF'
#!/bin/bash
# Tokyo Night Network Speed Widget for Linux/WSL
# Get default network interface
DEFAULT_INTERFACE=$(ip route 2>/dev/null | grep '^default' | awk '{print $5}' | head -n1)
if [ -z "$DEFAULT_INTERFACE" ]; then
DEFAULT_INTERFACE=$(ip link show 2>/dev/null | grep -E "state UP" | head -n1 | awk -F': ' '{print $2}')
fi
if [ -z "$DEFAULT_INTERFACE" ]; then
echo "#[fg=#89b4fa,bg=#1a1b26] offline "
exit 0
fi
# Read current bytes from /proc/net/dev
read_bytes() {
awk -v iface="$DEFAULT_INTERFACE:" '$1 == iface {print $2, $10}' /proc/net/dev
}
current=$(read_bytes)
rx_bytes=$(echo "$current" | awk '{print $1}')
tx_bytes=$(echo "$current" | awk '{print $2}')
if [ -z "$rx_bytes" ] || [ -z "$tx_bytes" ]; then
echo "#[fg=#89b4fa,bg=#1a1b26] offline "
exit 0
fi
# Cache file for previous values
CACHE_FILE="/tmp/tmux_network_cache"
if [ -f "$CACHE_FILE" ]; then
read -r prev_rx prev_tx prev_time < "$CACHE_FILE"
else
prev_rx=$rx_bytes
prev_tx=$tx_bytes
prev_time=$(date +%s)
fi
current_time=$(date +%s)
time_diff=$((current_time - prev_time))
[ "$time_diff" -eq 0 ] && time_diff=1
# Calculate speeds
rx_speed=$(( (rx_bytes - prev_rx) / time_diff ))
tx_speed=$(( (tx_bytes - prev_tx) / time_diff ))
# Ensure non-negative
[ "$rx_speed" -lt 0 ] && rx_speed=0
[ "$tx_speed" -lt 0 ] && tx_speed=0
# Format bytes to human readable
format_speed() {
local bytes=$1
if [ "$bytes" -lt 1024 ]; then
echo "${bytes}B/s"
elif [ "$bytes" -lt 1048576 ]; then
echo "$((bytes / 1024))K/s"
else
echo "$((bytes / 1048576))M/s"
fi
}
rx_formatted=$(format_speed $rx_speed)
tx_formatted=$(format_speed $tx_speed)
echo "#[fg=#89b4fa,bg=#1a1b26] ↓${rx_formatted} ↑${tx_formatted} "
# Save current values
echo "$rx_bytes $tx_bytes $current_time" > "$CACHE_FILE"
NETEOF
chmod +x "$WIDGETS_DIR/network-widget.sh"
log_info "Created Network widget (Linux)"
else
log_warn "Network widget already exists"
fi
# Date-time widget
if [ ! -f "$WIDGETS_DIR/date-time-widget.sh" ]; then
cat > "$WIDGETS_DIR/date-time-widget.sh" << 'DTEOF'
#!/bin/bash
echo "$(date +'%a %b %d %H:%M')"
DTEOF
chmod +x "$WIDGETS_DIR/date-time-widget.sh"
log_info "Created Date-Time widget"
fi
# Window name widget
if [ ! -f "$WIDGETS_DIR/window-name.sh" ]; then
cat > "$WIDGETS_DIR/window-name.sh" << 'WNEOF'
#!/bin/bash
path="$1"
cmd="$2"
# Show last 2 path segments
short_path=$(echo "$path" | awk -F'/' '{if(NF>2) print $(NF-1)"/"$NF; else print $0}')
echo "$short_path"
WNEOF
chmod +x "$WIDGETS_DIR/window-name.sh"
log_info "Created Window-Name widget"
fi
# Git branch widget
if [ ! -f "$WIDGETS_DIR/git-branch-widget.sh" ]; then
cat > "$WIDGETS_DIR/git-branch-widget.sh" << 'GBEOF'
#!/bin/bash
path="$1"
if [ -d "$path/.git" ] || git -C "$path" rev-parse --git-dir > /dev/null 2>&1; then
branch=$(git -C "$path" symbolic-ref --short HEAD 2>/dev/null || git -C "$path" rev-parse --short HEAD 2>/dev/null)
if [ -n "$branch" ]; then
echo " ⎇ $branch"
fi
fi
GBEOF
chmod +x "$WIDGETS_DIR/git-branch-widget.sh"
log_info "Created Git-Branch widget"
fi
log_step 14 "Updating .bashrc..."
if ! grep -q '# >>> ubuntu-in-windows-setup >>>' "$HOME/.bashrc" 2>/dev/null; then
cat >> ~/.bashrc << 'EOF'
# >>> ubuntu-in-windows-setup >>>
export LANG=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export PATH="$HOME/.local/bin:$PATH"
export PATH="/opt/nvim-linux64/bin:$PATH"
# SDKMAN
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$SDKMAN_DIR/bin/sdkman-init.sh" ]] && source "$SDKMAN_DIR/bin/sdkman-init.sh"
# Rust
export PATH="$HOME/.cargo/bin:$PATH"
# nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# Aliases
alias vim='nvim'
alias python='python3'
alias t='tmux'
# <<< ubuntu-in-windows-setup <<<
EOF
log_info "Updated .bashrc"
else
log_warn ".bashrc already configured"
fi
log_step 15 "Verifying installations..."
verify_install() {
local name=$1
local check=$2
if eval "$check"; then
log_info "$name: OK"
else
log_warn "$name: Not found (optional)"
fi
}
verify_install "Oh-My-Zsh" "[ -d '$HOME/.oh-my-zsh' ]"
verify_install "Powerlevel10k" "[ -d '$HOME/.oh-my-zsh/custom/themes/powerlevel10k' ]"
verify_install "TPM" "[ -d '$HOME/.tmux/plugins/tpm' ]"
verify_install "Neovim" "command_exists nvim"
verify_install "Tmux" "command_exists tmux"
verify_install "Zsh" "command_exists zsh"
log_step 16 "Setting zsh as default shell..."
if [ "$SHELL" = "$(which zsh)" ]; then
log_warn "Zsh is already the default shell"
else
# Set zsh as default shell
if command -v chsh >/dev/null 2>&1; then
sudo chsh -s "$(which zsh)" "$USER" 2>/dev/null || chsh -s "$(which zsh)" 2>/dev/null || log_warn "Could not set zsh as default shell. Run manually: chsh -s \$(which zsh)"
if [ $? -eq 0 ]; then
log_info "Zsh set as default shell (restart terminal to take effect)"
fi
else
log_warn "chsh command not found. Zsh installed but not set as default."
fi
fi
echo ""
echo "========================================"
echo " Installation Complete!"
echo "========================================"
echo ""
echo "Languages Installed:"
echo " Python: $(python3 --version 2>&1)"
echo " Java: $(java --version 2>&1 | head -1)"
echo " Go: $(go version 2>&1)"
echo " Rust: $(cargo --version 2>&1)"
echo " Ruby: $(ruby --version 2>&1)"
echo " Lua: $(lua5.4 -v 2>&1)"
echo " Node: $(node --version 2>&1)"
echo " TypeScript: $(tsc --version 2>&1)"
echo ""
echo "Tools Installed:"
echo " Neovim: $(nvim --version 2>&1 | head -1)"
echo " Tmux: $(tmux -V 2>&1)"
echo " Zsh: $(zsh --version 2>&1)"
echo ""
echo "Next Steps:"
echo " 1. Close and reopen your terminal (zsh is now your default shell)"
echo " 2. Configure PowerLevel10k: p10k configure"
echo " 3. Start tmux: tmux new-session -s main"
echo " 4. In tmux, press Ctrl+a then Shift+I to install plugins"
echo ""

Install and Setup WSL on Windows 11

  • Open an elevated PowerShell prompt.

Install and setup WSL

  • Enable Windows Subsystem for Linux feature.
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
  • Reboot your Windows.
  • Make sure WSL has been updated to version 2. If not, you can download the update package here.
  • Set WSL default version to 2:
wsl --set-default-version 2

For all steps above, follow this guide by Microsoft.

Install and setup AlpineWSL

  • Download AlpineWSL by yuk7 and extract it on your computer. Inside the Alpine folder you'll find a file called Alpine.exe. Execute it to install AlpineWSL.
    • NOTE: I've extracted my Alpine folder to C:\WSL\Alpine.
  • Make Alpine your default Linux distro.
wsl -s Alpine
  • Connect to AlpineWSL.
wsl
  • Install wget.
apk add wget
wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.33-r0/glibc-2.33-r0.apk
apk add glibc-2.33-r0.apk

Finalize and test

  • Shut down WSL.
wsl --shutdown
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment