mirror of
https://github.com/marcogll/omarchy_setup.git
synced 2026-01-13 13:25:16 +00:00
This commit introduces several new features and improvements: - **Adds Development Tools to `apps.sh`:** The `apps.sh` module now installs essential development tools, including `python`, `pip`, `nodejs`, `npm`, `uv`, and `nvm`. - **Implements `.zshrc.local` for Private Variables:** - A `.zshrc.local.example` file has been added to serve as a template for users to securely store their private environment variables, such as API keys. - The main `.zshrc` file now sources `.zshrc.local` if it exists. - **Adds AI Aliases to `.zshrc`:** A new section has been added to `.zshrc` with example aliases for interacting with command-line AI tools. - **Improves `.zshrc` Documentation:** The `.zshrc` file has been thoroughly documented with comments in Spanish, explaining the purpose of each section. The title has also been updated and professionalized. - **Fixes a Regression:** This commit restores the `ytm`, `ytv`, `ytls`, and SSH agent functions in `.zshrc` that were accidentally removed in a previous step.
435 lines
15 KiB
Bash
Executable File
435 lines
15 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# ===============================================================
|
|
# 🌀 Omarchy Setup Script — Configuración modular para Arch Linux
|
|
# ===============================================================
|
|
|
|
set -u
|
|
|
|
# Directorio del script
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
MODULES_DIR="${SCRIPT_DIR}/modules"
|
|
REPO_BASE="https://raw.githubusercontent.com/marcogll/omarchy_setup/main"
|
|
SUDO_PASSWORD=""
|
|
|
|
# Verificar que los módulos existen
|
|
if [[ ! -d "${MODULES_DIR}" ]] || [[ ! -f "${MODULES_DIR}/common.sh" ]]; then
|
|
echo -e "\033[0;31m✗ Error: Módulos no encontrados\033[0m"
|
|
echo ""
|
|
echo "Este script requiere que los módulos estén presentes localmente."
|
|
echo "Por favor, clona el repositorio completo:"
|
|
echo ""
|
|
echo " git clone https://github.com/marcogll/omarchy_setup.git"
|
|
echo " cd omarchy_setup"
|
|
echo " ./omarchy-setup.sh"
|
|
echo ""
|
|
exit 1
|
|
fi
|
|
|
|
# Cargar funciones comunes
|
|
source "${MODULES_DIR}/common.sh"
|
|
|
|
# Asegurar que los módulos son ejecutables (para ejecución individual)
|
|
log_info "Verificando permisos de los módulos..."
|
|
chmod +x "${MODULES_DIR}"/*.sh 2>/dev/null || true
|
|
|
|
# --- Funciones de UI Mejorada (Indicador de Progreso) ---
|
|
|
|
SPINNER_ACTIVE=0
|
|
SPINNER_MESSAGE=
|
|
|
|
pause_spinner() {
|
|
if (( SPINNER_ACTIVE )); then
|
|
SPINNER_ACTIVE=0
|
|
fi
|
|
}
|
|
|
|
resume_spinner() {
|
|
if [[ -n "$SPINNER_MESSAGE" ]]; then
|
|
log_info "$SPINNER_MESSAGE"
|
|
SPINNER_ACTIVE=1
|
|
fi
|
|
}
|
|
|
|
start_spinner() {
|
|
local message="$1"
|
|
pause_spinner
|
|
SPINNER_MESSAGE="$message"
|
|
SPINNER_ACTIVE=1
|
|
log_info "$message"
|
|
}
|
|
|
|
stop_spinner() {
|
|
local exit_code=$1
|
|
local success_msg=$2
|
|
local error_msg=${3:-"Ocurrió un error"}
|
|
|
|
pause_spinner
|
|
|
|
if [[ $exit_code -eq 0 ]]; then
|
|
log_success "$success_msg"
|
|
else
|
|
log_error "$error_msg"
|
|
fi
|
|
|
|
SPINNER_MESSAGE=
|
|
}
|
|
|
|
ensure_sudo_session() {
|
|
if sudo -n true 2>/dev/null; then
|
|
return 0
|
|
fi
|
|
|
|
if [[ -n "${SUDO_PASSWORD:-}" ]]; then
|
|
if printf '%s\n' "$SUDO_PASSWORD" | sudo -S -v >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
SUDO_PASSWORD=""
|
|
log_warning "La contraseña de sudo almacenada no es válida. Se solicitará nuevamente."
|
|
fi
|
|
|
|
pause_spinner
|
|
|
|
local attempts=0
|
|
while (( attempts < 3 )); do
|
|
if (( attempts == 0 )); then
|
|
log_info "Se requiere autenticación de sudo para continuar."
|
|
else
|
|
log_info "Intenta ingresar la contraseña nuevamente."
|
|
fi
|
|
local password_input=""
|
|
read -s -p "Contraseña de sudo: " password_input
|
|
echo ""
|
|
|
|
if [[ -z "$password_input" ]]; then
|
|
log_warning "La contraseña no puede estar vacía."
|
|
elif printf '%s\n' "$password_input" | sudo -S -v >/dev/null 2>&1; then
|
|
SUDO_PASSWORD="$password_input"
|
|
log_success "Sesión de sudo autenticada."
|
|
return 0
|
|
else
|
|
log_error "Contraseña de sudo incorrecta."
|
|
fi
|
|
|
|
((attempts++))
|
|
done
|
|
|
|
log_error "No se pudo autenticar con sudo después de varios intentos."
|
|
return 1
|
|
}
|
|
|
|
# --- Definición de Módulos ---
|
|
# Clave: Opción del menú
|
|
# Valor: "Nombre del Fichero;Función Principal;Descripción;Tipo (bg/fg)"
|
|
# Tipo 'bg': Tareas de fondo, usan spinner.
|
|
# Tipo 'fg': Tareas interactivas (foreground), no usan spinner.
|
|
declare -A MODULES
|
|
MODULES=(
|
|
["1"]="apps;run_module_main;📦 Instalar Aplicaciones (VS Code, VLC, drivers, etc.);bg"
|
|
["2"]="zsh-config;install_zsh;🐚 Configurar Zsh (shell, plugins, config);bg"
|
|
["3"]="docker;install_docker;🐳 Instalar Docker y Portainer;fg"
|
|
["4"]="zerotier;install_zerotier;🌐 Instalar ZeroTier VPN;fg"
|
|
["5"]="printer;install_printer;🖨️ Configurar Impresoras (CUPS);bg"
|
|
["6"]="mouse_cursor;install_mouse_cursor;🖱️ Instalar Tema de Cursor (Bibata);bg"
|
|
["7"]="icon_manager;run_module_main;🎨 Gestionar Temas de Iconos (Papirus, Tela, etc.);fg"
|
|
["K"]="ssh-keyring;sync_ssh_keyring;🔐 Sincronizar claves SSH con GNOME Keyring;fg"
|
|
["F"]="disk-format;run_module_main;💾 Habilitar Formatos FAT/exFAT/NTFS/ext4;bg"
|
|
["R"]="davinci-resolve;install_davinci_resolve;🎬 Instalar DaVinci Resolve (Intel Edition);fg"
|
|
["H"]="hyprland-config;run_module_main;🎨 Instalar Configuración de Hyprland;bg"
|
|
)
|
|
|
|
# Módulos a excluir de la opción "Instalar Todo"
|
|
EXCLUDED_FROM_ALL=("R")
|
|
|
|
# Generar dinámicamente la lista de módulos para "Instalar Todo"
|
|
get_install_all_choices() {
|
|
local choices=()
|
|
for key in $(printf '%s\n' "${!MODULES[@]}" | sort -V); do
|
|
# Verificar si la clave no está en el array de exclusión
|
|
if ! [[ " ${EXCLUDED_FROM_ALL[*]} " =~ " ${key} " ]]; then
|
|
choices+=("$key")
|
|
fi
|
|
done
|
|
echo "${choices[@]}"
|
|
}
|
|
|
|
# Módulos a incluir en la opción "Instalar Todo"
|
|
INSTALL_ALL_CHOICES=($(get_install_all_choices))
|
|
|
|
# Función para mostrar el menú
|
|
show_menu() {
|
|
clear
|
|
echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
|
|
echo -e "${CYAN}║${NC} ${BOLD}🌀 Omarchy Setup Script — Configuración Modular${NC} ${CYAN}║${NC}"
|
|
echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
|
|
echo ""
|
|
echo -e "${BOLD}Selecciona las opciones que deseas instalar:${NC}"
|
|
echo ""
|
|
# Generar menú dinámicamente
|
|
for key in "${!MODULES[@]}"; do
|
|
IFS=';' read -r _ _ description _ <<< "${MODULES[$key]}"
|
|
# Asegurarse de que las claves numéricas se ordenen correctamente
|
|
echo -e " ${GREEN}${key})${NC} ${description}"
|
|
done | sort -V
|
|
|
|
local install_all_keys=$(IFS=,; echo "${INSTALL_ALL_CHOICES[*]}")
|
|
echo -e " ${GREEN}A)${NC} ✅ Instalar Todo (${install_all_keys//,/, }) (excluye DaVinci)"
|
|
echo -e " ${GREEN}0)${NC} 🚪 Salir"
|
|
echo ""
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -ne "${BOLD}Selecciona opción: ${NC}"
|
|
}
|
|
|
|
# Función para ejecutar módulo
|
|
run_module() {
|
|
local choice=$1
|
|
IFS=';' read -r module_file func_name description type <<< "${MODULES[$choice]}"
|
|
|
|
# Para funciones internas como update_system
|
|
if [[ ! -f "${MODULES_DIR}/${module_file}.sh" && "$(type -t "$func_name")" == "function" ]]; then
|
|
"$func_name"
|
|
return $?
|
|
fi
|
|
|
|
local full_path="${MODULES_DIR}/${module_file}.sh"
|
|
if [[ ! -f "$full_path" ]]; then
|
|
log_error "Módulo para la opción '${choice}' (${module_file}.sh) no encontrado."
|
|
return 1
|
|
fi
|
|
|
|
# Exportar REPO_BASE para que los módulos lo puedan usar
|
|
export REPO_BASE
|
|
|
|
# Cargar y ejecutar el módulo
|
|
source "$full_path"
|
|
|
|
if [[ "$(type -t "$func_name")" != "function" ]]; then
|
|
log_error "La función principal '${func_name}' no está definida en '${module_file}.sh'."
|
|
return 1
|
|
fi
|
|
|
|
"$func_name"
|
|
return $?
|
|
}
|
|
|
|
# Función para ejecutar un módulo con lógica de reintento
|
|
# Intenta ejecutar un módulo. Si falla, lo reintenta una vez más.
|
|
# Devuelve 0 si tiene éxito en cualquier intento, 1 si falla en ambos.
|
|
run_module_with_retry() {
|
|
local choice=$1
|
|
local max_intentos=2
|
|
local intento_actual=1
|
|
local module_entry="${MODULES[$choice]}"
|
|
local module_type=""
|
|
if [[ -n "$module_entry" ]]; then
|
|
IFS=';' read -r _ _ _ module_type <<< "$module_entry"
|
|
fi
|
|
local tmp_output=""
|
|
if [[ "$module_type" == "bg" ]]; then
|
|
tmp_output="$(mktemp)"
|
|
fi
|
|
|
|
while [ $intento_actual -le $max_intentos ]; do
|
|
local estado_salida=0
|
|
if [[ -n "$tmp_output" ]]; then
|
|
run_module "$choice" >"$tmp_output" 2>&1
|
|
estado_salida=$?
|
|
if [[ -s "$tmp_output" ]]; then
|
|
if declare -F pause_spinner >/dev/null; then
|
|
pause_spinner
|
|
fi
|
|
cat "$tmp_output"
|
|
fi
|
|
: > "$tmp_output"
|
|
if [[ $estado_salida -ne 0 && $intento_actual -lt $max_intentos ]] && declare -F resume_spinner >/dev/null; then
|
|
resume_spinner
|
|
fi
|
|
else
|
|
run_module "$choice"
|
|
estado_salida=$?
|
|
fi
|
|
|
|
if [ $estado_salida -eq 0 ]; then
|
|
if [[ -n "$tmp_output" ]]; then
|
|
rm -f "$tmp_output"
|
|
fi
|
|
return 0 # Éxito, salimos de la función
|
|
fi
|
|
|
|
log_warning "El módulo falló en el intento $intento_actual (código: $estado_salida)."
|
|
if [ $intento_actual -lt $max_intentos ]; then
|
|
log_info "Reintentando en 3 segundos..."
|
|
sleep 3
|
|
fi
|
|
((intento_actual++))
|
|
done
|
|
|
|
log_error "El módulo falló después de $max_intentos intentos."
|
|
if [[ -n "$tmp_output" ]]; then
|
|
rm -f "$tmp_output"
|
|
fi
|
|
return 1 # Falla definitiva
|
|
}
|
|
|
|
# Función para instalar todo
|
|
install_all() {
|
|
log_step "Instalación Completa de Omarchy"
|
|
|
|
local failed=()
|
|
|
|
for choice in "${INSTALL_ALL_CHOICES[@]}"; do
|
|
IFS=';' read -r module_file _ description type <<< "${MODULES[$choice]}"
|
|
|
|
if ! ensure_sudo_session; then
|
|
failed+=("${module_file}")
|
|
continue
|
|
fi
|
|
|
|
# Separador visual para cada módulo
|
|
echo -e "\n${MAGENTA}────────────────────────────────────────────────────────────${NC}"
|
|
log_step "Iniciando Módulo: ${description}"
|
|
|
|
# Ejecutar con spinner para tareas de fondo (bg)
|
|
if [[ "$type" == "bg" ]]; then
|
|
start_spinner "Ejecutando: ${description#* }..."
|
|
if run_module_with_retry "${choice}"; then
|
|
stop_spinner 0 "Módulo '${description}' finalizado."
|
|
else
|
|
stop_spinner 1 "Error en el módulo '${description}'."
|
|
failed+=("${module_file}")
|
|
fi
|
|
else # Ejecutar sin spinner para tareas interactivas (fg) y sin reintento
|
|
if ! run_module "${choice}"; then
|
|
log_error "Error en el módulo '${description}'."
|
|
failed+=("${module_file}")
|
|
fi
|
|
fi
|
|
echo ""
|
|
done
|
|
|
|
if [[ ${#failed[@]} -eq 0 ]]; then
|
|
log_success "Todas las instalaciones se completaron correctamente"
|
|
else
|
|
log_warning "Algunos módulos fallaron: ${failed[*]}"
|
|
fi
|
|
}
|
|
|
|
# Función principal
|
|
main() {
|
|
# Limpieza al salir: detener el spinner y restaurar el cursor
|
|
trap 'stop_spinner 1 "Script interrumpido." >/dev/null 2>&1; unset SUDO_PASSWORD; exit 1' INT TERM
|
|
# Limpieza final al salir normalmente
|
|
trap 'tput cnorm; unset SUDO_PASSWORD' EXIT
|
|
|
|
# Verificar que estamos en Arch Linux
|
|
if [[ ! -f /etc/arch-release ]]; then
|
|
log_error "Este script está diseñado para Arch Linux"
|
|
exit 1
|
|
fi
|
|
|
|
# Verificar permisos de sudo
|
|
if ! ensure_sudo_session; then
|
|
log_error "No se pudieron obtener privilegios de sudo. Saliendo."
|
|
exit 1
|
|
fi
|
|
|
|
# Mantener sudo activo en background
|
|
local parent_pid=$$
|
|
(while true; do
|
|
sudo -n true >/dev/null 2>&1
|
|
sleep 60
|
|
kill -0 "$parent_pid" || exit
|
|
done 2>/dev/null) &
|
|
|
|
# Bucle principal del menú
|
|
# Exportar funciones para que los submódulos las puedan usar
|
|
export -f start_spinner
|
|
export -f stop_spinner
|
|
export -f pause_spinner
|
|
export -f resume_spinner
|
|
export -f ensure_sudo_session
|
|
|
|
while true; do
|
|
show_menu
|
|
read -r choice
|
|
choice=$(echo "${choice// /}" | tr '[:lower:]' '[:upper:]') # Eliminar espacios y convertir a mayúsculas
|
|
|
|
if [[ -v "MODULES[$choice]" ]]; then
|
|
IFS=';' read -r _ _ description type <<< "${MODULES[$choice]}"
|
|
|
|
# Manejo especial para DaVinci Resolve
|
|
if [[ "$choice" == "R" ]]; then
|
|
log_warning "DaVinci Resolve requiere el ZIP de instalación en ~/Downloads/"
|
|
echo -ne "${BOLD}¿Continuar con la instalación? [s/N]: ${NC} "
|
|
read -r confirm
|
|
if ! [[ "${confirm}" =~ ^[SsYy]$ ]]; then
|
|
log_info "Instalación cancelada"
|
|
read -p "Presiona Enter para continuar..."
|
|
continue
|
|
fi
|
|
fi
|
|
|
|
if ! ensure_sudo_session; then
|
|
read -p "Presiona Enter para continuar..."
|
|
continue
|
|
fi
|
|
|
|
if [[ "$type" == "bg" ]]; then
|
|
spinner_msg="${description#* }..." # "Instalar Apps..."
|
|
start_spinner "${spinner_msg}"
|
|
if run_module_with_retry "$choice"; then
|
|
stop_spinner 0 "Módulo '${description}' finalizado."
|
|
else
|
|
stop_spinner 1 "Error en el módulo '${description}'."
|
|
fi
|
|
else # 'fg'
|
|
log_info "Ejecutando módulo interactivo: ${description}"
|
|
run_module "$choice"
|
|
fi
|
|
|
|
echo ""
|
|
if declare -F pause_spinner >/dev/null; then
|
|
pause_spinner
|
|
fi
|
|
read -p "Presiona Enter para continuar..."
|
|
|
|
elif [[ "$choice" == "A" ]]; then
|
|
local modules_to_install=$(IFS=,; echo "${INSTALL_ALL_CHOICES[*]}")
|
|
log_warning "La opción 'Instalar Todo' ejecutará los módulos: ${modules_to_install//,/, }."
|
|
log_info "Los módulos excluidos (como DaVinci Resolve) deben instalarse por separado."
|
|
echo -ne "${BOLD}¿Confirmas que deseas instalar todas las opciones ahora? [s/N]: ${NC}"
|
|
read -r confirm
|
|
if [[ "${confirm}" =~ ^[SsYy]$ ]]; then
|
|
install_all
|
|
else
|
|
log_info "Instalación cancelada"
|
|
fi
|
|
echo ""
|
|
if declare -F pause_spinner >/dev/null; then
|
|
pause_spinner
|
|
fi
|
|
read -p "Presiona Enter para continuar..."
|
|
elif [[ "$choice" == "0" ]]; then
|
|
log_info "Saliendo..."
|
|
exit 0
|
|
else
|
|
log_error "Opción inválida. Presiona Enter para continuar..."
|
|
if declare -F pause_spinner >/dev/null; then
|
|
pause_spinner
|
|
fi
|
|
read -r
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Ejecutar función principal
|
|
|
|
# --- Redirección de logs ---
|
|
# Crear el directorio de logs si no existe
|
|
mkdir -p "${SCRIPT_DIR}/logs"
|
|
# Crear un nombre de archivo de log con la fecha y hora
|
|
LOG_FILE="${SCRIPT_DIR}/logs/omarchy-setup-$(date +%F_%H-%M-%S).log"
|
|
|
|
# Ejecutar la función principal y redirigir toda la salida (stdout y stderr)
|
|
# al archivo de log, mientras también se muestra en la terminal.
|
|
main "$@" 2>&1 | tee -a "${LOG_FILE}"
|