Răsfoiți Sursa

dev: automated commit - 2025-06-08 23:28:55

Mariano Z. 8 luni în urmă
părinte
comite
0ab9f62ace
13 a modificat fișierele cu 1302 adăugiri și 762 ștergeri
  1. 252 0
      common.sh
  2. 72 30
      main.go
  3. 58 30
      runs/aur
  4. 232 194
      runs/base
  5. 31 29
      runs/boot
  6. 32 15
      runs/docker
  7. 102 135
      runs/dotfiles
  8. 0 211
      runs/limine
  9. 279 0
      runs/limine_hibernate
  10. 55 0
      runs/nvim
  11. 51 18
      runs/paru
  12. 96 100
      runs/services
  13. 42 0
      runs/sudo

+ 252 - 0
common.sh

@@ -0,0 +1,252 @@
+#!/usr/bin/env bash
+# Script Standards and Common Functions
+# Source this file in your scripts for consistent behavior
+
+# ==============================================================================
+# STANDARD SCRIPT HEADER
+# ==============================================================================
+# All scripts should start with:
+# #!/usr/bin/env bash
+# # NAME: Brief description of what the script does
+# # REQUIRES: sudo interactive (if needed)
+#
+# set -euo pipefail
+#
+# # Source common functions (adjust path as needed)
+# source "$(dirname "$0")/common.sh" 2>/dev/null || true
+
+# ==============================================================================
+# CONFIGURATION
+# ==============================================================================
+readonly SCRIPT_NAME="${0##*/}"
+readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# Colors for output (only if terminal supports it)
+if [[ -t 1 ]] && command -v tput >/dev/null 2>&1; then
+	readonly RED=$(tput setaf 1)
+	readonly GREEN=$(tput setaf 2)
+	readonly YELLOW=$(tput setaf 3)
+	readonly BLUE=$(tput setaf 4)
+	readonly PURPLE=$(tput setaf 5)
+	readonly CYAN=$(tput setaf 6)
+	readonly WHITE=$(tput setaf 7)
+	readonly BOLD=$(tput bold)
+	readonly RESET=$(tput sgr0)
+else
+	readonly RED="" GREEN="" YELLOW="" BLUE="" PURPLE="" CYAN="" WHITE="" BOLD="" RESET=""
+fi
+
+# ==============================================================================
+# LOGGING FUNCTIONS
+# ==============================================================================
+log_info() {
+	printf "${GREEN}[INFO]${RESET} %s\n" "$*"
+}
+
+log_warn() {
+	printf "${YELLOW}[WARN]${RESET} %s\n" "$*" >&2
+}
+
+log_error() {
+	printf "${RED}[ERROR]${RESET} %s\n" "$*" >&2
+}
+
+log_success() {
+	printf "${GREEN}[SUCCESS]${RESET} %s\n" "$*"
+}
+
+log_debug() {
+	if [[ "${DEBUG:-0}" == "1" ]]; then
+		printf "${CYAN}[DEBUG]${RESET} %s\n" "$*" >&2
+	fi
+}
+
+log_sudo() {
+	printf "${PURPLE}[SUDO]${RESET} %s\n" "$*"
+}
+
+# ==============================================================================
+# UTILITY FUNCTIONS
+# ==============================================================================
+command_exists() {
+	command -v "$1" >/dev/null 2>&1
+}
+
+is_root() {
+	[[ $EUID -eq 0 ]]
+}
+
+require_root() {
+	if ! is_root; then
+		log_error "This script requires root privileges"
+		log_info "Run with: sudo $0 $*"
+		exit 1
+	fi
+}
+
+# Check if running in interactive terminal
+is_interactive() {
+	[[ -t 0 && -t 1 ]]
+}
+
+# ==============================================================================
+# CONFIRMATION FUNCTIONS
+# ==============================================================================
+confirm() {
+	local prompt="${1:-Continue?}"
+	local default="${2:-n}"
+	local response
+
+	if ! is_interactive; then
+		log_warn "Non-interactive mode, using default: $default"
+		[[ "$default" == "y" ]]
+		return $?
+	fi
+
+	while true; do
+		if [[ "$default" == "y" ]]; then
+			printf "%s [Y/n]: " "$prompt"
+		else
+			printf "%s [y/N]: " "$prompt"
+		fi
+
+		read -r response
+		response=${response,,} # Convert to lowercase
+
+		case "$response" in
+		"")
+			[[ "$default" == "y" ]]
+			return $?
+			;;
+		y | yes)
+			return 0
+			;;
+		n | no)
+			return 1
+			;;
+		*)
+			log_warn "Please answer 'y' or 'n'"
+			;;
+		esac
+	done
+}
+
+# Show changes and confirm
+confirm_change() {
+	local file="$1"
+	local description="$2"
+	local temp_file="$3"
+
+	log_info "Proposed changes for $description:"
+	log_info "File: $file"
+	echo
+
+	if command_exists colordiff; then
+		colordiff -u "$file" "$temp_file" 2>/dev/null || diff -u "$file" "$temp_file" 2>/dev/null || true
+	else
+		diff -u "$file" "$temp_file" 2>/dev/null || true
+	fi
+
+	echo
+	confirm "Apply these changes?"
+}
+
+# ==============================================================================
+# FILE OPERATIONS
+# ==============================================================================
+backup_file() {
+	local file="$1"
+	local backup_suffix="${2:-$(date +%Y%m%d_%H%M%S)}"
+	local backup_file="${file}.backup.${backup_suffix}"
+
+	if [[ -f "$file" ]]; then
+		cp "$file" "$backup_file"
+		log_info "Created backup: $backup_file"
+		echo "$backup_file"
+	fi
+}
+
+create_temp_file() {
+	local template="${1:-tmp.XXXXXX}"
+	mktemp "/tmp/${template}"
+}
+
+# ==============================================================================
+# PACKAGE MANAGEMENT
+# ==============================================================================
+install_packages() {
+	local packages=("$@")
+
+	if [[ ${#packages[@]} -eq 0 ]]; then
+		log_warn "No packages specified"
+		return 1
+	fi
+
+	log_info "Installing packages: ${packages[*]}"
+
+	if command_exists paru; then
+		paru -S --needed --noconfirm "${packages[@]}"
+	elif command_exists pacman; then
+		sudo pacman -S --needed --noconfirm "${packages[@]}"
+	else
+		log_error "No supported package manager found"
+		return 1
+	fi
+}
+
+# ==============================================================================
+# SERVICE MANAGEMENT
+# ==============================================================================
+enable_service() {
+	local service="$1"
+	local user_service="${2:-false}"
+
+	if [[ "$user_service" == "true" ]]; then
+		log_info "Enabling user service: $service"
+		systemctl --user enable --now "$service"
+	else
+		log_info "Enabling system service: $service"
+		sudo systemctl enable --now "$service"
+	fi
+}
+
+# ==============================================================================
+# ERROR HANDLING
+# ==============================================================================
+cleanup() {
+	local exit_code=$?
+	# Add any cleanup operations here
+	exit $exit_code
+}
+
+# Set up trap for cleanup
+trap cleanup EXIT INT TERM
+
+# ==============================================================================
+# SCRIPT INITIALIZATION
+# ==============================================================================
+init_script() {
+	log_info "Starting $SCRIPT_NAME"
+
+	# Set DEBUG mode if requested
+	if [[ "${1:-}" == "--debug" ]]; then
+		export DEBUG=1
+		log_debug "Debug mode enabled"
+		shift
+	fi
+}
+
+# ==============================================================================
+# SCRIPT COMPLETION
+# ==============================================================================
+finish_script() {
+	local exit_code="${1:-0}"
+
+	if [[ "$exit_code" -eq 0 ]]; then
+		log_success "$SCRIPT_NAME completed successfully"
+	else
+		log_error "$SCRIPT_NAME failed with exit code $exit_code"
+	fi
+
+	exit "$exit_code"
+}

+ 72 - 30
main.go

@@ -32,11 +32,12 @@ type Config struct {
 }
 
 type Script struct {
-	Path         string // Full filesystem path
-	Name         string // Just filename (e.g. "install.sh")
-	RelPath      string // Relative path from runs/ (e.g. "tools/install.sh")
-	Desc         string // Description from script comment
-	RequiresSudo bool   // Whether script needs elevated privileges
+	Path                string // Full filesystem path
+	Name                string // Just filename (e.g. "install.sh")
+	RelPath             string // Relative path from runs/ (e.g. "tools/install.sh")
+	Desc                string // Description from script comment
+	RequiresSudo        bool   // Whether script needs elevated privileges
+	RequiresInteractive bool   // Whether script needs interactive input
 }
 
 var config Config
@@ -213,15 +214,20 @@ func handlePush() error {
 	return nil
 }
 
-func getScriptMetadata(scriptPath string) (string, bool) {
+type ScriptMetadata struct {
+	RequiresSudo        bool
+	RequiresInteractive bool
+}
+
+func getScriptMetadata(scriptPath string) (string, ScriptMetadata) {
 	file, err := os.Open(scriptPath)
 	if err != nil {
-		return "No description", false
+		return "No description", ScriptMetadata{}
 	}
 	defer file.Close()
 
 	var desc string
-	var requiresSudo bool
+	scriptMetadata := ScriptMetadata{}
 
 	scanner := bufio.NewScanner(file)
 	for scanner.Scan() {
@@ -231,13 +237,9 @@ func getScriptMetadata(scriptPath string) (string, bool) {
 			desc = strings.TrimSpace(strings.TrimPrefix(line, "# NAME:"))
 		}
 
-		if strings.HasPrefix(line, "# REQUIRES: sudo") ||
-			strings.HasPrefix(line, "# REQUIRES:sudo") ||
-			strings.HasPrefix(line, "# ELEVATED: true") ||
-			strings.HasPrefix(line, "# ELEVATED:true") ||
-			strings.HasPrefix(line, "# SUDO: true") ||
-			strings.HasPrefix(line, "# SUDO:true") {
-			requiresSudo = true
+		if strings.HasPrefix(line, "# REQUIRES:") {
+			scriptMetadata.RequiresSudo = strings.Contains(line, "sudo")
+			scriptMetadata.RequiresInteractive = strings.Contains(line, "interactive")
 		}
 	}
 
@@ -245,7 +247,7 @@ func getScriptMetadata(scriptPath string) (string, bool) {
 		desc = "No description"
 	}
 
-	return desc, requiresSudo
+	return desc, scriptMetadata
 }
 
 func findScripts(filters []string) ([]Script, error) {
@@ -271,14 +273,15 @@ func findScripts(filters []string) ([]Script, error) {
 			scriptName := filepath.Base(path)
 
 			if len(filters) == 0 || matchesFilters(relPath, scriptName, filters) {
-				desc, requiresSudo := getScriptMetadata(path)
+				desc, metaData := getScriptMetadata(path)
 
 				scripts = append(scripts, Script{
-					Path:         path,
-					Name:         scriptName,
-					RelPath:      relPath,
-					Desc:         desc,
-					RequiresSudo: requiresSudo,
+					Path:                path,
+					Name:                scriptName,
+					RelPath:             relPath,
+					Desc:                desc,
+					RequiresSudo:        metaData.RequiresSudo,
+					RequiresInteractive: metaData.RequiresInteractive,
 				})
 			}
 		}
@@ -331,14 +334,15 @@ func executeScript(script Script, args []string, verbose bool) error {
 			errorLog("sudo command not found - cannot run elevated script")
 			return fmt.Errorf("sudo not available")
 		}
-		fullArgs := append([]string{script.Path}, args...)
+		// Use -S to read password from stdin, and preserve environment
+		fullArgs := append([]string{"-S", "-E", script.Path}, args...)
 		cmd = exec.Command("sudo", fullArgs...)
 		sudoLog("Running with sudo: %s", strings.Join(append([]string{script.Path}, args...), " "))
 	} else {
 		cmd = exec.Command(script.Path, args...)
 	}
 
-	if config.Interactive {
+	if config.Interactive || script.RequiresInteractive {
 		cmd.Stdin = os.Stdin
 	}
 
@@ -348,7 +352,7 @@ func executeScript(script Script, args []string, verbose bool) error {
 	}
 	defer logFileHandle.Close()
 
-	showOutput := verbose || config.Interactive
+	showOutput := verbose || config.Interactive || script.RequiresInteractive
 	if showOutput {
 		cmd.Stdout = io.MultiWriter(os.Stdout, logFileHandle)
 		cmd.Stderr = io.MultiWriter(os.Stderr, logFileHandle)
@@ -371,6 +375,7 @@ func createNewScript(scriptName string) error {
 
 	if !strings.HasSuffix(scriptName, ".sh") {
 		scriptName += ".sh"
+		scriptPath = filepath.Join(config.RunsDir, scriptName)
 	}
 
 	if _, err := os.Stat(scriptPath); err == nil {
@@ -378,15 +383,52 @@ func createNewScript(scriptName string) error {
 	}
 
 	template := fmt.Sprintf(`#!/usr/bin/env bash
-# NAME: %s script
-# REQUIRES: sudo
+# NAME: %s
+# REQUIRES: sudo interactive
 
 set -euo pipefail
 
-echo "Running %s script..."
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+    echo "[ERROR] Could not source common.sh" >&2
+    exit 1
+}
+
+check_requirements() {
+    # Check required commands
+    local required_commands=()
+    # Add your required commands here
+    # required_commands=(git curl wget)
+
+    for cmd in "${required_commands[@]}"; do
+        if ! command_exists "$cmd"; then
+            log_error "Required command not found: $cmd"
+            exit 1
+        fi
+    done
+}
+
+main() {
+    init_script
+
+    check_requirements
+
+    # Your main script logic goes here
+
+
+    # Example operations:
+    # if ! confirm "Proceed with operation?"; then
+    #     log_info "Operation cancelled"
+    #     finish_script 0
+    # fi
+
+    # Add your implementation here
+
+    finish_script 0
+}
 
-echo "✅ %s completed successfully"
-`, strings.TrimSuffix(scriptName, ".sh"), scriptName, strings.TrimSuffix(scriptName, ".sh"))
+main "$@"
+`, strings.TrimSuffix(filepath.Base(scriptName), ".sh"))
 
 	err := os.WriteFile(scriptPath, []byte(template), 0o755)
 	if err != nil {

+ 58 - 30
runs/aur

@@ -1,34 +1,62 @@
 #!/usr/bin/env bash
-# NAME: aur script
+# NAME: Install AUR packages
 
 set -euo pipefail
 
-echo "Running aur.sh script..."
-
-paru -S --needed --noconfirm adwaita-dark \
-	aws-session-manager-plugin \
-	bottles \
-	bruno-bin \
-	davmail \
-	dmg2img \
-	ebgaramond-otf \
-	insomnia-bin \
-	kubefwd-bin \
-	mpvpaper \
-	ngrok \
-	openvpn3-git \
-	otf-font-awesome-5 \
-	postman-bin \
-	rofi-lbonn-wayland-git \
-	rose-pine-gtk-theme \
-	slack-desktop \
-	soapui \
-	sublime-merge \
-	swayfx-git \
-	teams-for-linux-bin \
-	ttf-font-awesome-5 \
-	ttf-ms-win11-auto \
-	watchman-bin \
-	yubico-authenticator-bin
-
-echo "✅ aur completed successfully"
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+	echo "[ERROR] Could not source common.sh" >&2
+	exit 1
+}
+
+check_requirements() {
+	if ! command_exists paru; then
+		log_error "paru not found. Install it first"
+		exit 1
+	fi
+}
+
+install_aur_packages() {
+	local packages=(
+		adwaita-dark
+		aws-session-manager-plugin
+		bottles
+		bruno-bin
+		davmail
+		dmg2img
+		ebgaramond-otf
+		insomnia-bin
+		kubefwd-bin
+		mpvpaper
+		ngrok
+		openvpn3-git
+		otf-font-awesome-5
+		postman-bin
+		rofi-lbonn-wayland-git
+		rose-pine-gtk-theme
+		slack-desktop
+		soapui
+		sublime-merge
+		swayfx-git
+		teams-for-linux-bin
+		ttf-font-awesome-5
+		ttf-ms-win11-auto
+		watchman-bin
+		yubico-authenticator-bin
+		betterbird-bin
+	)
+
+	log_info "Installing AUR packages"
+	paru -S --needed --noconfirm "${packages[@]}"
+}
+
+main() {
+	init_script
+
+	check_requirements
+	install_aur_packages
+
+	finish_script 0
+}
+
+main "$@"

+ 232 - 194
runs/base

@@ -1,199 +1,237 @@
 #!/usr/bin/env bash
-# NAME: Install base system packages
+# NAME: Install base system packages with hardware detection
 
 set -euo pipefail
 
-AUR_HELPER="paru"
-
-if ! command -v $AUR_HELPER &>/dev/null; then
-	echo "[ERROR] $AUR_HELPER not found. Install it first."
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+	echo "[ERROR] Could not source common.sh" >&2
 	exit 1
-fi
-
-echo "Installing organized system packages..."
-
-# ═══════════════════════════════════════════════════════════
-# 🔧 DETECT HARDWARE
-# ═══════════════════════════════════════════════════════════
-CPU_VENDOR=$(lscpu | grep "Vendor ID" | awk '{print $3}')
-GPU_INFO=$(lspci | grep -i vga)
-
-echo "Detected CPU: $CPU_VENDOR"
-echo "Detected GPU: $GPU_INFO"
-
-# Determine microcode package
-if [[ "$CPU_VENDOR" == "GenuineIntel" ]]; then
-	MICROCODE="intel-ucode"
-	echo "→ Using Intel microcode"
-elif [[ "$CPU_VENDOR" == "AuthenticAMD" ]]; then
-	MICROCODE="amd-ucode"
-	echo "→ Using AMD microcode"
-else
-	MICROCODE=""
-	echo "→ Unknown CPU vendor, skipping microcode"
-fi
-
-# Determine GPU drivers
-GPU_PACKAGES=""
-if echo "$GPU_INFO" | grep -qi "amd\|radeon"; then
-	GPU_PACKAGES="xf86-video-amdgpu vulkan-radeon lib32-vulkan-radeon opencl-mesa lib32-opencl-mesa"
-	echo "→ Using AMD GPU drivers"
-elif echo "$GPU_INFO" | grep -qi "intel"; then
-	GPU_PACKAGES="xf86-video-intel vulkan-intel lib32-vulkan-intel intel-media-driver"
-	echo "→ Using Intel GPU drivers"
-elif echo "$GPU_INFO" | grep -qi "nvidia"; then
-	GPU_PACKAGES="nvidia nvidia-utils lib32-nvidia-utils nvidia-settings"
-	echo "→ Using NVIDIA drivers (you may want to review this)"
-else
-	echo "→ Unknown GPU, using generic drivers"
-fi
-
-# ═══════════════════════════════════════════════════════════
-# 🔧 SYSTEM BASE
-# ═══════════════════════════════════════════════════════════
-echo "Installing system base..."
-BASE_PACKAGES="base base-devel sudo stow linux-cachyos linux-cachyos-headers linux-firmware efibootmgr efitools mkinitcpio grub systemd-boot-manager"
-
-if [[ -n "$MICROCODE" ]]; then
-	BASE_PACKAGES="$BASE_PACKAGES $MICROCODE"
-fi
-
-$AUR_HELPER -S --noconfirm --needed $BASE_PACKAGES
-
-# ═══════════════════════════════════════════════════════════
-# 🌐 NETWORKING & BLUETOOTH
-# ═══════════════════════════════════════════════════════════
-echo "Installing networking..."
-$AUR_HELPER -S --noconfirm --needed \
-	networkmanager networkmanager-openvpn network-manager-applet \
-	dhclient dnsmasq iptables-nft iwd wpa_supplicant wireless-regdb \
-	bluez-libs blueman openssh
-
-# ═══════════════════════════════════════════════════════════
-# 🔊 AUDIO & VIDEO
-# ═══════════════════════════════════════════════════════════
-echo "Installing audio/video..."
-$AUR_HELPER -S --noconfirm --needed \
-	pipewire pipewire-alsa pipewire-pulse wireplumber \
-	pavucontrol alsa-firmware alsa-plugins alsa-utils \
-	gst-libav gst-plugin-pipewire gst-plugins-bad gst-plugins-ugly \
-	mpv ffmpegthumbnailer
-
-# ═══════════════════════════════════════════════════════════
-# 🎨 FONTS & THEMES
-# ═══════════════════════════════════════════════════════════
-echo "Installing fonts and themes..."
-$AUR_HELPER -S --noconfirm --needed \
-	adobe-source-han-sans-cn-fonts adobe-source-han-sans-jp-fonts \
-	adobe-source-han-sans-kr-fonts adobe-source-serif-fonts \
-	awesome-terminal-fonts inter-font noto-fonts noto-fonts-cjk noto-fonts-emoji \
-	ttf-bitstream-vera ttf-dejavu ttf-fantasque-nerd ttf-fira-code ttf-fira-mono \
-	ttf-fira-sans ttf-font-awesome-5 ttf-liberation ttf-linux-libertine \
-	ttf-meslo-nerd ttf-ms-win11-auto ttf-opensans otf-font-awesome-5 otf-libertinus \
-	papirus-icon-theme rose-pine-gtk-theme
-
-# ═══════════════════════════════════════════════════════════
-# 💻 DEVELOPMENT TOOLS
-# ═══════════════════════════════════════════════════════════
-echo "Installing development tools..."
-$AUR_HELPER -S --noconfirm --needed \
-	git git-lfs git-crypt github-cli lazygit \
-	go go-task python python-defusedxml python-packaging \
-	python-protobuf python-pynvim python-pywlroots lua luarocks \
-	ccls cmake ninja neovim-nightly-bin
-
-# ═══════════════════════════════════════════════════════════
-# 🛠️ CLI UTILITIES
-# ═══════════════════════════════════════════════════════════
-echo "Installing CLI utilities..."
-$AUR_HELPER -S --noconfirm --needed \
-	bat btop duf dysk dust eza fd fzf glances glow httpie ncdu \
-	plocate ripgrep tealdeer the_silver_searcher tmux wget \
-	xdg-user-dirs zoxide zellij yazi yq zsh
-
-# ═══════════════════════════════════════════════════════════
-# 🪟 WINDOW MANAGERS & DESKTOP
-# ═══════════════════════════════════════════════════════════
-echo "Installing window managers..."
-$AUR_HELPER -S --noconfirm --needed \
-	swaybg swayfx-git swayidle swaylock-effects waybar \
-	wlogout wdisplays wl-clipboard rofi-lbonn-wayland-git
-
-# ═══════════════════════════════════════════════════════════
-# 🌍 GUI APPLICATIONS
-# ═══════════════════════════════════════════════════════════
-echo "Installing GUI applications..."
-$AUR_HELPER -S --noconfirm --needed \
-	chromium zen-browser-bin \
-	legcord-bin slack-desktop teams-for-linux-bin \
-	obs-studio obsidian sublime-merge dbeaver \
-	libreoffice-fresh kitty nemo nemo-fileroller
-
-# ═══════════════════════════════════════════════════════════
-# 📁 SYSTEM UTILITIES
-# ═══════════════════════════════════════════════════════════
-echo "Installing system utilities..."
-$AUR_HELPER -S --noconfirm --needed \
-	brightnessctl cronie cups cups-pdf gamemode gvfs gvfs-afc gvfs-google \
-	gvfs-gphoto2 gvfs-mtp gvfs-nfs gvfs-smb haveged hdparm less lvm2 \
-	man-db man-pages meld modemmanager mtools mupdf netctl nss-mdns \
-	ntfs-3g ntp nvme-cli opencl-mesa pacman-contrib pass pika-backup \
-	pkgfile power-profiles-daemon pv reflector remmina rsync rtkit \
-	samba seahorse sg3_utils smartmontools snapper solaar steam \
-	steam-native-runtime syncthing system-config-printer timeshift \
-	transmission-qt ufw unrar unzip upower usb_modeswitch usbutils \
-	vi vorta w3m which xdg-desktop-portal xdg-desktop-portal-gnome \
-	xdg-desktop-portal-gtk xdg-desktop-portal-wlr yad zenity
-
-# ═══════════════════════════════════════════════════════════
-# 🎮 GAMING & GRAPHICS
-# ═══════════════════════════════════════════════════════════
-echo "Installing gaming and graphics..."
-GAMING_BASE="lib32-alsa-lib lib32-alsa-plugins lib32-gamemode lib32-libpulse lib32-mesa lib32-openal lib32-vkd3d lib32-vulkan-mesa-layers vkd3d vulkan-mesa-layers vulkan-tools vulkan-validation-layers wine protonup-qt"
-
-if [[ -n "$GPU_PACKAGES" ]]; then
-	$AUR_HELPER -S --noconfirm --needed $GAMING_BASE $GPU_PACKAGES
-else
-	$AUR_HELPER -S --noconfirm --needed $GAMING_BASE
-fi
-
-# ═══════════════════════════════════════════════════════════
-# 📱 MOBILE & HARDWARE
-# ═══════════════════════════════════════════════════════════
-echo "Installing hardware support..."
-$AUR_HELPER -S --noconfirm --needed \
-	sof-firmware xf86-input-libinput xfsprogs \
-	xorg-server xorg-xdpyinfo xorg-xhost xorg-xinit xorg-xinput \
-	xorg-xkill xorg-xrandr xorg-xwayland
-
-# ═══════════════════════════════════════════════════════════
-# ☁️ CLOUD & DEVOPS
-# ═══════════════════════════════════════════════════════════
-echo "Installing cloud and DevOps tools..."
-$AUR_HELPER -S --noconfirm --needed \
-	aws-cli aws-session-manager-plugin insomnia-bin \
-	kubectl kubectx kubefwd-bin ngrok postgresql
-
-# ═══════════════════════════════════════════════════════════
-# 📚 DOCUMENT & PRODUCTIVITY
-# ═══════════════════════════════════════════════════════════
-echo "Installing document tools..."
-$AUR_HELPER -S --noconfirm --needed \
-	pandoc-cli texinfo wkhtmltopdf-bin zathura zathura-pdf-mupdf \
-	swappy wf-recorder
-
-# ═══════════════════════════════════════════════════════════
-# 🔐 SECURITY & ENCRYPTION
-# ═══════════════════════════════════════════════════════════
-echo "Installing security tools..."
-$AUR_HELPER -S --noconfirm --needed \
-	yubico-authenticator-bin yubikey-manager-qt
-
-# ═══════════════════════════════════════════════════════════
-# 📦 PACKAGE MANAGERS & MISC
-# ═══════════════════════════════════════════════════════════
-echo "Installing package managers and misc..."
-$AUR_HELPER -S --noconfirm --needed \
-	pyenv watchman-bin wimlib
-
-tldr --update
+}
+
+check_requirements() {
+	if ! command_exists paru; then
+		log_error "paru not found. Install it first"
+		exit 1
+	fi
+}
+
+detect_hardware() {
+	local cpu_vendor gpu_info microcode gpu_packages
+
+	cpu_vendor=$(lscpu | grep "Vendor ID" | awk '{print $3}')
+	gpu_info=$(lspci | grep -i vga)
+
+	log_info "Detected CPU: $cpu_vendor"
+	log_info "Detected GPU: $gpu_info"
+
+	# Determine microcode package
+	case "$cpu_vendor" in
+	"GenuineIntel")
+		microcode="intel-ucode"
+		log_info "Using Intel microcode"
+		;;
+	"AuthenticAMD")
+		microcode="amd-ucode"
+		log_info "Using AMD microcode"
+		;;
+	*)
+		microcode=""
+		log_warn "Unknown CPU vendor, skipping microcode"
+		;;
+	esac
+
+	# Determine GPU drivers
+	gpu_packages=""
+	if echo "$gpu_info" | grep -qi "amd\|radeon"; then
+		gpu_packages="xf86-video-amdgpu vulkan-radeon lib32-vulkan-radeon opencl-mesa lib32-opencl-mesa"
+		log_info "Using AMD GPU drivers"
+	elif echo "$gpu_info" | grep -qi "intel"; then
+		gpu_packages="xf86-video-intel vulkan-intel lib32-vulkan-intel intel-media-driver"
+		log_info "Using Intel GPU drivers"
+	elif echo "$gpu_info" | grep -qi "nvidia"; then
+		gpu_packages="nvidia nvidia-utils lib32-nvidia-utils nvidia-settings"
+		log_info "Using NVIDIA drivers"
+	else
+		log_warn "Unknown GPU, using generic drivers"
+	fi
+}
+
+install_base_system() {
+	log_info "Installing system base packages"
+
+	local base_packages="base base-devel sudo stow linux-cachyos linux-cachyos-headers linux-firmware efibootmgr efitools mkinitcpio grub systemd-boot-manager"
+
+	if [[ -n "$microcode" ]]; then
+		base_packages="$base_packages $microcode"
+	fi
+
+	paru -S --needed --overwrite="*" $base_packages
+}
+
+install_networking() {
+	log_info "Installing networking packages"
+
+	paru -S --needed \
+		networkmanager networkmanager-openvpn network-manager-applet \
+		dhclient dnsmasq iptables-nft iwd wpa_supplicant wireless-regdb \
+		bluez-libs blueman openssh
+}
+
+install_audio_video() {
+	log_info "Installing audio and video packages"
+
+	paru -S --needed \
+		pipewire pipewire-alsa pipewire-pulse wireplumber \
+		pavucontrol alsa-firmware alsa-plugins alsa-utils \
+		gst-libav gst-plugin-pipewire gst-plugins-bad gst-plugins-ugly \
+		mpv ffmpegthumbnailer
+}
+
+install_fonts_themes() {
+	log_info "Installing fonts and themes"
+
+	paru -S --needed \
+		adobe-source-han-sans-cn-fonts adobe-source-han-sans-jp-fonts \
+		adobe-source-han-sans-kr-fonts adobe-source-serif-fonts \
+		awesome-terminal-fonts inter-font noto-fonts noto-fonts-cjk noto-fonts-emoji \
+		ttf-bitstream-vera ttf-dejavu ttf-fantasque-nerd ttf-fira-code ttf-fira-mono \
+		ttf-fira-sans ttf-font-awesome-5 ttf-liberation ttf-linux-libertine \
+		ttf-meslo-nerd ttf-ms-win11-auto ttf-opensans otf-font-awesome-5 otf-libertinus \
+		papirus-icon-theme rose-pine-gtk-theme
+}
+
+install_development() {
+	log_info "Installing development tools"
+
+	paru -S --needed \
+		git git-lfs git-crypt github-cli lazygit \
+		go go-task python python-defusedxml python-packaging \
+		python-protobuf python-pynvim python-pywlroots lua luarocks \
+		ccls cmake ninja neovim-nightly-bin
+}
+
+install_cli_utilities() {
+	log_info "Installing CLI utilities"
+
+	paru -S --needed \
+		bat btop duf dysk dust eza fd fzf glances glow httpie ncdu \
+		plocate ripgrep tealdeer the_silver_searcher tmux wget \
+		xdg-user-dirs zoxide yazi yq zsh
+}
+
+install_window_managers() {
+	log_info "Installing window managers and desktop environment"
+
+	paru -S --needed \
+		swaybg swayfx-git swayidle swaylock-effects waybar \
+		wlogout wdisplays wl-clipboard rofi-lbonn-wayland-git
+}
+
+install_gui_applications() {
+	log_info "Installing GUI applications"
+
+	paru -S --needed \
+		chromium zen-browser-bin \
+		legcord-bin slack-desktop teams-for-linux-bin \
+		obs-studio obsidian sublime-merge dbeaver \
+		libreoffice-fresh kitty nemo nemo-fileroller
+}
+
+install_system_utilities() {
+	log_info "Installing system utilities"
+
+	paru -S --needed \
+		brightnessctl cronie cups cups-pdf gamemode gvfs gvfs-afc gvfs-google \
+		gvfs-gphoto2 gvfs-mtp gvfs-nfs gvfs-smb haveged hdparm less lvm2 \
+		man-db man-pages meld modemmanager mtools mupdf netctl nss-mdns \
+		ntfs-3g ntp nvme-cli opencl-mesa pacman-contrib pass pika-backup \
+		pkgfile power-profiles-daemon pv reflector remmina rsync rtkit \
+		samba seahorse sg3_utils smartmontools snapper solaar steam \
+		steam-native-runtime syncthing system-config-printer \
+		transmission-qt ufw unrar unzip upower usb_modeswitch usbutils \
+		vi vorta w3m which xdg-desktop-portal xdg-desktop-portal-gnome \
+		xdg-desktop-portal-gtk xdg-desktop-portal-wlr yad zenity
+}
+
+install_gaming_graphics() {
+	log_info "Installing gaming and graphics packages"
+
+	local gaming_base="lib32-alsa-lib lib32-alsa-plugins lib32-gamemode lib32-libpulse lib32-mesa lib32-openal lib32-vkd3d lib32-vulkan-mesa-layers vkd3d vulkan-mesa-layers vulkan-tools vulkan-validation-layers wine protonup-qt"
+
+	if [[ -n "$gpu_packages" ]]; then
+		paru -S --needed $gaming_base $gpu_packages
+	else
+		paru -S --needed $gaming_base
+	fi
+}
+
+install_hardware_support() {
+	log_info "Installing hardware support"
+
+	paru -S --needed \
+		sof-firmware xf86-input-libinput xfsprogs \
+		xorg-server xorg-xdpyinfo xorg-xhost xorg-xinit xorg-xinput \
+		xorg-xkill xorg-xrandr xorg-xwayland
+}
+
+install_cloud_devops() {
+	log_info "Installing cloud and DevOps tools"
+
+	paru -S --needed \
+		aws-cli aws-session-manager-plugin insomnia-bin \
+		kubectl kubectx kubefwd-bin ngrok postgresql
+}
+
+install_document_tools() {
+	log_info "Installing document and productivity tools"
+
+	paru -S --needed \
+		pandoc-cli texinfo wkhtmltopdf-bin zathura zathura-pdf-mupdf \
+		swappy wf-recorder
+}
+
+install_security_tools() {
+	log_info "Installing security tools"
+
+	paru -S --needed \
+		yubico-authenticator-bin yubikey-manager-qt
+}
+
+install_misc_packages() {
+	log_info "Installing miscellaneous packages"
+
+	paru -S --needed \
+		pyenv watchman-bin wimlib \
+		slurp grim swaync gum nwg-look lxsession colordiff
+
+	log_info "Updating tldr database"
+	tldr --update
+}
+
+main() {
+	init_script
+
+	check_requirements
+	detect_hardware
+
+	install_base_system
+	install_networking
+	install_audio_video
+	install_fonts_themes
+	install_development
+	install_cli_utilities
+	install_window_managers
+	install_gui_applications
+	install_system_utilities
+	install_gaming_graphics
+	install_hardware_support
+	install_cloud_devops
+	install_document_tools
+	install_security_tools
+	install_misc_packages
+
+	finish_script 0
+}
+
+main "$@"

+ 31 - 29
runs/boot

@@ -1,37 +1,40 @@
 #!/usr/bin/env bash
 # NAME: Configure systemd-boot manager settings
+# REQUIRES: sudo
 
 set -euo pipefail
 
-SDBOOT_CONFIG="/etc/sdboot-manage.conf"
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+    echo "[ERROR] Could not source common.sh" >&2
+    exit 1
+}
 
-echo "Configuring systemd-boot manager..."
+readonly SDBOOT_CONFIG="/etc/sdboot-manage.conf"
 
-# Check if sdboot-manage is installed
-if ! command -v sdboot-manage &>/dev/null; then
-    echo "⚠️  sdboot-manage not found. Install systemd-boot-manager first."
-    exit 1
-fi
+check_requirements() {
+    if ! command_exists sdboot-manage; then
+        log_error "sdboot-manage not found. Install systemd-boot-manager first"
+        exit 1
+    fi
+}
+
+configure_systemd_boot() {
+    log_info "Configuring systemd-boot manager"
 
-# Backup existing config if it exists
-if [[ -f "$SDBOOT_CONFIG" ]]; then
-    sudo cp "$SDBOOT_CONFIG" "$SDBOOT_CONFIG.backup.$(date +%Y%m%d_%H%M%S)"
-    echo "→ Backed up existing config"
-fi
+    if [[ -f "$SDBOOT_CONFIG" ]]; then
+        backup_file "$SDBOOT_CONFIG"
+    fi
 
-# Create the configuration
-sudo tee "$SDBOOT_CONFIG" >/dev/null <<'EOF'
+    log_info "Creating systemd-boot configuration"
+    tee "$SDBOOT_CONFIG" >/dev/null <<'EOF'
 # Config file for sdboot-manage
 # Kernel options to be appended to the "options" line
 LINUX_OPTIONS="zswap.enabled=0 nowatchdog ipv6.disable=1 audit=0 loglevel=3 rd.systemd.show_status=auto rd.udev.log_level=3"
-#LINUX_FALLBACK_OPTIONS=""
 
 # When DISABLE_FALLBACK is set to "yes", it will stop creating fallback entries for each kernel.
 DISABLE_FALLBACK="no"
 
-# Use this pattern to match kernels which should be considered native OS kernels
-#KERNEL_PATTERN="vmlinuz-"
-
 # Setting REMOVE_EXISTING to "yes" will remove all your existing systemd-boot entries before building new entries
 REMOVE_EXISTING="yes"
 
@@ -41,19 +44,18 @@ OVERWRITE_EXISTING="yes"
 
 # When REMOVE_OBSOLETE is set to "yes" entries for kernels no longer available on the system will be removed
 REMOVE_OBSOLETE="yes"
+EOF
 
-# If PRESERVE_FOREIGN is set to "yes", do not delete entries starting with $ENTRY_ROOT
-#PRESERVE_FOREIGN="no"
+    log_info "systemd-boot configuration updated"
+}
 
-# Setting NO_AUTOUPDATE to "yes" will stop the updates to systemd-boot when systemd is updated - not recommended unless you are seperately updating systemd-boot
-#NO_AUTOUPDATE="no"
+main() {
+    init_script
 
-# Setting NO_AUTOGEN to "yes" will stop the automatic creation of entries when kernels are installed or updated
-#NO_AUTOGEN="no"
+    check_requirements
+    configure_systemd_boot
 
-# Use this to change the default initramfs prefix (e.g. when using booster)
-#INITRD_PREFIX="initramfs"
+    finish_script 0
+}
 
-# Additional entities to initrd can be added here, such as microcode if your initramfs does not include it.
-#INITRD_ENTRIES=()
-EOF
+main "$@"

+ 32 - 15
runs/docker

@@ -1,24 +1,41 @@
 #!/usr/bin/env bash
-# NAME: Install and configure Docker on Arch Linux
+# NAME: Install and configure Docker
 
 set -euo pipefail
 
-echo "Installing Docker..."
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+	echo "[ERROR] Could not source common.sh" >&2
+	exit 1
+}
 
-# Install Docker packages
-sudo pacman -S --needed --noconfirm docker docker-compose docker-buildx
+install_docker() {
+	log_info "Installing Docker packages"
+	sudo pacman -S --needed --noconfirm docker docker-compose docker-buildx
+}
 
-# Add current user to docker group
-sudo usermod -aG docker "$USER"
+configure_docker() {
+	log_info "Adding current user to docker group"
+	sudo usermod -aG docker "$USER"
 
-# Enable and start Docker service
-sudo systemctl enable docker.service
-sudo systemctl start docker.service
+	log_info "Enabling Docker services"
+	sudo systemctl enable docker.service
+	sudo systemctl start docker.service
+	sudo systemctl enable containerd.service
+	sudo systemctl start containerd.service
+}
 
-# Enable containerd service (dependency)
-sudo systemctl enable containerd.service
-sudo systemctl start containerd.service
+main() {
+	init_script
 
-echo "✅ Docker installed successfully!"
-echo "⚠️  You need to log out and back in (or reboot) for group changes to take effect"
-echo "🐳 Test with: docker run hello-world"
+	install_docker
+	configure_docker
+
+	log_info "Docker installed successfully"
+	log_warn "You need to log out and back in (or reboot) for group changes to take effect"
+	log_info "Test with: docker run hello-world"
+
+	finish_script 0
+}
+
+main "$@"

+ 102 - 135
runs/dotfiles

@@ -1,151 +1,118 @@
 #!/usr/bin/env bash
-# NAME: Setup dotfiles and Neovim configuration
+# NAME: Setup dotfiles and configuration
+# REQUIRES: interactive
 
 set -euo pipefail
 
-DOTFILES_REPO="https://git.mz.uy/marianozunino/dotfiles.git"
-NVIM_REPO="https://github.com/marianozunino/nvim.git"
-DOTFILES_DIR="$HOME/dotfiles"
-NVIM_CONFIG_DIR="$HOME/.config/nvim"
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+    echo "[ERROR] Could not source common.sh" >&2
+    exit 1
+}
 
-echo "🏠 Setting up Forbi's development environment..."
+readonly DOTFILES_REPO="https://git.mz.uy/marianozunino/dotfiles.git"
+readonly DOTFILES_DIR="$HOME/dotfiles"
 
-# ═══════════════════════════════════════════════════════════
-# 📁 CLONE DOTFILES
-# ═══════════════════════════════════════════════════════════
-echo "📦 Cloning dotfiles repository..."
+check_requirements() {
+    local required_commands=(git git-crypt stow)
 
-if [[ -d "$DOTFILES_DIR" ]]; then
-    echo "→ Dotfiles directory already exists, updating..."
-    cd "$DOTFILES_DIR"
-    git pull origin main || git pull origin master
-else
-    echo "→ Cloning dotfiles to $DOTFILES_DIR"
-    git clone "$DOTFILES_REPO" "$DOTFILES_DIR"
-fi
+    for cmd in "${required_commands[@]}"; do
+        if ! command_exists "$cmd"; then
+            log_error "Required command not found: $cmd"
+            exit 1
+        fi
+    done
+}
 
-# ═══════════════════════════════════════════════════════════
-# 🔐 HANDLE GIT-CRYPT
-# ═══════════════════════════════════════════════════════════
-echo "🔐 Checking for git-crypt..."
+clone_dotfiles() {
+    log_info "Setting up dotfiles repository"
 
-cd "$DOTFILES_DIR"
+    if [[ -d "$DOTFILES_DIR" ]]; then
+        log_info "Dotfiles directory already exists, updating"
+        cd "$DOTFILES_DIR"
+        git pull origin main || git pull origin master
+    else
+        log_info "Cloning dotfiles to $DOTFILES_DIR"
+        git clone "$DOTFILES_REPO" "$DOTFILES_DIR"
+    fi
+}
 
-# Check if repo uses git-crypt
-if [[ -f ".git-crypt/.gitattributes" ]] || git-crypt status &>/dev/null; then
-    echo "→ git-crypt repository detected"
+handle_git_crypt() {
+    log_info "Checking git-crypt status"
+    cd "$DOTFILES_DIR"
 
-    # Check if already unlocked
     if git-crypt status | grep -q "unlocked"; then
-        echo "→ Repository already unlocked"
+        log_info "Repository already unlocked"
+        return 0
+    fi
+
+    log_info "Repository is encrypted, attempting to unlock"
+
+    if gpg --card-status &>/dev/null; then
+        log_info "YubiKey detected, fetching public key"
+
+        local key_id
+        key_id=$(gpg --card-status 2>/dev/null | grep "General key info" -A 1 | tail -1 | awk '{print $1}' | sed 's/.*\///')
+
+        if [[ -n "$key_id" ]] && ! gpg --list-keys "$key_id" &>/dev/null; then
+            gpg --card-edit --batch --command-fd 0 <<<"fetch" &>/dev/null ||
+                gpg --keyserver hkps://keys.openpgp.org --recv-keys "$key_id" &>/dev/null || true
+        fi
+    else
+        log_error "YubiKey not detected"
+        exit 1
+    fi
+
+    if git-crypt unlock 2>/dev/null; then
+        log_info "Repository unlocked successfully"
     else
-        echo "→ Repository is encrypted, attempting to unlock..."
-
-        # Check if YubiKey/GPG setup is available
-        if command -v gpg &>/dev/null; then
-            echo "→ GPG found, checking for available keys..."
-
-            # List secret keys to see if YubiKey is connected
-            if gpg --list-secret-keys &>/dev/null; then
-                echo "→ GPG keys detected, attempting git-crypt unlock..."
-
-                # Attempt to unlock with GPG (YubiKey)
-                if git-crypt unlock 2>/dev/null; then
-                    echo "✅ Repository unlocked successfully with GPG/YubiKey!"
-                else
-                    echo "❌ Failed to unlock with GPG"
-                    echo
-                    echo "🔑 Manual unlock required:"
-                    echo "  1. Make sure your YubiKey is connected"
-                    echo "  2. Test GPG: gpg --card-status"
-                    echo "  3. Try: cd $DOTFILES_DIR && git-crypt unlock"
-                    echo "  4. Or unlock with key file: git-crypt unlock /path/to/key"
-                    echo "  5. Then re-run: ./run forbi"
-                    echo
-                    exit 1
-                fi
-            else
-                echo "❌ No GPG secret keys found"
-                echo
-                echo "🔑 YubiKey/GPG setup needed:"
-                echo "  1. Connect your YubiKey"
-                echo "  2. Import your GPG key: gpg --card-status"
-                echo "  3. Set trust level: gpg --edit-key <your-key-id> trust"
-                echo "  4. Then re-run: ./run forbi"
-                echo
-                exit 1
-            fi
+        log_error "Failed to unlock repository"
+        log_info "Manual unlock required: cd $DOTFILES_DIR && git-crypt unlock"
+        exit 1
+    fi
+}
+
+create_directories() {
+    log_info "Creating necessary directories"
+    mkdir -p "$HOME/.config" "$HOME/Dev" "$HOME/.local/bin"
+}
+
+stow_packages() {
+    cd "$DOTFILES_DIR"
+
+    log_info "Available stow packages:"
+    for dir in */; do
+        if [[ -d "$dir" ]]; then
+            log_info "  ${dir%/}"
+        fi
+    done
+
+    if confirm "Stow all packages?" "y"; then
+        log_info "Stowing all packages"
+        if stow */; then
+            log_info "All packages stowed successfully"
         else
-            echo "❌ GPG not available"
-            echo
-            echo "🔑 To unlock your dotfiles:"
-            echo "  1. Install GPG and connect YubiKey, or"
-            echo "  2. Use key file: cd $DOTFILES_DIR && git-crypt unlock /path/to/key"
-            echo "  3. Then re-run: ./run forbi"
-            echo
-            exit 1
+            log_error "Some packages failed to stow. Check for conflicts"
         fi
+    else
+        log_info "Skipping automatic stow. Run manually:"
+        log_info "  cd $DOTFILES_DIR"
+        log_info "  stow <package_name>"
     fi
-else
-    echo "→ No git-crypt encryption detected"
-fi
-
-# ═══════════════════════════════════════════════════════════
-# ⚙️ CLONE NEOVIM CONFIG
-# ═══════════════════════════════════════════════════════════
-echo "📝 Setting up Neovim configuration..."
-
-# Backup existing Neovim config if it exists
-if [[ -d "$NVIM_CONFIG_DIR" ]]; then
-    BACKUP_DIR="$NVIM_CONFIG_DIR.backup.$(date +%Y%m%d_%H%M%S)"
-    echo "→ Backing up existing Neovim config to $BACKUP_DIR"
-    mv "$NVIM_CONFIG_DIR" "$BACKUP_DIR"
-fi
-
-# Clone Neovim configuration
-echo "→ Cloning Neovim config to $NVIM_CONFIG_DIR"
-git clone "$NVIM_REPO" "$NVIM_CONFIG_DIR"
-
-# ═══════════════════════════════════════════════════════════
-# 🔧 CONFIGURE GIT HOOKS
-# ═══════════════════════════════════════════════════════════
-echo "🎣 Setting up Git hooks for Neovim config..."
-
-cd "$NVIM_CONFIG_DIR"
-if [[ -d ".githooks" ]]; then
-    git config core.hooksPath .githooks
-    echo "→ Git hooks configured"
-else
-    echo "→ No .githooks directory found, skipping"
-fi
-
-# ═══════════════════════════════════════════════════════════
-# 📂 CREATE NECESSARY DIRECTORIES
-# ═══════════════════════════════════════════════════════════
-echo "📂 Creating necessary directories..."
-
-# Create .config directory if it doesn't exist
-mkdir -p "$HOME/.config"
-
-# Create common development directories
-mkdir -p "$HOME/Dev"
-mkdir -p "$HOME/.local/bin"
-
-echo
-echo "# Install everything"
-echo "stow */"
-echo
-echo "# Or install individual modules"
-echo "stow zsh"
-echo "stow nvim"
-echo "stow sway"
-echo
-echo "Run these commands in: $DOTFILES_DIR"
-
-# ═══════════════════════════════════════════════════════════
-# 📋 SUMMARY & NEXT STEPS
-# ═══════════════════════════════════════════════════════════
-echo
-echo "📁 Locations:"
-echo "  • Dotfiles: $DOTFILES_DIR"
-echo "  • Neovim config: $NVIM_CONFIG_DIR"
+}
+
+main() {
+    init_script
+
+    check_requirements
+    clone_dotfiles
+    handle_git_crypt
+    create_directories
+    stow_packages
+
+    log_info "Dotfiles location: $DOTFILES_DIR"
+
+    finish_script 0
+}
+
+main "$@"

+ 0 - 211
runs/limine

@@ -1,211 +0,0 @@
-#!/bin/bash
-# NAME: Script to transition from systemd-boot to Limine bootloader
-# REQUIRES: sudo
-
-set -euo pipefail
-
-echo "do you confirm this transition?"
-
-read -r -p "Are you sure? [y/N] " response
-
-if [[ "$response" != "y" ]]; then
-	exit 0
-fi
-
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-NC='\033[0m'
-
-print_status() { echo -e "${BLUE}[INFO]${NC} $1"; }
-print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
-print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
-print_error() { echo -e "${RED}[ERROR]${NC} $1"; }
-
-if [[ $EUID -ne 0 ]]; then
-	print_error "This script must be run as root (use sudo)"
-	exit 1
-fi
-
-if ! command -v pacman &>/dev/null; then
-	print_error "This script is designed for Arch Linux / CachyOS systems"
-	exit 1
-fi
-
-print_status "Starting transition from systemd-boot to Limine..."
-
-ESP_PATH=""
-if command -v bootctl &>/dev/null; then
-	ESP_PATH=$(bootctl --print-esp-path 2>/dev/null || echo "")
-fi
-
-if [[ -z "$ESP_PATH" ]]; then
-	if [[ -d "/boot/EFI" ]]; then
-		ESP_PATH="/boot"
-	elif [[ -d "/efi/EFI" ]]; then
-		ESP_PATH="/efi"
-	else
-		print_error "Could not detect ESP path"
-		exit 1
-	fi
-fi
-
-print_success "ESP detected at: $ESP_PATH"
-
-BACKUP_DIR="/root/bootloader-backup-$(date +%Y%m%d-%H%M%S)"
-mkdir -p "$BACKUP_DIR"
-
-[[ -d "$ESP_PATH/loader" ]] && cp -r "$ESP_PATH/loader" "$BACKUP_DIR/" 2>/dev/null || true
-[[ -d "$ESP_PATH/EFI/systemd" ]] && cp -r "$ESP_PATH/EFI/systemd" "$BACKUP_DIR/" 2>/dev/null || true
-efibootmgr -v >"$BACKUP_DIR/efibootmgr-backup.txt" 2>/dev/null || true
-
-print_success "Backup created at: $BACKUP_DIR"
-
-CURRENT_CMDLINE=""
-if [[ -f "/etc/kernel/cmdline" ]]; then
-	CURRENT_CMDLINE=$(cat /etc/kernel/cmdline)
-	print_status "Found existing /etc/kernel/cmdline: $CURRENT_CMDLINE"
-else
-	CURRENT_CMDLINE=$(cat /proc/cmdline | sed 's/BOOT_IMAGE=[^ ]* //')
-	print_status "Using current /proc/cmdline: $CURRENT_CMDLINE"
-fi
-
-print_status "Installing Limine and related packages..."
-pacman -S --needed --noconfirm limine limine-snapper-sync limine-mkinitcpio-hook
-print_success "Limine packages installed"
-
-print_status "Installing Limine bootloader to ESP..."
-mkdir -p "$ESP_PATH/EFI/Limine"
-
-if [[ -f "/usr/share/limine/limine_x64.efi" ]]; then
-	cp /usr/share/limine/limine_x64.efi "$ESP_PATH/EFI/Limine/"
-elif [[ -f "/usr/share/limine/BOOTX64.EFI" ]]; then
-	cp /usr/share/limine/BOOTX64.EFI "$ESP_PATH/EFI/Limine/limine_x64.efi"
-else
-	print_error "Could not find Limine EFI files"
-	exit 1
-fi
-
-print_success "Limine files installed to ESP"
-
-print_status "Creating UEFI boot entry for Limine..."
-ESP_DEVICE=$(df "$ESP_PATH" | tail -1 | awk '{print $1}')
-ESP_DISK=$(echo "$ESP_DEVICE" | sed 's/[0-9]*$//')
-ESP_PART_NUM=$(echo "$ESP_DEVICE" | sed 's/.*[^0-9]//')
-
-efibootmgr -c -d "$ESP_DISK" -p "$ESP_PART_NUM" -L "Limine" -l "\\EFI\\Limine\\limine_x64.efi" || {
-	print_warning "Could not create UEFI boot entry automatically"
-}
-
-print_success "Limine UEFI boot entry created"
-
-print_status "Configuring kernel command line..."
-echo "$CURRENT_CMDLINE" >/etc/kernel/cmdline
-print_success "Kernel command line configured: $CURRENT_CMDLINE"
-
-print_status "Configuring Limine..."
-[[ ! -f "/etc/default/limine" ]] && cat >/etc/default/limine <<EOF
-SPACE_NUMBER=2
-ENABLE_VERIFICATION=yes
-BACKUP_THRESHOLD=8
-BOOT_ORDER="*, *fallback, Snapshots"
-ENABLE_SORT=no
-ENABLE_LIMINE_FALLBACK=no
-FIND_BOOTLOADERS=yes
-ROOT_SNAPSHOTS_PATH="/@snapshots"
-EOF
-
-print_status "Creating custom Limine configuration..."
-cat >"$ESP_PATH/limine.conf" <<'EOF'
-### Read more at config document: https://github.com/limine-bootloader/limine/blob/trunk/CONFIG.md
-timeout: 1
-### Note: For "default_entry" to select a sub-entry within an OS menu, modify "/OS name" to "/+OS name" to keep its submenus visible.
-default_entry: 2
-#interface_branding_color: 3
-interface_branding:
-hash_mismatch_panic: no
-term_palette: 1e1e2e;f38ba8;a6e3a1;f9e2af;89b4fa;f5c2e7;94e2d5;cdd6f4
-term_palette_bright: 585b70;f38ba8;a6e3a1;f9e2af;89b4fa;f5c2e7;94e2d5;cdd6f4
-term_background: 1e1e2e
-term_foreground: cdd6f4
-term_background_bright: 585b70
-term_foreground_bright: cdd6f4
-
-EOF
-
-print_status "Generating Limine boot entries..."
-limine-update
-print_success "Limine configuration created"
-
-print_status "Setting Limine as default boot option..."
-LIMINE_ENTRY=$(efibootmgr | grep "Limine" | head -1 | cut -c5-8)
-
-if [[ -n "$LIMINE_ENTRY" ]]; then
-	CURRENT_ORDER=$(efibootmgr | grep "BootOrder" | cut -d' ' -f2)
-	NEW_ORDER=$(echo "$CURRENT_ORDER" | sed "s/$LIMINE_ENTRY,//g" | sed "s/,$LIMINE_ENTRY//g" | sed "s/^$LIMINE_ENTRY$//g")
-
-	if [[ -n "$NEW_ORDER" ]]; then
-		efibootmgr -o "$LIMINE_ENTRY,$NEW_ORDER"
-	else
-		efibootmgr -o "$LIMINE_ENTRY"
-	fi
-
-	print_success "Limine set as default boot option"
-else
-	print_warning "Could not find Limine boot entry to set as default"
-fi
-
-print_status "Removing systemd-boot components..."
-
-# Remove systemd-boot UEFI entries
-SYSTEMD_ENTRIES=$(efibootmgr | grep -i "Linux Boot Manager\|systemd" | cut -c5-8 || true)
-if [[ -n "$SYSTEMD_ENTRIES" ]]; then
-	for entry in $SYSTEMD_ENTRIES; do
-		print_status "Removing UEFI boot entry: Boot$entry"
-		efibootmgr -b "$entry" -B || print_warning "Failed to remove boot entry $entry"
-	done
-fi
-
-# Remove systemd-boot files from ESP
-if [[ -d "$ESP_PATH/EFI/systemd" ]]; then
-	print_status "Removing systemd-boot EFI files..."
-	rm -rf "$ESP_PATH/EFI/systemd"
-	print_success "systemd-boot EFI files removed"
-fi
-
-if [[ -d "$ESP_PATH/loader" ]]; then
-	print_status "Removing systemd-boot configuration..."
-	rm -rf "$ESP_PATH/loader"
-	print_success "systemd-boot configuration removed"
-fi
-
-if [[ -f "$ESP_PATH/limine.conf" ]]; then
-	print_success "Limine configuration file exists"
-else
-	print_error "Limine configuration file missing!"
-fi
-
-if [[ -f "$ESP_PATH/EFI/Limine/limine_x64.efi" ]]; then
-	print_success "Limine EFI bootloader exists"
-else
-	print_error "Limine EFI bootloader missing!"
-fi
-
-sudo pacman -S snap-pac
-sudo systemctl enable --now limine-snapper-sync.service
-
-echo
-print_success "TRANSITION COMPLETED SUCCESSFULLY!"
-echo
-print_status "Summary:"
-echo "  • Limine bootloader installed to: $ESP_PATH/EFI/Limine/"
-echo "  • Configuration file: $ESP_PATH/limine.conf"
-echo "  • Kernel command line: /etc/kernel/cmdline"
-echo "  • Backup created at: $BACKUP_DIR"
-echo "  • Boot order updated"
-echo "  • systemd-boot completely removed"
-echo
-print_warning "IMPORTANT: Reboot to test the new Limine setup!"
-print_warning "systemd-boot has been completely removed - ensure Limine works before removing backup!"
-print_success "Script completed. Please reboot when ready."

+ 279 - 0
runs/limine_hibernate

@@ -0,0 +1,279 @@
+#!/usr/bin/env bash
+# NAME: Configure hibernation for CachyOS with Limine bootloader
+# REQUIRES: sudo interactive
+
+set -euo pipefail
+
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+	echo "[ERROR] Could not source common.sh" >&2
+	exit 1
+}
+
+readonly LIMINE_CONFIG="/etc/default/limine"
+readonly MKINITCPIO_CONFIG="/etc/mkinitcpio.conf"
+
+check_requirements() {
+	local swap_uuid
+	swap_uuid=$(blkid -t TYPE=swap -o value -s UUID | head -n1)
+
+	if [[ -z "$swap_uuid" ]]; then
+		log_error "No swap partition found"
+		log_info "Please create a swap partition or swapfile before running this script"
+		exit 1
+	fi
+
+	log_info "Found swap partition UUID: $swap_uuid"
+	readonly SWAP_UUID="$swap_uuid"
+}
+
+escape_for_sed() {
+	printf '%s\n' "$1" | sed 's/[[\.*^$()+?{|]/\\&/g'
+}
+
+show_diff_and_confirm() {
+	local file="$1"
+	local description="$2"
+	local temp_file="$3"
+
+	log_info "Proposed changes for $description"
+	log_info "File: $file"
+	echo
+
+	if command_exists colordiff; then
+		colordiff -u "$file" "$temp_file" || true
+	else
+		diff -u "$file" "$temp_file" || true
+	fi
+
+	echo
+	confirm "Apply these changes?"
+}
+
+update_limine_config() {
+	log_info "Updating Limine configuration"
+
+	backup_file "$LIMINE_CONFIG"
+
+	local current_cmdline new_cmdline changes_made=false
+	current_cmdline=$(grep 'KERNEL_CMDLINE\[default\]' "$LIMINE_CONFIG" | cut -d'"' -f2)
+	log_info "Current kernel cmdline: $current_cmdline"
+
+	new_cmdline="$current_cmdline"
+
+	if [[ ! "$new_cmdline" =~ resume= ]]; then
+		new_cmdline="$new_cmdline resume=UUID=$SWAP_UUID"
+		log_info "Adding resume parameter"
+		changes_made=true
+	fi
+
+	if [[ ! "$new_cmdline" =~ ipv6.disable= ]]; then
+		new_cmdline="$new_cmdline ipv6.disable=1"
+		log_info "Adding ipv6.disable parameter"
+		changes_made=true
+	fi
+
+	new_cmdline=$(echo "$new_cmdline" | sed 's/  */ /g' | sed 's/^ *//' | sed 's/ *$//')
+
+	if [[ "$changes_made" == "true" ]]; then
+		log_info "New kernel cmdline: $new_cmdline"
+
+		local new_cmdline_escaped temp_file
+		new_cmdline_escaped=$(escape_for_sed "$new_cmdline")
+		temp_file=$(mktemp)
+
+		sed "s|KERNEL_CMDLINE\[default\]=\".*\"|KERNEL_CMDLINE[default]=\"$new_cmdline_escaped\"|" "$LIMINE_CONFIG" >"$temp_file"
+
+		if show_diff_and_confirm "$LIMINE_CONFIG" "LIMINE CONFIGURATION" "$temp_file"; then
+			cp "$temp_file" "$LIMINE_CONFIG"
+			log_info "Limine configuration updated successfully"
+			rm "$temp_file"
+			return 0
+		else
+			log_info "Skipping Limine configuration update"
+			log_info "You can manually add: resume=UUID=$SWAP_UUID to your kernel cmdline"
+			rm "$temp_file"
+			return 1
+		fi
+	else
+		log_info "Limine configuration already contains required parameters"
+		return 1
+	fi
+}
+
+update_mkinitcpio_config() {
+	log_info "Updating mkinitcpio configuration"
+
+	backup_file "$MKINITCPIO_CONFIG"
+
+	if grep -q '^HOOKS=.*resume' "$MKINITCPIO_CONFIG"; then
+		log_info "Resume hook already present in mkinitcpio.conf"
+		return 1
+	fi
+
+	log_info "Adding resume hook to mkinitcpio.conf"
+
+	local current_hooks new_hooks temp_file
+	current_hooks=$(grep '^HOOKS=' "$MKINITCPIO_CONFIG")
+	log_info "Current HOOKS: $current_hooks"
+
+	new_hooks=$(echo "$current_hooks" | sed 's/filesystems/filesystems resume/' | sed 's/resume resume/resume/')
+
+	if [[ ! "$new_hooks" =~ resume ]]; then
+		new_hooks=$(echo "$current_hooks" | sed 's/)/ resume)/')
+	fi
+
+	log_info "New HOOKS: $new_hooks"
+
+	local new_hooks_escaped
+	new_hooks_escaped=$(escape_for_sed "$new_hooks")
+	temp_file=$(mktemp)
+
+	sed "s|^HOOKS=.*|$new_hooks_escaped|" "$MKINITCPIO_CONFIG" >"$temp_file"
+
+	if show_diff_and_confirm "$MKINITCPIO_CONFIG" "MKINITCPIO CONFIGURATION" "$temp_file"; then
+		cp "$temp_file" "$MKINITCPIO_CONFIG"
+		log_info "mkinitcpio.conf updated successfully"
+		rm "$temp_file"
+		return 0
+	else
+		log_info "Skipping mkinitcpio.conf update"
+		log_info "You can manually add 'resume' to your HOOKS array"
+		rm "$temp_file"
+		return 1
+	fi
+}
+
+apply_system_changes() {
+	local limine_updated="$1"
+	local mkinitcpio_updated="$2"
+	local operations_needed=()
+
+	log_info "System updates required"
+
+	if [[ "$mkinitcpio_updated" == "true" ]]; then
+		operations_needed+=("Regenerate initramfs (mkinitcpio -P)")
+	fi
+
+	if [[ "$limine_updated" == "true" ]]; then
+		operations_needed+=("Update Limine bootloader (limine-update)")
+	fi
+
+	if [[ ${#operations_needed[@]} -eq 0 ]]; then
+		log_info "No system updates needed - configuration is already up to date"
+		return 0
+	fi
+
+	log_info "The following operations need to be performed:"
+	for i in "${!operations_needed[@]}"; do
+		log_info "  $((i + 1)). ${operations_needed[$i]}"
+	done
+
+	if ! confirm "Apply all pending system changes?" "y"; then
+		log_warn "Skipping system updates"
+		log_warn "Configuration files have been updated but system changes were not applied"
+		log_info "You will need to manually run:"
+
+		if [[ "$mkinitcpio_updated" == "true" ]]; then
+			log_info "  mkinitcpio -P"
+		fi
+
+		if [[ "$limine_updated" == "true" ]]; then
+			log_info "  limine-update"
+		fi
+
+		log_warn "Hibernation will not work until these commands are executed"
+		return 1
+	fi
+
+	log_info "Applying system changes"
+
+	if [[ "$mkinitcpio_updated" == "true" ]]; then
+		log_info "Regenerating initramfs"
+		if ! mkinitcpio -P; then
+			log_error "Failed to regenerate initramfs"
+			log_info "Please check the configuration and run 'mkinitcpio -P' manually"
+			exit 1
+		fi
+		log_info "Initramfs regenerated successfully"
+	fi
+
+	if [[ "$limine_updated" == "true" ]]; then
+		log_info "Updating Limine configuration"
+		if ! limine-update; then
+			log_error "Failed to update Limine bootloader"
+			log_info "Please run 'limine-update' manually"
+			exit 1
+		fi
+		log_info "Limine bootloader updated successfully"
+	fi
+
+	return 0
+}
+
+show_completion_summary() {
+	local limine_updated="$1"
+	local mkinitcpio_updated="$2"
+
+	log_info "Hibernation configuration completed successfully"
+	echo
+	log_info "Configuration details:"
+	log_info "  - Swap UUID: $SWAP_UUID"
+
+	if [[ "$limine_updated" == "true" ]]; then
+		log_info "  - Resume parameter added to kernel cmdline"
+		log_info "  - IPv6 disabled (optional optimization)"
+	else
+		log_info "  - Kernel cmdline already configured"
+	fi
+
+	if [[ "$mkinitcpio_updated" == "true" ]]; then
+		log_info "  - Resume hook added to initramfs"
+	else
+		log_info "  - Resume hook already present in initramfs"
+	fi
+
+	echo
+	log_info "Backups created:"
+	log_info "  - $LIMINE_CONFIG.backup.*"
+	log_info "  - $MKINITCPIO_CONFIG.backup.*"
+	echo
+	log_info "Next steps:"
+	log_info "  1. Reboot your system"
+	log_info "  2. Test hibernation with: systemctl hibernate"
+	log_info "  3. Wake up and verify everything works correctly"
+	echo
+	log_info "If you encounter issues, restore backups with:"
+	log_info "  cp $LIMINE_CONFIG.backup.* $LIMINE_CONFIG"
+	log_info "  cp $MKINITCPIO_CONFIG.backup.* $MKINITCPIO_CONFIG"
+	log_info "  mkinitcpio -P && limine-update"
+}
+
+main() {
+	init_script
+
+	check_requirements
+
+	local limine_updated mkinitcpio_updated
+
+	if update_limine_config; then
+		limine_updated="true"
+	else
+		limine_updated="false"
+	fi
+
+	if update_mkinitcpio_config; then
+		mkinitcpio_updated="true"
+	else
+		mkinitcpio_updated="false"
+	fi
+
+	if apply_system_changes "$limine_updated" "$mkinitcpio_updated"; then
+		show_completion_summary "$limine_updated" "$mkinitcpio_updated"
+		finish_script 0
+	else
+		finish_script 1
+	fi
+}
+
+main "$@"

+ 55 - 0
runs/nvim

@@ -0,0 +1,55 @@
+#!/usr/bin/env bash
+# NAME: Setup Neovim configuration
+# REQUIRES: interactive
+
+set -euo pipefail
+
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+    echo "[ERROR] Could not source common.sh" >&2
+    exit 1
+}
+
+readonly NVIM_REPO="https://github.com/marianozunino/nvim.git"
+readonly NVIM_CONFIG_DIR="$HOME/.config/nvim"
+
+check_requirements() {
+    if ! command_exists git; then
+        log_error "git is required but not installed"
+        exit 1
+    fi
+}
+
+setup_neovim_config() {
+    log_info "Setting up Neovim configuration"
+
+    if [[ -d "$NVIM_CONFIG_DIR" ]]; then
+        local backup_dir="$NVIM_CONFIG_DIR.backup.$(date +%Y%m%d_%H%M%S)"
+        log_info "Backing up existing Neovim config to $backup_dir"
+        mv "$NVIM_CONFIG_DIR" "$backup_dir"
+    fi
+
+    log_info "Cloning Neovim config to $NVIM_CONFIG_DIR"
+    git clone "$NVIM_REPO" "$NVIM_CONFIG_DIR"
+
+    cd "$NVIM_CONFIG_DIR"
+
+    if [[ -d ".githooks" ]]; then
+        git config core.hooksPath .githooks
+        chmod +x .githooks/* 2>/dev/null || true
+        log_info "Git hooks configured"
+    fi
+}
+
+main() {
+    init_script
+
+    check_requirements
+    setup_neovim_config
+
+    log_info "Neovim configuration: $NVIM_CONFIG_DIR"
+
+    finish_script 0
+}
+
+main "$@"

+ 51 - 18
runs/paru

@@ -1,26 +1,59 @@
 #!/usr/bin/env bash
-# NAME: Installs paru
+# NAME: Install paru AUR helper
+
 set -euo pipefail
 
-if command -v paru &>/dev/null; then
-	echo "paru ya está instalado."
-	exit 0
-fi
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+	echo "[ERROR] Could not source common.sh" >&2
+	exit 1
+}
+
+check_requirements() {
+	local required_commands=(git makepkg)
+
+	for cmd in "${required_commands[@]}"; do
+		if ! command_exists "$cmd"; then
+			log_error "Required command not found: $cmd"
+			exit 1
+		fi
+	done
+}
+
+install_paru() {
+	if command_exists paru; then
+		log_info "paru is already installed"
+		return 0
+	fi
+
+	log_info "Installing build dependencies"
+	sudo pacman -S --needed --noconfirm base-devel git
+
+	local tmpdir
+	tmpdir=$(mktemp -d)
+	log_info "Using temporary directory: $tmpdir"
+
+	cd "$tmpdir"
+
+	log_info "Cloning paru repository"
+	git clone https://aur.archlinux.org/paru.git
+	cd paru
+
+	log_info "Building and installing paru"
+	makepkg -si --noconfirm
 
-echo "Instalando dependencias necesarias..."
-sudo pacman -S --needed --noconfirm base-devel git
+	log_info "Cleaning up temporary files"
+	cd /
+	rm -rf "$tmpdir"
+}
 
-tmpdir=$(mktemp -d)
-echo "Temp dir: $tmpdir"
-cd "$tmpdir"
+main() {
+	init_script
 
-echo "Cloning paru..."
-git clone https://aur.archlinux.org/paru.git
-cd paru
+	check_requirements
+	install_paru
 
-echo "Compiling paru..."
-makepkg -si --noconfirm
+	finish_script 0
+}
 
-echo "Cleaning up..."
-cd ..
-rm -rf "$tmpdir"
+main "$@"

+ 96 - 100
runs/services

@@ -3,103 +3,99 @@
 
 set -euo pipefail
 
-echo "Enabling and configuring system services..."
-
-# ═══════════════════════════════════════════════════════════
-# 🌐 NETWORKING SERVICES
-# ═══════════════════════════════════════════════════════════
-echo "Enabling networking services..."
-
-# NetworkManager (main network management)
-sudo systemctl enable --now NetworkManager.service
-
-# Bluetooth
-sudo systemctl enable --now bluetooth.service
-
-# SSH daemon
-sudo systemctl enable --now sshd.service
-
-# ═══════════════════════════════════════════════════════════
-# 🔊 AUDIO SERVICES
-# ═══════════════════════════════════════════════════════════
-echo "Enabling audio services..."
-
-# PipeWire audio (replaces PulseAudio)
-systemctl --user enable --now pipewire.service
-systemctl --user enable --now pipewire-pulse.service
-systemctl --user enable --now wireplumber.service
-
-# ═══════════════════════════════════════════════════════════
-# 🖨️ PRINTING SERVICES
-# ═══════════════════════════════════════════════════════════
-echo "Enabling printing services..."
-
-# CUPS printing system
-sudo systemctl enable --now cups.service
-
-# ═══════════════════════════════════════════════════════════
-# ⏰ TIME & SCHEDULING SERVICES
-# ═══════════════════════════════════════════════════════════
-echo "Configuring time and scheduling services..."
-
-echo "en_US.UTF-8 UTF-8" | sudo tee -a /etc/locale.gen
-sudo locale-gen
-
-# Set system locale
-echo "LANG=en_US.UTF-8" | sudo tee /etc/locale.conf
-echo "→ System locale set to en_US.UTF-8"
-
-sudo timedatectl set-timezone America/Montevideo
-
-# Enable NTP time synchronization
-sudo timedatectl set-ntp true
-sudo systemctl enable --now systemd-timesyncd.service
-
-# Cron daemon for scheduled tasks
-sudo systemctl enable --now cronie.service
-
-# ═══════════════════════════════════════════════════════════
-# 🔒 SECURITY & FIREWALL SERVICES
-# ═══════════════════════════════════════════════════════════
-echo "Enabling security services..."
-
-# UFW firewall
-sudo systemctl enable --now ufw.service
-
-# Enable basic firewall rules
-sudo ufw --force enable
-sudo ufw default deny incoming
-sudo ufw default allow outgoing
-
-# ═══════════════════════════════════════════════════════════
-# 💾 BACKUP & SYNC SERVICES
-# ═══════════════════════════════════════════════════════════
-echo "Enabling backup services..."
-
-# Snapper for BTRFS snapshots (if using BTRFS)
-if findmnt / -t btrfs &>/dev/null; then
-    sudo systemctl enable --now snapper-timeline.timer
-    sudo systemctl enable --now snapper-cleanup.timer
-    echo "→ Snapper enabled (BTRFS detected)"
-else
-    echo "→ Skipping Snapper (no BTRFS detected)"
-fi
-
-# ═══════════════════════════════════════════════════════════
-# ⚡ POWER MANAGEMENT
-# ═══════════════════════════════════════════════════════════
-echo "Enabling power management..."
-
-# Power profiles daemon
-sudo systemctl enable --now power-profiles-daemon.service
-
-# ═══════════════════════════════════════════════════════════
-# 🔧 SYSTEM OPTIMIZATION
-# ═══════════════════════════════════════════════════════════
-echo "Enabling system optimization..."
-
-# Haveged entropy daemon
-sudo systemctl enable --now haveged.service
-
-# Locate database updates
-sudo systemctl enable --now plocate-updatedb.timer
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+    echo "[ERROR] Could not source common.sh" >&2
+    exit 1
+}
+
+enable_networking_services() {
+    log_info "Enabling networking services"
+
+    sudo systemctl enable --now NetworkManager.service
+    sudo systemctl enable --now bluetooth.service
+    sudo systemctl enable --now sshd.service
+}
+
+enable_audio_services() {
+    log_info "Enabling audio services"
+
+    systemctl --user enable --now pipewire.service
+    systemctl --user enable --now pipewire-pulse.service
+    systemctl --user enable --now wireplumber.service
+}
+
+enable_printing_services() {
+    log_info "Enabling printing services"
+
+    sudo systemctl enable --now cups.service
+}
+
+configure_time_locale() {
+    log_info "Configuring time and locale services"
+
+    if ! grep -q "en_US.UTF-8 UTF-8" /etc/locale.gen; then
+        echo "en_US.UTF-8 UTF-8" | sudo tee -a /etc/locale.gen
+        sudo locale-gen
+    fi
+
+    echo "LANG=en_US.UTF-8" | sudo tee /etc/locale.conf
+    log_info "System locale set to en_US.UTF-8"
+
+    sudo timedatectl set-timezone America/Montevideo
+    sudo timedatectl set-ntp true
+    sudo systemctl enable --now systemd-timesyncd.service
+    sudo systemctl enable --now cronie.service
+}
+
+enable_security_services() {
+    log_info "Enabling security services"
+
+    sudo systemctl enable --now ufw.service
+    sudo ufw --force enable
+    sudo ufw default deny incoming
+    sudo ufw default allow outgoing
+}
+
+enable_backup_services() {
+    log_info "Enabling backup services"
+
+    if findmnt / -t btrfs &>/dev/null; then
+        sudo systemctl enable --now snapper-timeline.timer
+        sudo systemctl enable --now snapper-cleanup.timer
+        log_info "Snapper enabled (BTRFS detected)"
+    else
+        log_info "Skipping Snapper (no BTRFS detected)"
+    fi
+}
+
+enable_power_management() {
+    log_info "Enabling power management"
+
+    sudo systemctl enable --now power-profiles-daemon.service
+}
+
+enable_system_optimization() {
+    log_info "Enabling system optimization"
+
+    sudo systemctl enable --now haveged.service
+    sudo systemctl enable --now plocate-updatedb.timer
+    sudo systemctl enable --now syncthing@forbi.service
+}
+
+main() {
+    init_script
+
+    enable_networking_services
+    enable_audio_services
+    enable_printing_services
+    configure_time_locale
+    enable_security_services
+    enable_backup_services
+    enable_power_management
+    enable_system_optimization
+
+    finish_script 0
+}
+
+main "$@"

+ 42 - 0
runs/sudo

@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+# NAME: Configure passwordless sudo for wheel group
+# REQUIRES: sudo
+
+set -euo pipefail
+
+# Source common functions
+source "$(dirname "$0")/../common.sh" || {
+	echo "[ERROR] Could not source common.sh" >&2
+	exit 1
+}
+
+check_requirements() {
+	if ! is_root; then
+		log_error "This script requires root privileges"
+		log_info "Run with: sudo $0"
+		exit 1
+	fi
+}
+
+configure_sudo() {
+	local sudoers_file="/etc/sudoers.d/wheel"
+	local config_line="%wheel ALL=(ALL:ALL) NOPASSWD: ALL"
+
+	log_sudo "Configuring wheel group for passwordless sudo"
+
+	echo "$config_line" >"$sudoers_file"
+	chmod 440 "$sudoers_file"
+
+	log_info "Wheel group configured for passwordless sudo"
+	log_info "Users in wheel group can now use sudo without password"
+}
+
+main() {
+	init_script
+
+	check_requirements
+	configure_sudo
+
+	finish_script 0
+}
+main "$@"