Files
dotfiles/dotfile-install.sh
2025-06-04 19:55:19 +02:00

938 lines
27 KiB
Bash

#!/bin/bash
# Dotfiles Installation Script - Cross-Platform
# Test Developer Setup: Fish or Zsh + Ghostty + Tools
set -e # Exit on error
DOTFILES_DIR="$HOME/git/dotfiles"
BACKUP_DIR="$HOME/.config-backup-$(date +%Y%m%d-%H%M%S)"
# Colors
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
# Package manager and OS detection
PACKAGE_MANAGER=""
OS=""
log() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
detect_os() {
log "Detecting operating system..."
if [[ "$OSTYPE" == "darwin"* ]]; then
OS="macos"
PACKAGE_MANAGER="brew"
elif [[ -f /etc/arch-release ]]; then
OS="arch"
PACKAGE_MANAGER="pacman"
elif [[ -f /etc/debian_version ]]; then
OS="debian"
PACKAGE_MANAGER="apt"
elif [[ -f /etc/fedora-release ]]; then
OS="fedora"
PACKAGE_MANAGER="dnf"
elif [[ -f /etc/redhat-release ]]; then
OS="rhel"
PACKAGE_MANAGER="yum"
elif [[ -f /etc/opensuse-release ]]; then
OS="opensuse"
PACKAGE_MANAGER="zypper"
else
error "Unsupported operating system"
exit 1
fi
log "Detected: $OS with $PACKAGE_MANAGER"
}
install_package_manager() {
case $PACKAGE_MANAGER in
"brew")
if ! command -v brew &> /dev/null; then
log "Installing Homebrew..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
fi
;;
"pacman")
log "Pacman detected - updating package database..."
sudo pacman -Sy
;;
"apt")
log "APT detected - updating package database..."
sudo apt update
;;
"dnf")
log "DNF detected - updating package database..."
sudo dnf check-update || true
;;
"yum")
log "YUM detected - updating package database..."
sudo yum check-update || true
;;
"zypper")
log "Zypper detected - updating package database..."
sudo zypper refresh
;;
esac
}
install_package() {
local package=$1
log "Installing $package..."
case $PACKAGE_MANAGER in
"brew")
brew install "$package"
;;
"pacman")
sudo pacman -S --noconfirm "$package"
;;
"apt")
sudo apt install -y "$package"
;;
"dnf")
sudo dnf install -y "$package"
;;
"yum")
sudo yum install -y "$package"
;;
"zypper")
sudo zypper install -y "$package"
;;
esac
}
install_cask() {
local app=$1
log "Installing $app..."
case $PACKAGE_MANAGER in
"brew")
brew install --cask "$app"
;;
"pacman")
# This shouldn't be called for Arch anymore since we use yay directly
warn "Arch Linux packages should be installed via yay, not individual pacman calls"
;;
"apt")
case $app in
"brave-browser")
# Add Brave repository
curl -fsSLo /usr/share/keyrings/brave-browser-archive-keyring.gpg https://brave-browser-apt-release.s3.brave.com/brave-browser-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/brave-browser-archive-keyring.gpg arch=amd64] https://brave-browser-apt-release.s3.brave.com/ stable main" | sudo tee /etc/apt/sources.list.d/brave-browser-release.list
sudo apt update
sudo apt install -y brave-browser
;;
"postman")
warn "Install Postman via Snap: sudo snap install postman"
;;
"1password")
# Add 1Password repository
curl -sS https://downloads.1password.com/linux/keys/1password.asc | sudo gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg
echo 'deb [arch=amd64 signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/amd64 stable main' | sudo tee /etc/apt/sources.list.d/1password.list
sudo apt update
sudo apt install -y 1password
;;
"parsec")
warn "Download Parsec manually from parseapp.com"
;;
"ghostty")
warn "Ghostty needs manual installation or Flatpak"
;;
esac
;;
"dnf"|"yum")
case $app in
"brave-browser")
sudo dnf config-manager --add-repo https://brave-browser-rpm-release.s3.brave.com/x86_64/
sudo rpm --import https://brave-browser-rpm-release.s3.brave.com/brave-core.asc
sudo dnf install -y brave-browser
;;
*)
warn "Install $app via Flatpak: flatpak install $app"
;;
esac
;;
*)
warn "GUI app installation for $app not implemented for $PACKAGE_MANAGER"
warn "Consider using Flatpak: flatpak install $app"
;;
esac
}
header() {
echo -e "${BLUE}
╔════════════════════════════════════╗
║ Complete Development Setup ║
║ Cross-Platform Tool Installer ║
╚════════════════════════════════════╝${NC}"
}
check_prerequisites() {
log "Checking prerequisites..."
detect_os
install_package_manager
# Check dotfiles directory
if [ ! -d "$DOTFILES_DIR" ]; then
error "Dotfiles directory not found: $DOTFILES_DIR"
log "Please ensure your dotfiles are at: $DOTFILES_DIR"
exit 1
fi
}
backup_configs() {
log "Creating backup..."
mkdir -p "$BACKUP_DIR"
# Backup existing configs
[ -d ~/.config/fish ] && cp -r ~/.config/fish "$BACKUP_DIR/"
[ -f ~/.zshrc ] && cp ~/.zshrc "$BACKUP_DIR/"
[ -d ~/.config/ghostty ] && cp -r ~/.config/ghostty "$BACKUP_DIR/"
[ -d ~/.config/neofetch ] && cp -r ~/.config/neofetch "$BACKUP_DIR/"
[ -f ~/.config/starship.toml ] && cp ~/.config/starship.toml "$BACKUP_DIR/"
log "Backup created: $BACKUP_DIR"
}
install_tools() {
log "Installing development tools..."
# Core tools - OS specific package names
case $OS in
"macos")
# Command line tools
install_package starship
install_package neofetch
install_package btop
install_package git
install_package fzf
install_package ripgrep
install_package fd
install_package bat
install_package go
install_package python3
install_package node
install_package syncthing
install_package tailscale
install_package vim
# GUI Applications
install_cask parsec
install_cask postman
install_cask 1password
install_cask brave-browser
install_cask ghostty
;;
"arch")
# Check if yay is installed, install if not
if ! command -v yay &> /dev/null; then
log "Installing yay AUR helper..."
sudo pacman -S --needed base-devel git
git clone https://aur.archlinux.org/yay.git /tmp/yay
cd /tmp/yay && makepkg -si --noconfirm
cd - && rm -rf /tmp/yay
fi
# Install everything via yay (handles both official repos + AUR)
log "Installing all packages via yay..."
yay -S --noconfirm \
starship \
neofetch \
btop \
git \
fzf \
ripgrep \
fd \
bat \
go \
python \
nodejs \
npm \
syncthing \
tailscale \
vim \
parsec \
postman-bin \
1password \
brave-bin \
ghostty
;;
"debian")
# Command line tools
install_package git
install_package fzf
install_package ripgrep
install_package fd-find
install_package bat
install_package golang-go
install_package python3
install_package nodejs
install_package npm
install_package syncthing
install_package vim
# Install starship manually
curl -sS https://starship.rs/install.sh | sh
install_package neofetch
# Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
warn "GUI applications need manual installation on Debian/Ubuntu:"
warn "- Parsec, Postman, 1Password, Brave Browser"
warn "- btop may need manual installation on older versions"
;;
"fedora"|"rhel")
# Command line tools
install_package git
install_package fzf
install_package ripgrep
install_package fd-find
install_package bat
install_package golang
install_package python3
install_package nodejs
install_package npm
install_package syncthing
install_package vim
# Install starship manually
curl -sS https://starship.rs/install.sh | sh
install_package neofetch
# Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
warn "GUI applications available via Flatpak or manual installation"
;;
"opensuse")
# Command line tools
install_package git
install_package fzf
install_package ripgrep
install_package fd
install_package bat
install_package go
install_package python3
install_package nodejs
install_package npm
install_package syncthing
install_package vim
# Install starship manually
curl -sS https://starship.rs/install.sh | sh
install_package neofetch
# Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
warn "GUI applications may need manual installation"
;;
esac
# Setup vim and other tools
setup_vim
install_node_tools
log "Tools installation complete!"
# Show post-installation notes
show_post_install_notes
}
show_post_install_notes() {
case $OS in
"macos"|"arch")
log "All tools installed successfully!"
;;
"debian"|"fedora"|"rhel"|"opensuse")
warn "Additional GUI apps can be installed via:"
warn "• Flatpak: flatpak install brave postman"
warn "• Snap: snap install brave postman"
warn "• Manual downloads from official websites"
warn "• AppImage versions where available"
;;
esac
}
install_node_tools() {
log "Installing Node.js tools..."
# Ensure npm is available
if command -v npm &> /dev/null; then
npm install -g @playwright/test
npx playwright install
else
warn "npm not found, skipping Playwright installation"
fi
}
setup_vim() {
log "Setting up Kickstart Vim..."
# Backup existing .vimrc
[ -f ~/.vimrc ] && mv ~/.vimrc ~/.vimrc.backup.$(date +%Y%m%d-%H%M%S)
# Clone or update kickstart.vim
if [ -d ~/kickstart.vim ]; then
cd ~/kickstart.vim && git pull
else
git clone https://github.com/theopn/kickstart.vim.git ~/kickstart.vim
fi
# Create symlink
ln -sf ~/kickstart.vim/.vimrc ~/.vimrc
# Install plugins
vim +PlugInstall +qa
}
setup_dotfiles_symlinks() {
log "Creating dotfiles symlinks..."
# Remove existing configs
rm -rf ~/.config/neofetch ~/.config/btop
rm -f ~/.config/starship.toml
# Create symlinks to dotfiles - common configs
ln -sf "$DOTFILES_DIR/neofetch" ~/.config/neofetch
ln -sf "$DOTFILES_DIR/terminal/starship.toml" ~/.config/starship.toml
# Ghostty (only if directory exists in dotfiles)
if [ -d "$DOTFILES_DIR/terminal/ghostty" ]; then
rm -rf ~/.config/ghostty
ln -sf "$DOTFILES_DIR/terminal/ghostty" ~/.config/ghostty
fi
# Btop config
mkdir -p ~/.config/btop
if [ -f "$DOTFILES_DIR/btop/btop.conf" ]; then
ln -sf "$DOTFILES_DIR/btop/btop.conf" ~/.config/btop/btop.conf
fi
if [ -d "$DOTFILES_DIR/btop/themes" ]; then
ln -sf "$DOTFILES_DIR/btop/themes" ~/.config/btop/themes
fi
log "Dotfiles symlinks created!"
}
setup_auto_updates() {
log "Setting up automatic system updates..."
# Create update script
local update_script="$HOME/.local/bin/update-system.sh"
mkdir -p "$(dirname "$update_script")"
:
# Copy the update script content (same as before)
cat > "$update_script" << 'EOF'
#!/bin/bash
# System Update Script
# Updates all packages, dotfiles, and development tools
DOTFILES_DIR="$HOME/git/dotfiles"
LOG_FILE="$HOME/.local/share/system-updates.log"
LAST_UPDATE_FILE="$HOME/.local/share/last-system-update"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
log() {
echo -e "${GREEN}[INFO]${NC} $1"
echo "[$DATE] INFO: $1" >> "$LOG_FILE"
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
echo "[$DATE] WARN: $1" >> "$LOG_FILE"
}
error() {
echo -e "${RED}[ERROR]${NC} $1"
echo "[$DATE] ERROR: $1" >> "$LOG_FILE"
}
detect_os() {
if [[ "$OSTYPE" == "darwin"* ]]; then
OS="macos"
PACKAGE_MANAGER="brew"
elif [[ -f /etc/arch-release ]]; then
OS="arch"
PACKAGE_MANAGER="yay"
elif [[ -f /etc/debian_version ]]; then
OS="debian"
PACKAGE_MANAGER="apt"
elif [[ -f /etc/fedora-release ]]; then
OS="fedora"
PACKAGE_MANAGER="dnf"
elif [[ -f /etc/redhat-release ]]; then
OS="rhel"
PACKAGE_MANAGER="yum"
else
OS="unknown"
PACKAGE_MANAGER="unknown"
fi
}
is_update_needed() {
if [ ! -f "$LAST_UPDATE_FILE" ]; then
return 0 # No previous update, run it
fi
local last_update=$(cat "$LAST_UPDATE_FILE" 2>/dev/null || echo "0")
local current_date=$(date +%s)
local days_since_update=$(( (current_date - last_update) / 86400 ))
if [ $days_since_update -ge 30 ]; then
log "Last update was $days_since_update days ago, running update..."
return 0
else
log "Last update was $days_since_update days ago, skipping..."
return 1
fi
}
update_system_packages() {
log "Updating system packages ($OS)..."
case $PACKAGE_MANAGER in
"brew")
brew update
brew upgrade
brew cleanup
;;
"yay")
yay -Syu --noconfirm
yay -Yc --noconfirm
;;
"apt")
sudo apt update
sudo apt upgrade -y
sudo apt autoremove -y
sudo apt autoclean
;;
"dnf")
sudo dnf upgrade -y
sudo dnf autoremove -y
;;
"yum")
sudo yum update -y
sudo yum clean all
;;
esac
}
update_dotfiles() {
log "Updating dotfiles..."
if [ -d "$DOTFILES_DIR/.git" ]; then
cd "$DOTFILES_DIR"
git fetch origin
LOCAL=$(git rev-parse @)
REMOTE=$(git rev-parse @{u})
if [ "$LOCAL" != "$REMOTE" ]; then
git pull origin main || git pull origin master
log "Dotfiles updated"
else
log "Dotfiles up to date"
fi
fi
}
update_vim_plugins() {
if [ -f "$HOME/.vimrc" ] && command -v vim &> /dev/null; then
vim +PlugUpdate +PlugClean! +qa
log "Vim plugins updated"
fi
}
update_node_packages() {
if command -v npm &> /dev/null; then
npm update -g
log "npm packages updated"
fi
if command -v npx &> /dev/null; then
npx playwright install 2>/dev/null || true
fi
}
mark_update_completed() {
date +%s > "$LAST_UPDATE_FILE"
log "Update timestamp saved"
}
main() {
mkdir -p "$(dirname "$LOG_FILE")"
mkdir -p "$(dirname "$LAST_UPDATE_FILE")"
# Check if running with --force flag
if [[ "$1" == "--force" ]] || is_update_needed; then
echo "[$DATE] Starting system update..." >> "$LOG_FILE"
detect_os
log "Starting update process..."
update_system_packages
update_dotfiles
update_vim_plugins
update_node_packages
mark_update_completed
log "Update completed!"
echo "[$DATE] Update completed" >> "$LOG_FILE"
fi
}
main "$@"
EOF
chmod +x "$update_script"
# Create startup check script
create_startup_check "$update_script"
# Setup traditional cron job (for always-on systems)
setup_cron_job "$update_script"
# Setup system-specific startup methods
setup_startup_updates "$update_script"
log "Auto-update system configured!"
log "Updates will run:"
log "• Monthly on 1st at 2:00 AM (if system is on)"
log "• At startup if update is overdue (30+ days)"
log "• Manual: $update_script --force"
log "View logs: tail -f ~/.local/share/system-updates.log"
}
create_startup_check() {
local update_script="$1"
local startup_check="$HOME/.local/bin/check-updates-on-startup.sh"
cat > "$startup_check" << EOF
#!/bin/bash
# Check for overdue updates on startup (runs max once per day)
DAILY_CHECK_FILE="\$HOME/.local/share/daily-update-check"
TODAY=\$(date +%Y-%m-%d)
# Only run once per day
if [ -f "\$DAILY_CHECK_FILE" ] && [ "\$(cat "\$DAILY_CHECK_FILE" 2>/dev/null)" = "\$TODAY" ]; then
exit 0
fi
# Mark today as checked
echo "\$TODAY" > "\$DAILY_CHECK_FILE"
# Run update check in background (non-blocking)
nohup "$update_script" > /dev/null 2>&1 &
EOF
chmod +x "$startup_check"
return "$startup_check"
}
setup_startup_updates() {
local update_script="$1"
local startup_check="$HOME/.local/bin/check-updates-on-startup.sh"
case $OS in
"macos")
setup_macos_startup "$startup_check"
;;
"arch"|"debian"|"fedora"|"rhel")
setup_linux_startup "$startup_check"
;;
esac
}
setup_macos_startup() {
local startup_check="$1"
local plist_file="$HOME/Library/LaunchAgents/com.user.system-updates.plist"
log "Setting up macOS LaunchAgent for startup updates..."
mkdir -p "$HOME/Library/LaunchAgents"
cat > "$plist_file" << EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.system-updates</string>
<key>ProgramArguments</key>
<array>
<string>$startup_check</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>$HOME/.local/share/startup-updates.log</string>
<key>StandardErrorPath</key>
<string>$HOME/.local/share/startup-updates-error.log</string>
</dict>
</plist>
EOF
# Load the LaunchAgent
launchctl load "$plist_file" 2>/dev/null || warn "LaunchAgent setup may need manual activation"
log "macOS startup updates configured"
}
setup_linux_startup() {
local startup_check="$1"
# Try systemd user service first
if command -v systemctl &> /dev/null; then
setup_systemd_user_service "$startup_check"
else
# Fallback to shell profile
setup_shell_profile_check "$startup_check"
fi
}
setup_systemd_user_service() {
local startup_check="$1"
local service_dir="$HOME/.config/systemd/user"
log "Setting up systemd user service for startup updates..."
mkdir -p "$service_dir"
# Create service file
cat > "$service_dir/system-updates.service" << EOF
[Unit]
Description=System Updates Check
After=network-online.target
[Service]
Type=oneshot
ExecStart=$startup_check
StandardOutput=append:$HOME/.local/share/startup-updates.log
StandardError=append:$HOME/.local/share/startup-updates-error.log
[Install]
WantedBy=default.target
EOF
# Enable the service
systemctl --user daemon-reload
systemctl --user enable system-updates.service
log "systemd user service configured"
}
setup_shell_profile_check() {
local startup_check="$1"
log "Adding startup update check to shell profiles..."
# Add to shell profiles (as fallback)
local check_line="# Auto-update check (once per day)
if [ -f \"$startup_check\" ]; then
\"$startup_check\" &
fi"
# Add to profiles that exist
for profile in ~/.profile ~/.bash_profile ~/.zprofile; do
if [ -f "$profile" ]; then
if ! grep -q "check-updates-on-startup" "$profile"; then
echo "$check_line" >> "$profile"
log "Added to $profile"
fi
fi
done
}
setup_cron_job() {
local script_path="$1"
log "Setting up monthly cron job..."
# Create cron job entry
local cron_job="0 2 1 * * $script_path >> ~/.local/share/cron-updates.log 2>&1"
# Add to crontab if not already present
(crontab -l 2>/dev/null | grep -v "$script_path"; echo "$cron_job") | crontab -
log "Cron job installed: Monthly updates on 1st at 2:00 AM"
# Show current crontab
log "Current cron jobs:"
crontab -l | grep -v "^#" || warn "No cron jobs found"
}
setup_fish() {
log "Setting up Fish shell..."
# Install Fish
install_package fish
# Install Fisher (Fish plugin manager)
if ! command -v fisher &> /dev/null; then
fish -c "curl -sL https://git.io/fisher | source && fisher install jorgebucaran/fisher"
fi
# Remove existing fish config
rm -rf ~/.config/fish
# Create symlink to dotfiles
ln -sf "$DOTFILES_DIR/shells/fish" ~/.config/fish
# Install Fish plugins
log "Installing Fish plugins..."
fish -c "fisher install jorgebucaran/nvm.fish"
fish -c "fisher install PatrickF1/fzf.fish"
fish -c "fisher install franciscolourenco/done"
# Install Node.js via NVM (if available)
if command -v fish &> /dev/null; then
fish -c "nvm install latest && nvm use latest" || warn "NVM setup failed, Node.js should be available via system package"
fi
# Set as default shell
local fish_path=$(which fish)
if ! grep -q "$fish_path" /etc/shells; then
echo "$fish_path" | sudo tee -a /etc/shells
fi
chsh -s "$fish_path"
log "Fish shell configured!"
}
setup_zsh() {
log "Setting up Zsh shell..."
# Install Zsh
install_package zsh
# Remove existing configs
rm -rf ~/.zshrc ~/.oh-my-zsh
# Install Oh My Zsh
RUNZSH=no sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
# Install NVM for Zsh (cross-platform)
if [ ! -d "$HOME/.nvm" ]; then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
fi
# Create symlink to dotfiles
ln -sf "$DOTFILES_DIR/shells/zsh/.zshrc" ~/.zshrc
# Install Node.js via NVM
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
if command -v nvm &> /dev/null; then
nvm install node && nvm use node
else
warn "NVM setup failed, using system Node.js"
fi
# Set as default shell
local zsh_path=$(which zsh)
if ! grep -q "$zsh_path" /etc/shells; then
echo "$zsh_path" | sudo tee -a /etc/shells
fi
chsh -s "$zsh_path"
log "Zsh shell configured!"
}
show_menu() {
echo -e "${BLUE}
Select your shell setup:
1) Fish Shell
- Modern auto-completion
- Clean configuration
- User-friendly syntax
2) Zsh Shell
- POSIX compatible
- Oh-My-Zsh framework
- Traditional Unix shell
Detected OS: $OS ($PACKAGE_MANAGER)
Tools to be installed:
• Development: Go, Python, Node.js, Git, Vim
• Terminal: Starship, Neofetch, btop, fzf, ripgrep, fd, bat
• Networking: Syncthing, Tailscale
• Applications: Parsec, Postman, 1Password, Brave Browser
• Testing: Playwright framework
${NC}"
read -p "Choose (1-2): " choice
}
main() {
header
check_prerequisites
backup_configs
install_tools
setup_dotfiles_symlinks
setup_auto_updates
show_menu
case $choice in
1)
setup_fish
log "Fish setup complete! Restart your terminal."
;;
2)
setup_zsh
log "Zsh setup complete! Restart your terminal."
;;
*)
error "Invalid choice. Exiting."
exit 1
;;
esac
echo -e "${GREEN}
╔══════════════════════════════════════════════════════════╗
║ Setup Complete! ║
║ ║
║ ✓ $OS system detected ║
║ ✓ Development tools installed ║
║ ✓ Dotfiles symlinked ║
║ ✓ Shell configured ║
║ ✓ Smart auto-updates enabled ║
║ ║
║ Updates run automatically: ║
║ • Monthly (1st at 2 AM) if system is on ║
║ • At startup if overdue (30+ days) ║
║ • Max once per day (non-blocking) ║
║ ║
║ Manual commands: ║
║ • Force update: ~/.local/bin/update-system.sh --force ║
║ • View logs: tail -f ~/.local/share/system-updates.log ║
║ ║
║ Perfect for laptops and desktops! ║
║ ║
║ Restart terminal to see changes! ║
║ ║
║ Backup: $BACKUP_DIR
╚══════════════════════════════════════════════════════════╝${NC}"
}
main "$@"