#!/bin/bash # 02-dev-tools-setup.sh # Installs various development tools and utilities. # Supports NixOS (declarative via flake), Fedora, and Debian/Ubuntu. # Relies on DISTRO and PACKAGE_MANAGER being set by the caller. echo "--- Starting Development Tools Setup ---" if [ -z "$DISTRO" ] || [ -z "$PACKAGE_MANAGER" ]; then echo "ERROR: DISTRO and PACKAGE_MANAGER must be set in the environment." exit 1 fi # ── NixOS path ───────────────────────────────────────────── if [ "$DISTRO" == "nixos" ]; then echo "NixOS detected: development tools are managed declaratively" echo "via nixos/flake.nix and nixos/home.nix." echo "" echo "The following packages are already configured in the flake:" echo " - Go (go, gopls, delve)" echo " - Ruby on Rails (ruby, bundler, rails, rbenv, nodejs)" echo " - Python (python3, pip, virtualenv)" echo " - Neovim (latest)" echo " - Cloud/DevOps (kubectl, k9s, terraform, helm, awscli2)" echo " - Other tools (oh-my-posh, fzf, bat, eza, ripgrep, fd)" echo "" echo "Run the main-setup.sh NixOS workflow or apply manually:" echo "" echo " sudo nixos-rebuild switch --flake ~/dotfiles/nixos#nixos" echo " home-manager switch --flake ~/dotfiles/nixos#blake" echo "" echo "Skipping imperative dev tools setup." echo "--- Development Tools Setup Finished ---" exit 0 fi # ═══════════════════════════════════════════════════════════ # TRADITIONAL DISTRO PATH (Fedora / Ubuntu / Debian / Pop) # ═══════════════════════════════════════════════════════════ # Setup NVIM echo "Setting up Neovim..." _nvim_asset_name() { case "$(uname -m)" in x86_64) echo "nvim-linux-x86_64" ;; aarch64) echo "nvim-linux-arm64" ;; *) echo "Unsupported arch for Neovim: $(uname -m)" >&2; exit 1 ;; esac } NVIM_LATEST="$(curl -fsSL https://api.github.com/repos/neovim/neovim/releases/latest | grep '"tag_name"' | cut -d'"' -f4)" NVIM_INSTALLED="$(nvim --version 2>/dev/null | head -n1 | awk '{print $2}')" if [ "$NVIM_INSTALLED" = "$NVIM_LATEST" ]; then echo "Neovim ${NVIM_LATEST} already up-to-date." else NVIM_ASSET="$(_nvim_asset_name)" echo "Installing Neovim ${NVIM_LATEST} (${NVIM_ASSET})..." TEMP_NVIM_DIR=$(mktemp -d) curl -fsSLo "${TEMP_NVIM_DIR}/${NVIM_ASSET}.tar.gz" \ "https://github.com/neovim/neovim/releases/latest/download/${NVIM_ASSET}.tar.gz" sudo rm -rf /opt/nvim sudo tar -C /opt -xzf "${TEMP_NVIM_DIR}/${NVIM_ASSET}.tar.gz" sudo mv "/opt/${NVIM_ASSET}" /opt/nvim 2>/dev/null || true rm -rf "${TEMP_NVIM_DIR}" sudo ln -sf /opt/nvim/bin/nvim /usr/local/bin/nvim echo "Neovim ${NVIM_LATEST} installed to /opt/nvim, symlinked to /usr/local/bin/nvim" fi # pynvim echo "Installing pynvim for Neovim Python support..." if /usr/bin/python3 -m pip show pynvim &>/dev/null; then echo "pynvim already installed." else /usr/bin/python3 -m pip install --user pynvim echo "pynvim installed for the current user." fi # Install Nerd Font (Hack) echo "Installing Hack Nerd Font..." NERDFONT_VERSION="v3.2.1" # Or use "latest" if API allows, else check manually NERDFONT_NAME="Hack" FONT_DIR="$HOME/.local/share/fonts" mkdir -p "$FONT_DIR" # Check if a Hack Nerd Font is already installed to avoid re-downloading if fc-list | grep -qi "Hack Nerd Font"; then echo "Hack Nerd Font already installed." else echo "Downloading and installing Hack Nerd Font..." TEMP_FONT_DIR=$(mktemp -d) wget -qO "${TEMP_FONT_DIR}/${NERDFONT_NAME}.zip" "https://github.com/ryanoasis/nerd-fonts/releases/download/${NERDFONT_VERSION}/${NERDFONT_NAME}.zip" if [ $? -eq 0 ]; then unzip -q "${TEMP_FONT_DIR}/${NERDFONT_NAME}.zip" -d "${TEMP_FONT_DIR}/${NERDFONT_NAME}NerdFont" # Copy only .ttf or .otf files find "${TEMP_FONT_DIR}/${NERDFONT_NAME}NerdFont" \( -name "*.ttf" -o -name "*.otf" \) -exec cp {} "$FONT_DIR/" \; echo "Updating font cache..." fc-cache -f -v echo "Hack Nerd Font installed." else echo "ERROR: Failed to download Hack Nerd Font." fi rm -rf "${TEMP_FONT_DIR}" # Clean up fi OS="$(uname | tr '[:upper:]' '[:lower:]')" ARCH_RAW="$(uname -m)" case "${ARCH_RAW}" in x86_64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; *) echo "Unsupported arch: ${ARCH_RAW}" >&2; exit 1 ;; esac _add_to_rc() { local rcfile="$1" snippet="$2" grep -qxF "${snippet}" "${rcfile}" 2>/dev/null || { echo "# added by 02-dev-tools-setup.sh" >> "${rcfile}" echo "${snippet}" >> "${rcfile}" echo " → updated ${rcfile}" } } if ! command -v go &>/dev/null; then echo "➤ Fetching latest Go version..." LATEST_GO_FULL="$(curl -fsSL https://go.dev/VERSION?m=text)" # e.g. "go1.21.4" LATEST_GO="${LATEST_GO_FULL#go}" TAR="go${LATEST_GO}.${OS}-${ARCH}.tar.gz" URL="https://go.dev/dl/${TAR}" echo " latest is ${LATEST_GO_FULL}, downloading ${TAR}..." TMPDIR="$(mktemp -d)" curl -fsSL "${URL}" -o "${TMPDIR}/${TAR}" sudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf "${TMPDIR}/${TAR}" rm -rf "${TMPDIR}" echo "Installed Go ${LATEST_GO_FULL} to /usr/local/go" # ensure PATH in your RCs SNIPPET='export PATH=$PATH:/usr/local/go/bin' for rc in "${HOME}/.bashrc" "${HOME}/.zshrc"; do [ -f "${rc}" ] && _add_to_rc "${rc}" "${SNIPPET}" done else INSTALLED_FULL="$(go version | awk '{print $3}')" # e.g. "go1.21.4" LATEST_GO_FULL="$(curl -fsSL https://go.dev/VERSION?m=text)" if [ "${INSTALLED_FULL}" = "${LATEST_GO_FULL}" ]; then echo "Go ${INSTALLED_FULL} already up-to-date" else echo "Go ${INSTALLED_FULL} installed but ${LATEST_GO_FULL} is available." echo " rerun this script to upgrade or uninstall manually." fi fi echo if ! command -v oh-my-posh &>/dev/null; then echo "➤ Installing Oh-My-Posh latest release..." # GitHub /releases/latest/download will 302 redirect to actual asset BIN_URL="https://github.com/JanDeDobbeleer/oh-my-posh/releases/latest/download/posh-${OS}-${ARCH}" mkdir -p "${HOME}/.local/bin" TMPBIN="$(mktemp)" curl -fsSL -L "${BIN_URL}" -o "${TMPBIN}" install -m755 "${TMPBIN}" "${HOME}/.local/bin/oh-my-posh" rm -f "${TMPBIN}" echo "Installed oh-my-posh to ~/.local/bin/oh-my-posh" echo "Theme is managed via dotfiles repo at terminal/posh-theme.omp.json" echo "Run 04-config-symlinks.sh to link it to ~/.config/oh-my-posh/theme.omp.json" else echo "✔ Oh-My-Posh already installed: $(oh-my-posh --version | head -n1)" fi # ── Ruby on Rails ────────────────────────────────────────── echo "" echo "--- Setting up Ruby on Rails ---" if ! command -v ruby &>/dev/null; then echo "➤ Installing Ruby via rbenv..." # Install rbenv if [ ! -d "$HOME/.rbenv" ]; then git clone https://github.com/rbenv/rbenv.git "$HOME/.rbenv" git clone https://github.com/rbenv/ruby-build.git "$HOME/.rbenv/plugins/ruby-build" echo 'export PATH="$PATH:$HOME/.rbenv/bin"' >> "$HOME/.bashrc" echo 'export PATH="$PATH:$HOME/.rbenv/bin"' >> "$HOME/.zshrc" echo 'eval "$(rbenv init -)"' >> "$HOME/.bashrc" echo 'eval "$(rbenv init -)"' >> "$HOME/.zshrc" echo "rbenv installed. A new shell is needed or run:" echo " export PATH=\"\$PATH:\$HOME/.rbenv/bin\" && eval \"\$(rbenv init -)\"" else echo "rbenv already installed." fi # Install Ruby latest stable via rbenv export PATH="$PATH:$HOME/.rbenv/bin" eval "$(rbenv init - bash 2>/dev/null || true)" if command -v rbenv &>/dev/null; then RUBY_LATEST=$(rbenv install -l 2>/dev/null | grep -v - | tail -1) if [ -n "$RUBY_LATEST" ]; then echo "➤ Installing Ruby ${RUBY_LATEST} (this may take a while)..." rbenv install "$RUBY_LATEST" rbenv global "$RUBY_LATEST" echo "Ruby ${RUBY_LATEST} installed and set as global version." fi fi else echo "✔ Ruby already installed: $(ruby --version 2>/dev/null | head -n1)" fi # Install Bundler if command -v gem &>/dev/null; then if ! command -v bundler &>/dev/null; then echo "➤ Installing Bundler..." gem install bundler echo "Bundler installed." else echo "✔ Bundler already installed." fi # Install Rails if ! command -v rails &>/dev/null; then echo "➤ Installing Rails..." gem install rails rbenv rehash 2>/dev/null || true echo "Rails installed." else echo "✔ Rails already installed: $(rails --version 2>/dev/null | head -n1)" fi fi echo "--- Development Tools Setup Finished ---" # ── Helm (Kubernetes package manager) ────────────────────── echo "" echo "--- Installing Helm ---" if command -v helm &>/dev/null; then echo "✔ Helm already installed: $(helm version --short 2>/dev/null | head -n1)" else echo "➤ Fetching latest Helm version..." HELM_LATEST="$(curl -fsSL https://api.github.com/repos/helm/helm/releases/latest | grep '"tag_name"' | cut -d'"' -f4)" # e.g. "v3.16.0" echo " latest is ${HELM_LATEST}, downloading..." TMPDIR="$(mktemp -d)" curl -fsSL "https://get.helm.sh/helm-${HELM_LATEST}-linux-${ARCH}.tar.gz" -o "${TMPDIR}/helm.tar.gz" tar -C "${TMPDIR}" -xzf "${TMPDIR}/helm.tar.gz" sudo install -m755 "${TMPDIR}/linux-${ARCH}/helm" /usr/local/bin/helm rm -rf "${TMPDIR}" echo "Installed Helm ${HELM_LATEST} to /usr/local/bin/helm" fi # --- kubectx + kubens (fast k8s context/namespace switching) --- echo "" echo "--- Installing kubectx & kubens ---" _install_kubectx_kubens() { KUBECTX_VERSION="v0.9.5" echo " version: ${KUBECTX_VERSION}" TMPDIR="$(mktemp -d)" curl -fsSL "https://raw.githubusercontent.com/ahmetb/kubectx/${KUBECTX_VERSION}/kubectx" \ -o "${TMPDIR}/kubectx" curl -fsSL "https://raw.githubusercontent.com/ahmetb/kubectx/${KUBECTX_VERSION}/kubens" \ -o "${TMPDIR}/kubens" sudo install -m755 "${TMPDIR}/kubectx" /usr/local/bin/kubectx sudo install -m755 "${TMPDIR}/kubens" /usr/local/bin/kubens rm -rf "${TMPDIR}" echo "✔ kubectx & kubens installed to /usr/local/bin/" } if command -v kubectx &>/dev/null && command -v kubens &>/dev/null; then echo "✔ kubectx & kubens already installed" else _install_kubectx_kubens fi # --- stern (multi-pod/container log tailing for k8s) --- echo "" echo "--- Installing stern ---" _stern_version() { curl -fsSL "https://api.github.com/repos/stern/stern/releases/latest" \ | grep '"tag_name"' | cut -d'"' -f4 } if command -v stern &>/dev/null; then echo "✔ stern already installed" else STERN_VER="$(_stern_version)" echo " latest is ${STERN_VER}, downloading..." TMPDIR="$(mktemp -d)" STERN_URL="https://github.com/stern/stern/releases/download/${STERN_VER}/stern_${STERN_VER#v}_linux_${ARCH}.tar.gz" curl -fsSL "${STERN_URL}" -o "${TMPDIR}/stern.tar.gz" tar -C "${TMPDIR}" -xzf "${TMPDIR}/stern.tar.gz" stern sudo install -m755 "${TMPDIR}/stern" /usr/local/bin/stern rm -rf "${TMPDIR}" echo "✔ stern ${STERN_VER} installed to /usr/local/bin/stern" fi # --- dive (Docker image layer explorer) --- echo "" echo "--- Installing dive ---" _dive_version() { curl -fsSL "https://api.github.com/repos/wagoodman/dive/releases/latest" \ | grep '"tag_name"' | cut -d'"' -f4 } if command -v dive &>/dev/null; then echo "✔ dive already installed" else DIVE_VER="$(_dive_version)" echo " latest is ${DIVE_VER}, downloading..." DIVE_URL="https://github.com/wagoodman/dive/releases/download/${DIVE_VER}/dive_${DIVE_VER#v}_linux_${ARCH}.deb" TMPDIR="$(mktemp -d)" curl -fsSL "${DIVE_URL}" -o "${TMPDIR}/dive.deb" if [ "${PACKAGE_MANAGER}" = "apt" ]; then sudo dpkg -i "${TMPDIR}/dive.deb" 2>/dev/null || sudo apt-get install -f -y else # For rpm-based distros, extract binary from deb ar x "${TMPDIR}/dive.deb" data.tar.gz 2>/dev/null \ && tar -C "${TMPDIR}" -xzf data.tar.gz ./usr/local/bin/dive \ && sudo install -m755 "${TMPDIR}/usr/local/bin/dive" /usr/local/bin/dive \ || echo "⚠ Could not install dive from .deb on non-apt system. Try manual install." fi rm -rf "${TMPDIR}" echo "✔ dive ${DIVE_VER} installed" fi # --- trivy (container/filesystem vulnerability scanner) --- echo "" echo "--- Installing trivy ---" if command -v trivy &>/dev/null; then echo "✔ trivy already installed: $(trivy --version 2>/dev/null | head -n1)" else echo "➤ Downloading trivy via official install script..." curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \ | sudo sh -s -- -b /usr/local/bin echo "✔ trivy installed to /usr/local/bin/trivy" fi # --- AWS CLI v2 --- echo "" echo "--- Installing AWS CLI v2 ---" _awscli_install() { TMPDIR="$(mktemp -d)" curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-${ARCH_RAW}.zip" \ -o "${TMPDIR}/awscliv2.zip" unzip -q "${TMPDIR}/awscliv2.zip" -d "${TMPDIR}" sudo "${TMPDIR}/aws/install" --update rm -rf "${TMPDIR}" echo "✔ AWS CLI v2 installed" } if command -v aws &>/dev/null; then echo "✔ AWS CLI already installed: $(aws --version 2>&1)" else _awscli_install fi