From ea25918192a8995ef709918200af2c5987343e1d Mon Sep 17 00:00:00 2001 From: Baerspektivo Date: Wed, 4 Jun 2025 19:10:48 +0200 Subject: [PATCH] added install and update script to get more comfort and easyer way to work --- install.bash | 937 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 937 insertions(+) create mode 100644 install.bash diff --git a/install.bash b/install.bash new file mode 100644 index 0000000..e2720c3 --- /dev/null +++ b/install.bash @@ -0,0 +1,937 @@ +#!/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 + + + + + Label + com.user.system-updates + ProgramArguments + + $startup_check + + RunAtLoad + + StandardOutPath + $HOME/.local/share/startup-updates.log + StandardErrorPath + $HOME/.local/share/startup-updates-error.log + + +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 "$@"