mirror of
https://github.com/marcogll/omarchy_setup.git
synced 2026-01-13 13:25:16 +00:00
* feat: add dev tools, AI aliases, and improve zshrc documentation
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.
* feat: improve keyring UX and icon manager flow
This commit introduces two main improvements to the user experience:
1. **Refactors the Icon Manager for Non-Interactive Installation:**
- The `icon_manager.sh` module can now be run in a non-interactive mode.
- The "Install All" process has been updated to use this non-interactive mode, which installs the default icon theme without pausing the script or requiring user input.
2. **Improves the GNOME Keyring Workflow:**
- The script no longer errors out if the GNOME Keyring agent is not immediately available after installation.
- Instead, a clear summary message is now displayed at the end of the "Install All" process, instructing the user to log out and back in, and then run the SSH key synchronization module separately. This provides a much smoother and more intuitive user experience.
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Marco Gallegos <marco.gallegos@outlook.com>
453 lines
16 KiB
Bash
Executable File
453 lines
16 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"
|
|
["7D"]="icon_manager;set_default_icon_theme;🎨 Instalar Tema de Iconos por Defecto;bg"
|
|
["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
|
|
# Excluir módulos no deseados y el módulo interactivo de iconos
|
|
if [[ " ${EXCLUDED_FROM_ALL[*]} " =~ " ${key} " || "$key" == "7" ]]; then
|
|
continue
|
|
fi
|
|
# Si el módulo 7D existe, añadirlo en lugar del 7
|
|
if [[ "$key" == "7D" ]]; then
|
|
choices+=("7D")
|
|
elif [[ ! "$key" =~ D$ ]]; then # Evitar añadir otros módulos 'D'
|
|
choices+=("$key")
|
|
fi
|
|
done
|
|
# Asegurarse de que el orden sea consistente
|
|
printf '%s\n' "${choices[@]}" | sort -V | xargs
|
|
}
|
|
|
|
# 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, excluyendo los módulos "D" (Default)
|
|
for key in $(printf '%s\n' "${!MODULES[@]}" | sort -V); do
|
|
if [[ "$key" =~ D$ ]]; then
|
|
continue
|
|
fi
|
|
IFS=';' read -r _ _ description _ <<< "${MODULES[$key]}"
|
|
echo -e " ${GREEN}${key})${NC} ${description}"
|
|
done
|
|
|
|
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
|
|
|
|
echo ""
|
|
log_step "Pasos Finales Recomendados"
|
|
log_info "Para completar la configuración, por favor, sigue estos pasos:"
|
|
echo "1. ${BOLD}Cierra sesión y vuelve a iniciarla.${NC} Esto es crucial para que se activen servicios como Docker y GNOME Keyring."
|
|
echo "2. ${BOLD}Abre una nueva terminal y ejecuta este script de nuevo.${NC}"
|
|
echo "3. ${BOLD}Selecciona la opción 'K'${NC} para sincronizar tus claves SSH con el agente de GNOME Keyring."
|
|
echo ""
|
|
}
|
|
|
|
# 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}"
|