Last active
October 28, 2025 20:11
-
-
Save danielnv18/8774a1200548b84abafb98e481e15e52 to your computer and use it in GitHub Desktop.
WSL Ubuntu setup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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