#!/bin/bash # Path: Scripts/base.sh set -e BLUE='\033[1;34m' YELLOW='\033[1;33m' GREEN='\033[1;32m' RED='\033[1;31m' NC='\033[0m' DOTFILES_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" REPO_ROOT="$(dirname "$DOTFILES_DIR")" source "$DOTFILES_DIR/lib/distro.sh" OH_MY_ZSH_REPO="https://github.com/ohmyzsh/ohmyzsh.git" OH_MY_ZSH_COMMIT="b26b5002633e865b70e17933536fe4dc99127898" ZSH_SYNTAX_HIGHLIGHTING_REPO="https://github.com/zsh-users/zsh-syntax-highlighting.git" ZSH_SYNTAX_HIGHLIGHTING_COMMIT="5eb677bb0fa9a3e60f0eff031dc13926e093df92" ZSH_AUTOSUGGESTIONS_REPO="https://github.com/zsh-users/zsh-autosuggestions.git" ZSH_AUTOSUGGESTIONS_COMMIT="85919cd1ffa7d2d5412f6d3fe437ebdbeeec4fc5" RCLONE_VERSION="v1.74.2" EARTHLY_VERSION="v0.8.16" clone_pinned_repo() { local repo_url="$1" local destination="$2" local commit="$3" local label="$4" if [ -d "$destination" ]; then echo -e "${GREEN} LOG: $label already present.${NC}" return 0 fi echo -e "${BLUE} LOG:${YELLOW} Installing $label at pinned commit ${commit:0:12}...${NC}" git clone "$repo_url" "$destination" git -C "$destination" checkout "$commit" if [ "$(git -C "$destination" rev-parse HEAD)" != "$commit" ]; then echo -e "${RED} ERROR: Failed to pin $label to expected commit $commit${NC}" exit 1 fi } install_debian_docker_from_repo() { local docker_repo_os="$OS" local docker_codename="${VERSION_CODENAME:-}" local dpkg_arch if [[ "$docker_repo_os" != "ubuntu" && "$docker_repo_os" != "debian" ]]; then docker_repo_os="ubuntu" fi if [ -z "$docker_codename" ] && command -v lsb_release >/dev/null 2>&1; then docker_codename="$(lsb_release -cs)" fi if [ -z "$docker_codename" ]; then echo -e "${RED} ERROR: Unable to determine Debian/Ubuntu codename for Docker repository setup.${NC}" exit 1 fi dpkg_arch="$(dpkg --print-architecture)" sudo apt-get remove -y docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc >/dev/null 2>&1 || true sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL "https://download.docker.com/linux/${docker_repo_os}/gpg" | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo "deb [arch=${dpkg_arch} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${docker_repo_os} ${docker_codename} stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null sudo apt-get update sudo DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable --now docker } download_and_verify_release_asset() { local asset_url="$1" local checksum_url="$2" local asset_name="$3" local destination_path="$4" local checksum_file local expected_checksum local actual_checksum checksum_file="$(mktemp)" curl -fLsS "$asset_url" -o "$destination_path" curl -fLsS "$checksum_url" -o "$checksum_file" expected_checksum="$(awk -v asset="$asset_name" '$2 == asset { print $1; exit }' "$checksum_file")" if [ -z "$expected_checksum" ]; then rm -f "$checksum_file" echo -e "${RED} ERROR: Could not find checksum for $asset_name in $checksum_url${NC}" exit 1 fi actual_checksum="$(sha256sum "$destination_path" | awk '{print $1}')" if [ "$actual_checksum" != "$expected_checksum" ]; then rm -f "$checksum_file" echo -e "${RED} ERROR: Checksum verification failed for $asset_name${NC}" exit 1 fi rm -f "$checksum_file" } ensure_git_credential_helper() { local helper_value="$1" local existing_helpers="" existing_helpers=$(git config --global --get-all credential.helper 2>/dev/null || true) if printf '%s\n' "$existing_helpers" | grep -Fx -- "$helper_value" >/dev/null 2>&1; then return 0 fi git config --global --add credential.helper "$helper_value" } echo -e "${BLUE} LOG:${YELLOW} Initializing Base System Layer...${NC}" # Confirm Architecture ARCH=$(uname -m) if [ "$ARCH" != "x86_64" ] && [ "$ARCH" != "aarch64" ] && [ "$ARCH" != "arm64" ]; then echo -e "${RED} ERROR: Unsupported architecture: $ARCH${NC}" exit 1 fi is_wsl() { [ -f /proc/version ] && grep -qEi "(Microsoft|WSL)" /proc/version } # Package Installation PACKAGES=( curl wget git sudo rsync zsh tmux unzip tar gzip build-essential openssl python bison mercurial ripgrep fd bat fzf jq btop httpie gnupg zoxide stow direnv bind nmap socat tcpdump net-tools strace gdb hexyl ninja-build libcurl4-openssl-dev just ) FINAL_PACKAGES=() for pkg in "${PACKAGES[@]}"; do case "$pkg" in "build-essential") if is_arch_family; then FINAL_PACKAGES+=(base-devel) elif is_debian_family; then FINAL_PACKAGES+=(build-essential) elif is_fedora_family; then FINAL_PACKAGES+=(gcc gcc-c++ make patch) elif is_macos; then : fi continue ;; "python") if is_arch_family; then FINAL_PACKAGES+=(python) elif is_debian_family; then FINAL_PACKAGES+=(python3 python3-pip python3-venv) elif is_fedora_family; then FINAL_PACKAGES+=(python3 python3-pip) elif is_macos; then FINAL_PACKAGES+=(python) fi continue ;; "fd") if is_arch_family; then FINAL_PACKAGES+=(fd) elif is_debian_family || is_fedora_family; then FINAL_PACKAGES+=(fd-find) fi continue ;; "bat") FINAL_PACKAGES+=(bat) continue ;; "openssl") if is_arch_family; then FINAL_PACKAGES+=(openssl) elif is_debian_family; then FINAL_PACKAGES+=(libssl-dev) elif is_fedora_family; then FINAL_PACKAGES+=(openssl openssl-devel) elif is_macos; then FINAL_PACKAGES+=(openssl@3) fi continue ;; "bind") if is_arch_family; then FINAL_PACKAGES+=(bind) elif is_debian_family; then FINAL_PACKAGES+=(dnsutils) elif is_fedora_family; then FINAL_PACKAGES+=(bind-utils) elif is_macos; then FINAL_PACKAGES+=(bind) fi continue ;; "ninja-build") if is_arch_family; then FINAL_PACKAGES+=(ninja) elif is_debian_family || is_fedora_family; then FINAL_PACKAGES+=(ninja-build) elif is_macos; then FINAL_PACKAGES+=(ninja) fi continue ;; "libcurl4-openssl-dev") if is_arch_family; then FINAL_PACKAGES+=(curl) elif is_debian_family; then FINAL_PACKAGES+=(libcurl4-openssl-dev) elif is_fedora_family; then FINAL_PACKAGES+=(libcurl-devel) elif is_macos; then FINAL_PACKAGES+=(curl) fi continue ;; "gnupg") if is_fedora_family; then FINAL_PACKAGES+=(gnupg2) elif is_macos; then FINAL_PACKAGES+=(gnupg) else FINAL_PACKAGES+=(gnupg) fi continue ;; *) esac FINAL_PACKAGES+=("$pkg") done if is_debian_family; then FINAL_PACKAGES+=(ca-certificates bsdmainutils pkg-config cmake) fi if is_fedora_family; then FINAL_PACKAGES+=(ca-certificates pkgconf-pkg-config cmake) FINAL_PACKAGES+=(R-core gcc-gfortran bzip2 bzip2-devel readline-devel sqlite sqlite-devel tk-devel libffi-devel xz xz-devel ncurses-devel zlib-devel findutils llvm) fi if is_macos; then FINAL_PACKAGES=( curl wget git zsh tmux unzip python bison mercurial ripgrep fd bat fzf jq btop httpie gnupg zoxide stow direnv bind nmap socat hexyl ninja just ) fi echo -e "${BLUE} LOG:${YELLOW} Installing: ${NC}${FINAL_PACKAGES[*]}" install_status=0 install_packages "${FINAL_PACKAGES[@]}" || install_status=$? if [ "$install_status" -eq 42 ]; then exit 0 elif [ "$install_status" -ne 0 ]; then exit "$install_status" fi if is_debian_family || is_fedora_family; then echo -e "${BLUE} LOG:${YELLOW} Fixing fd/bat binary names when needed...${NC}" [ -f /usr/bin/fdfind ] && sudo ln -sf /usr/bin/fdfind /usr/local/bin/fd [ -f /usr/bin/batcat ] && sudo ln -sf /usr/bin/batcat /usr/local/bin/bat fi # Installing pinned repo-managed CLI binaries bash "$REPO_ROOT/Scripts/bin/install.sh" --all if ! command -v rclone &> /dev/null; then echo -e "${BLUE} LOG:${YELLOW} Installing Rclone CLI...${NC}" if is_macos; then brew install rclone else case "$ARCH" in x86_64) RCLONE_ARCH="amd64" ;; aarch64|arm64) RCLONE_ARCH="arm64" ;; esac TEMP_DIR="$(mktemp -d)" RCLONE_ZIP="$TEMP_DIR/rclone.zip" RCLONE_ASSET="rclone-${RCLONE_VERSION}-linux-${RCLONE_ARCH}.zip" RCLONE_ASSET_URL="https://github.com/rclone/rclone/releases/download/${RCLONE_VERSION}/${RCLONE_ASSET}" RCLONE_CHECKSUM_URL="https://github.com/rclone/rclone/releases/download/${RCLONE_VERSION}/SHA256SUMS" download_and_verify_release_asset "$RCLONE_ASSET_URL" "$RCLONE_CHECKSUM_URL" "$RCLONE_ASSET" "$RCLONE_ZIP" unzip -q "$RCLONE_ZIP" -d "$TEMP_DIR" install -m 755 "$TEMP_DIR"/rclone-${RCLONE_VERSION}-linux-"${RCLONE_ARCH}"/rclone "$HOME/.local/bin/rclone" rm -rf "$TEMP_DIR" fi fi if ! command -v earthly &> /dev/null; then echo -e "${BLUE} LOG:${YELLOW} Installing Earthly CLI...${NC}" if is_macos; then brew install earthly else case "$ARCH" in x86_64) EARTHLY_ARCH="amd64" ;; aarch64|arm64) EARTHLY_ARCH="arm64" ;; esac EARTHLY_ASSET="earthly-linux-${EARTHLY_ARCH}" EARTHLY_ASSET_URL="https://github.com/earthly/earthly/releases/download/${EARTHLY_VERSION}/${EARTHLY_ASSET}" EARTHLY_CHECKSUM_URL="https://github.com/earthly/earthly/releases/download/${EARTHLY_VERSION}/checksum.asc" download_and_verify_release_asset "$EARTHLY_ASSET_URL" "$EARTHLY_CHECKSUM_URL" "$EARTHLY_ASSET" "$HOME/.local/bin/earthly" chmod +x "$HOME/.local/bin/earthly" fi fi # Docker Installation if command -v docker &> /dev/null; then echo -e "${GREEN} LOG: Docker is already installed.${NC}" else if is_wsl; then echo -e "${RED} LOG: WSL Detected! Skipping Native Docker.${NC}" echo -e "${RED} >>> Please install Docker Desktop on Windows.${NC}" elif is_macos; then echo -e "${YELLOW} NOTE:${NC} Docker is not installed. Please install Docker Desktop for macOS manually." else echo -e "${BLUE} LOG:${YELLOW} Installing Native Docker...${NC}" if is_arch_family; then sudo pacman -S --noconfirm --needed docker docker-compose sudo systemctl enable --now docker elif is_debian_family; then install_debian_docker_from_repo elif is_fedora_family; then if is_atomic_fedora; then echo -e "${YELLOW} NOTE:${NC} Skipping native Docker auto-install on rpm-ostree systems." echo -e "${YELLOW} NOTE:${NC} After reboot, install your preferred container runtime separately if needed." else sudo dnf install -y moby-engine docker-compose sudo systemctl enable --now docker fi fi # Add user to group if command -v getent >/dev/null 2>&1 && getent group docker >/dev/null 2>&1; then sudo usermod -aG docker $(whoami) fi fi fi # 4. Zsh & Configuration if [ ! -d "$HOME/.oh-my-zsh" ]; then clone_pinned_repo "$OH_MY_ZSH_REPO" "$HOME/.oh-my-zsh" "$OH_MY_ZSH_COMMIT" "Oh My Zsh" fi # Plugins ZSH_CUSTOM="${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}" mkdir -p "$ZSH_CUSTOM/plugins" [ ! -d "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting" ] && clone_pinned_repo "$ZSH_SYNTAX_HIGHLIGHTING_REPO" "$ZSH_CUSTOM/plugins/zsh-syntax-highlighting" "$ZSH_SYNTAX_HIGHLIGHTING_COMMIT" "zsh-syntax-highlighting" [ ! -d "$ZSH_CUSTOM/plugins/zsh-autosuggestions" ] && clone_pinned_repo "$ZSH_AUTOSUGGESTIONS_REPO" "$ZSH_CUSTOM/plugins/zsh-autosuggestions" "$ZSH_AUTOSUGGESTIONS_COMMIT" "zsh-autosuggestions" # 5. Git Credentials (WSL Bridge) if is_wsl; then GCM_WIN="/mnt/c/Program Files/Git/mingw64/bin/git-credential-manager.exe" [ -f "$GCM_WIN" ] && ensure_git_credential_helper "! \"$GCM_WIN\"" else ensure_git_credential_helper 'cache --timeout=43200' fi # 6. Cleanup & Secrets rm -f "$HOME/.zshrc" "$HOME/.zsh_aliases" mkdir -p "$REPO_ROOT/Zsh" [ -e "$REPO_ROOT/Zsh/.zsh_secrets" ] || : > "$REPO_ROOT/Zsh/.zsh_secrets" # 7. Set Shell TARGET_SHELL="$(command -v zsh)" CURRENT_LOGIN_SHELL="" if is_macos && [ -x /bin/zsh ]; then TARGET_SHELL="/bin/zsh" fi if command -v getent >/dev/null 2>&1; then CURRENT_LOGIN_SHELL="$(getent passwd "$(whoami)" | cut -d: -f7)" elif is_macos && command -v dscl >/dev/null 2>&1; then CURRENT_LOGIN_SHELL="$(dscl . -read "/Users/$(whoami)" UserShell 2>/dev/null | awk '{print $2}')" fi if [ "$CURRENT_LOGIN_SHELL" != "$TARGET_SHELL" ]; then if [ ! -t 0 ]; then echo -e "${YELLOW} NOTE:${NC} Non-interactive session detected. Skipping login shell change to $TARGET_SHELL." echo -e "${YELLOW} NOTE:${NC} Run 'chsh -s $TARGET_SHELL' manually later if you want zsh as your login shell." elif is_macos && command -v chsh >/dev/null 2>&1; then chsh -s "$TARGET_SHELL" elif command -v chsh >/dev/null 2>&1; then sudo chsh -s "$TARGET_SHELL" "$(whoami)" elif command -v usermod >/dev/null 2>&1; then sudo usermod -s "$TARGET_SHELL" "$(whoami)" else echo -e "${YELLOW} NOTE:${NC} Could not find chsh/usermod. Please change your login shell to $TARGET_SHELL manually." fi fi echo -e "${GREEN} LOG: Base System Setup Complete.${NC}"