Skip to content

Instantly share code, notes, and snippets.

@danielnv18
Last active October 28, 2025 20:11
Show Gist options
  • Select an option

  • Save danielnv18/8774a1200548b84abafb98e481e15e52 to your computer and use it in GitHub Desktop.

Select an option

Save danielnv18/8774a1200548b84abafb98e481e15e52 to your computer and use it in GitHub Desktop.
WSL Ubuntu setup
#!/usr/bin/env bash
# Quick install:
# bash <(curl -fsSL https://gist.githubusercontent.com/danielnv18/8774a1200548b84abafb98e481e15e52/raw/setup-wsl-ubuntu.sh) --name "Your Name" --email "your@email.com"
set -euo pipefail
# =========================
# Ubuntu on WSL Bootstrapper
# - Git configuration (name, email)
# - SSH keys from Windows
# - Neovim (AppImage, stable)
# - LazyVim
# - Zsh + Oh My Zsh
# - NVM + latest LTS Node + latest npm (nounset-safe)
# - Oh My Posh (user install to ~/.local/bin), init with default
# - Oh My Zsh plugins: zsh-autosuggestions, zsh-syntax-highlighting
# =========================
# Global variables for git config
GIT_NAME=""
GIT_EMAIL=""
log() { printf "\n[%s] %s\n" "$(date +'%H:%M:%S')" "$*"; }
die() { echo "Error: $*" >&2; exit 1; }
need_cmd() { command -v "$1" >/dev/null 2>&1; }
show_usage() {
cat <<EOF
Usage: $0 --name "Your Name" --email "your@email.com"
Required arguments:
--name Your full name for git configuration
--email Your email address for git configuration
Example:
$0 --name "John Doe" --email "john@example.com"
EOF
exit 1
}
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
--name)
GIT_NAME="$2"
shift 2
;;
--email)
GIT_EMAIL="$2"
shift 2
;;
-h|--help)
show_usage
;;
*)
echo "Unknown option: $1"
show_usage
;;
esac
done
if [ -z "$GIT_NAME" ] || [ -z "$GIT_EMAIL" ]; then
echo "Error: Both --name and --email are required."
echo
show_usage
fi
}
require_root_apt() {
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
if need_cmd sudo; then
sudo -v || die "sudo is required."
APT="sudo apt-get"
else
die "This script needs root privileges for apt operations (install sudo or run as root)."
fi
else
APT="apt-get"
fi
}
detect_wsl() {
if grep -qi microsoft /proc/version 2>/dev/null; then
log "WSL environment detected."
else
log "Warning: This doesn't appear to be a WSL environment."
fi
}
install_base_packages() {
require_root_apt
log "Updating apt package lists…"
$APT update -y
log "Installing base packages…"
$APT install -y \
curl wget git unzip tar ca-certificates \
zsh fzf ripgrep fd-find \
libfuse2 \
build-essential
# Provide 'fd' alias for Ubuntu's 'fdfind'
if ! need_cmd fd && need_cmd fdfind; then
if [ ! -e /usr/local/bin/fd ]; then
sudo ln -s "$(command -v fdfind)" /usr/local/bin/fd
fi
fi
}
configure_git() {
if ! need_cmd git; then die "git not installed."; fi
log "Configuring git user information…"
git config --global user.name "$GIT_NAME"
git config --global user.email "$GIT_EMAIL"
log "Git configured with:"
echo " Name: $GIT_NAME"
echo " Email: $GIT_EMAIL"
}
copy_ssh_keys_from_windows() {
log "Setting up SSH keys from Windows…"
# Find Windows user directory
local win_user_home
if [ -d "/mnt/c/Users" ]; then
# Try to find the Windows username
win_user_home="/mnt/c/Users/${USER}"
if [ ! -d "$win_user_home" ]; then
# Fallback: try to find a user directory (excluding Public, Default, etc.)
win_user_home=$(find /mnt/c/Users -maxdepth 1 -type d ! -name "Public" ! -name "Default*" ! -name "All Users" ! -name "Users" 2>/dev/null | head -n 1)
fi
fi
if [ -z "$win_user_home" ] || [ ! -d "$win_user_home" ]; then
log "Warning: Could not find Windows user home directory."
log "Skipping SSH key copy. You can manually copy keys later."
return 0
fi
local win_ssh_dir="$win_user_home/.ssh"
local wsl_ssh_dir="$HOME/.ssh"
if [ ! -d "$win_ssh_dir" ]; then
log "No SSH directory found at $win_ssh_dir"
log "Skipping SSH key copy. Generate new keys with: ssh-keygen -t ed25519 -C '$GIT_EMAIL'"
return 0
fi
mkdir -p "$wsl_ssh_dir"
chmod 700 "$wsl_ssh_dir"
local keys_copied=0
# Copy common SSH key files
for keyfile in id_rsa id_rsa.pub id_ed25519 id_ed25519.pub id_ecdsa id_ecdsa.pub; do
if [ -f "$win_ssh_dir/$keyfile" ]; then
log "Copying $keyfile…"
cp "$win_ssh_dir/$keyfile" "$wsl_ssh_dir/"
# Set correct permissions
if [[ "$keyfile" == *.pub ]]; then
chmod 644 "$wsl_ssh_dir/$keyfile"
else
chmod 600 "$wsl_ssh_dir/$keyfile"
fi
keys_copied=$((keys_copied + 1))
fi
done
# Copy config if it exists
if [ -f "$win_ssh_dir/config" ]; then
log "Copying SSH config…"
cp "$win_ssh_dir/config" "$wsl_ssh_dir/"
chmod 600 "$wsl_ssh_dir/config"
fi
# Copy known_hosts if it exists
if [ -f "$win_ssh_dir/known_hosts" ]; then
log "Copying known_hosts…"
cp "$win_ssh_dir/known_hosts" "$wsl_ssh_dir/"
chmod 644 "$wsl_ssh_dir/known_hosts"
fi
if [ $keys_copied -gt 0 ]; then
log "Successfully copied $keys_copied SSH key(s) from Windows."
log "SSH keys are ready to use."
else
log "No SSH keys found in $win_ssh_dir"
log "Generate new keys with: ssh-keygen -t ed25519 -C '$GIT_EMAIL'"
fi
}
install_neovim_appimage() {
local target="/usr/local/bin/nvim"
local tmpfile
tmpfile="$(mktemp)"
if need_cmd nvim; then
log "Neovim already installed. Skipping."
return 0
fi
log "Downloading Neovim AppImage (stable)…"
curl -fsSL -o "$tmpfile" "https://github.com/neovim/neovim/releases/download/stable/nvim-linux-x86_64.appimage" \
|| die "Failed to download Neovim AppImage."
require_root_apt
sudo install -m 0755 "$tmpfile" "$target"
rm -f "$tmpfile"
sudo chmod +x "$target"
log "Neovim installed at $target"
}
install_lazyvim() {
if ! need_cmd nvim; then die "Neovim not found. Install Neovim first."; fi
local nvdir="$HOME/.config/nvim"
if [ -d "$nvdir" ] && [ -f "$nvdir/init.lua" ]; then
log "Existing Neovim config found. Skipping LazyVim clone."
return 0
fi
log "Installing LazyVim starter into $nvdir…"
mkdir -p "$(dirname "$nvdir")"
git clone --depth 1 https://github.com/LazyVim/starter "$nvdir"
rm -rf "$nvdir/.git"
log "LazyVim starter installed."
}
install_oh_my_zsh() {
if [ -d "$HOME/.oh-my-zsh" ]; then
log "Oh My Zsh already installed. Skipping."
return 0
fi
if ! need_cmd zsh; then die "zsh not installed."; fi
log "Installing Oh My Zsh (unattended)…"
export RUNZSH=no CHSH=no KEEP_ZSHRC=yes
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" \
|| die "Oh My Zsh install failed."
log "Oh My Zsh installed."
}
# Install Oh My Zsh plugins (autosuggestions + syntax-highlighting)
install_zsh_plugins() {
local zsh_custom="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}"
mkdir -p "$zsh_custom/plugins"
# zsh-autosuggestions
if [ -d "$zsh_custom/plugins/zsh-autosuggestions" ]; then
log "zsh-autosuggestions already present."
else
log "Installing zsh-autosuggestions…"
git clone https://github.com/zsh-users/zsh-autosuggestions "$zsh_custom/plugins/zsh-autosuggestions"
fi
# zsh-syntax-highlighting (must be last in plugins= order)
if [ -d "$zsh_custom/plugins/zsh-syntax-highlighting" ]; then
log "zsh-syntax-highlighting already present."
else
log "Installing zsh-syntax-highlighting…"
git clone https://github.com/zsh-users/zsh-syntax-highlighting "$zsh_custom/plugins/zsh-syntax-highlighting"
fi
}
install_nvm_node() {
if [ ! -d "$HOME/.nvm" ]; then
log "Installing NVM…"
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
else
log "NVM already present. Ensuring Node LTS and latest npm…"
fi
# NVM is not nounset-safe; temporarily disable -u
_had_nounset=false
case $- in *u*) _had_nounset=true; set +u ;; esac
if [ -s "$HOME/.nvm/nvm.sh" ]; then
. "$HOME/.nvm/nvm.sh"
else
$_had_nounset && set -u
die "NVM script not found after installation."
fi
log "Installing latest LTS Node…"
nvm install --lts
nvm alias default 'lts/*'
nvm use --lts
log "Updating npm to latest…"
npm i -g npm@latest || true
$_had_nounset && set -u
}
install_oh_my_posh() {
# Ensure ~/.local/bin exists and is in PATH for THIS process
mkdir -p "$HOME/.local/bin"
case ":$PATH:" in *":$HOME/.local/bin:"*) ;; *) export PATH="$HOME/.local/bin:$PATH" ;; esac
if need_cmd oh-my-posh; then
log "Oh My Posh already installed."
else
log "Installing Oh My Posh (user)…"
bash -c 'curl -s https://ohmyposh.dev/install.sh | bash -s' || die "Oh My Posh install failed."
fi
}
configure_zshrc() {
local zrc="$HOME/.zshrc"
mkdir -p "$HOME"
touch "$zrc"
# Ensure plugins=(git zsh-autosuggestions zsh-syntax-highlighting)
if grep -qE '^\s*plugins=\(' "$zrc"; then
log "Updating plugins list in .zshrc…"
# Replace any existing plugins=() line with our desired order
# shellcheck disable=SC2016
sed -i 's/^\s*plugins=.*/plugins=(git zsh-autosuggestions zsh-syntax-highlighting)/' "$zrc"
else
log "Adding plugins list to .zshrc…"
printf '\nplugins=(git zsh-autosuggestions zsh-syntax-highlighting)\n' >> "$zrc"
fi
# Managed block (PATH, NVM, editor, oh-my-posh init, fzf defaults)
if grep -qF "# >>> wsl-setup managed start >>>" "$zrc"; then
log "Updating managed block in .zshrc…"
awk -v start="# >>> wsl-setup managed start >>>" -v end="# <<< wsl-setup managed end <<<" '
$0==start {inblock=1; next}
$0==end {inblock=0; next}
!inblock {print}
' "$zrc" > "${zrc}.tmp" && mv "${zrc}.tmp" "$zrc"
else
log "Adding managed block to .zshrc…"
fi
# Single-quoted heredoc so nothing expands now; variables expand at shell runtime.
cat >> "$zrc" <<'EOF'
# >>> wsl-setup managed start >>>
# Ensure user-local bin on PATH (Oh My Posh lives here)
export PATH="$HOME/.local/bin:$PATH"
# Load NVM if installed
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
# Prefer Neovim
export EDITOR="nvim"
export VISUAL="nvim"
alias vi="nvim"
alias vim="nvim"
# Initialize Oh My Posh with default config
if command -v oh-my-posh >/dev/null 2>&1; then
eval "$(oh-my-posh init zsh)"
fi
# fzf defaults (optional)
export FZF_DEFAULT_COMMAND='fd --hidden --follow --exclude .git'
export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
# <<< wsl-setup managed end <<<
EOF
}
set_default_shell_zsh() {
if [ -x "$(command -v zsh)" ]; then
local current_shell
current_shell="$(getent passwd "$USER" | cut -d: -f7 || echo "")"
if [ "$current_shell" != "$(command -v zsh)" ]; then
log "Setting zsh as default shell…"
chsh -s "$(command -v zsh)" || log "Could not change shell automatically. Run: chsh -s $(command -v zsh)"
fi
fi
}
main() {
parse_args "$@"
detect_wsl
log "Starting Ubuntu on WSL setup…"
log "Git will be configured with: $GIT_NAME <$GIT_EMAIL>"
install_base_packages
configure_git
copy_ssh_keys_from_windows
install_neovim_appimage
install_lazyvim
install_oh_my_zsh
install_zsh_plugins
install_nvm_node
install_oh_my_posh
configure_zshrc
set_default_shell_zsh
log "All done."
echo
echo "Next steps:"
echo " - Run: exec zsh (reload shell with PATH, OMZ plugins, and Oh My Posh)"
echo " - Open Neovim: nvim (LazyVim bootstraps on first run)."
echo " - In Windows Terminal, choose a Nerd Font for proper glyphs."
echo " - Test SSH: ssh -T git@github.com (or your git host)"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment