#!/usr/bin/env bash # shellcheck shell=bash # shellcheck disable=SC2034 # This file is generated by `bash scripts/build-bootstrap.sh`. # Do not edit it directly. set -euo pipefail # --- begin: scripts/lib/cli.sh --- # shellcheck shell=bash # shellcheck disable=SC2034 CLI_HELP_REQUESTED_STATUS=2 print_usage() { cat <<'EOF' Uso: bash install.sh [opções] curl -fsSL https://obslove.dev | bash -s -- [opções] Opções: -c, --check Valida o runtime sem instalar apps; via curl, o bootstrap ainda pode sincronizar o clone e preparar dependências iniciais. -e, --exclusive-key Destrutiva: remove as outras chaves SSH do GitHub e mantém só a atual. -n, --no-gh Pula a etapa de GitHub SSH. -s, --ssh-name NOME Define o nome da chave SSH enviada ao GitHub. -v, --verbose Desativa o modo resumido e mostra a saída completa. -h, --help Exibe esta ajuda. EOF } parse_cli_args() { while (($# > 0)); do case "$1" in -c|--check) CHECK_ONLY=1 shift ;; -e|--exclusive-key) EXCLUSIVE_GITHUB_SSH_KEY=1 shift ;; -n|--no-gh) SKIP_GITHUB_SSH=1 shift ;; -s|--ssh-name) [[ $# -ge 2 ]] || { printf 'Erro: faltou informar o valor de %s.\n' "$1" >&2 return 1 } GITHUB_SSH_KEY_NAME="$2" shift 2 ;; --ssh-name=*) GITHUB_SSH_KEY_NAME="${1#*=}" shift ;; -v|--verbose) STEP_OUTPUT_ONLY=0 shift ;; -h|--help) print_usage return "$CLI_HELP_REQUESTED_STATUS" ;; --) shift break ;; -*) printf 'Erro: opção desconhecida: %s\n' "$1" >&2 printf 'Use --help para ver as opções disponíveis.\n' >&2 return 1 ;; *) printf 'Erro: argumento não reconhecido: %s\n' "$1" >&2 return 1 ;; esac done if (($# > 0)); then printf 'Erro: argumentos extras não reconhecidos: %s\n' "$*" >&2 return 1 fi return 0 } # --- end: scripts/lib/cli.sh --- # --- begin: scripts/lib/runtime-config.sh --- # shellcheck shell=bash # shellcheck disable=SC2034 config_init_common_defaults() { REPO_HTTPS_URL="https://github.com/obslove/arch-postinstall-apps.git" REPO_SSH_URL="git@github.com:obslove/arch-postinstall-apps.git" PROJECTS_DIR="$HOME/Projects" REPOSITORIES_DIR="$HOME/Repositories" BACKUPS_DIR="$HOME/Backups" INSTALL_DIR="$PROJECTS_DIR/arch-postinstall-apps" YAY_REPO_DIR="$REPOSITORIES_DIR/yay" EASY_EFFECTS_PRESET_DIR="$HOME/EasyEffects-Preset" EASY_EFFECTS_PRESET_REPO_HTTPS_URL="${POSTINSTALL_EASY_EFFECTS_PRESET_REPO_HTTPS_URL:-${POSTINSTALL_EASY_EFFECTS_PRESET_REPO_URL:-${EASY_EFFECTS_PRESET_REPO_HTTPS_URL:-${EASY_EFFECTS_PRESET_REPO_URL:-https://github.com/obslove/EasyEffects-Preset.git}}}}" EASY_EFFECTS_PRESET_REPO_SSH_URL="${POSTINSTALL_EASY_EFFECTS_PRESET_REPO_SSH_URL:-${EASY_EFFECTS_PRESET_REPO_SSH_URL:-git@github.com:obslove/EasyEffects-Preset.git}}" TERMINAL_LYRICS_DIR="${POSTINSTALL_TERMINAL_LYRICS_DIR:-${TERMINAL_LYRICS_DIR:-$PROJECTS_DIR/terminal-lyrics}}" TERMINAL_LYRICS_REPO_HTTPS_URL="${POSTINSTALL_TERMINAL_LYRICS_REPO_HTTPS_URL:-${TERMINAL_LYRICS_REPO_HTTPS_URL:-https://github.com/obslove/terminal-lyrics.git}}" TERMINAL_LYRICS_REPO_SSH_URL="${POSTINSTALL_TERMINAL_LYRICS_REPO_SSH_URL:-${TERMINAL_LYRICS_REPO_SSH_URL:-git@github.com:obslove/terminal-lyrics.git}}" SYNTHETIC_PROFILE_GENERATOR_DIR="${POSTINSTALL_SYNTHETIC_PROFILE_GENERATOR_DIR:-${SYNTHETIC_PROFILE_GENERATOR_DIR:-$PROJECTS_DIR/synthetic-profile-generator}}" SYNTHETIC_PROFILE_GENERATOR_REPO_HTTPS_URL="${POSTINSTALL_SYNTHETIC_PROFILE_GENERATOR_REPO_HTTPS_URL:-${SYNTHETIC_PROFILE_GENERATOR_REPO_HTTPS_URL:-https://github.com/obslove/synthetic-profile-generator.git}}" SYNTHETIC_PROFILE_GENERATOR_REPO_SSH_URL="${POSTINSTALL_SYNTHETIC_PROFILE_GENERATOR_REPO_SSH_URL:-${SYNTHETIC_PROFILE_GENERATOR_REPO_SSH_URL:-git@github.com:obslove/synthetic-profile-generator.git}}" OBSLOVE_DOTS_DIR="${POSTINSTALL_OBSLOVE_DOTS_DIR:-${OBSLOVE_DOTS_DIR:-$HOME/Dots/obslove}}" OBSLOVE_DOTS_REPO_HTTPS_URL="${POSTINSTALL_OBSLOVE_DOTS_REPO_HTTPS_URL:-${OBSLOVE_DOTS_REPO_HTTPS_URL:-https://github.com/obslove/obslove.git}}" OBSLOVE_DOTS_REPO_SSH_URL="${POSTINSTALL_OBSLOVE_DOTS_REPO_SSH_URL:-${OBSLOVE_DOTS_REPO_SSH_URL:-git@github.com:obslove/obslove.git}}" DOTS_HYPRLAND_DIR="${POSTINSTALL_DOTS_HYPRLAND_DIR:-${DOTS_HYPRLAND_DIR:-$REPOSITORIES_DIR/dots-hyprland}}" DOTS_HYPRLAND_REPO_HTTPS_URL="${POSTINSTALL_DOTS_HYPRLAND_REPO_HTTPS_URL:-${DOTS_HYPRLAND_REPO_HTTPS_URL:-https://github.com/end-4/dots-hyprland.git}}" DOTS_HYPRLAND_REPO_SSH_URL="${POSTINSTALL_DOTS_HYPRLAND_REPO_SSH_URL:-${DOTS_HYPRLAND_REPO_SSH_URL:-git@github.com:end-4/dots-hyprland.git}}" II_VYNX_DIR="${POSTINSTALL_II_VYNX_DIR:-${II_VYNX_DIR:-$REPOSITORIES_DIR/ii-vynx}}" II_VYNX_REPO_HTTPS_URL="${POSTINSTALL_II_VYNX_REPO_HTTPS_URL:-${II_VYNX_REPO_HTTPS_URL:-https://github.com/vaguesyntax/ii-vynx.git}}" II_VYNX_REPO_SSH_URL="${POSTINSTALL_II_VYNX_REPO_SSH_URL:-${II_VYNX_REPO_SSH_URL:-git@github.com:vaguesyntax/ii-vynx.git}}" YAY_SNAPSHOT_URL="${POSTINSTALL_YAY_SNAPSHOT_URL:-${YAY_SNAPSHOT_URL:-https://aur.archlinux.org/cgit/aur.git/snapshot/yay.tar.gz}}" SSH_KEY_PATH="${POSTINSTALL_SSH_KEY_PATH:-${SSH_KEY_PATH:-$HOME/.ssh/id_ed25519}}" GITHUB_SSH_KEY_NAME="${POSTINSTALL_GITHUB_SSH_KEY_NAME:-${GITHUB_SSH_KEY_NAME:-}}" LOG_FILE="${POSTINSTALL_LOG_FILE:-$BACKUPS_DIR/arch-postinstall.log}" SUMMARY_FILE="${POSTINSTALL_SUMMARY_FILE:-$BACKUPS_DIR/arch-postinstall-summary.txt}" CHECK_ONLY="${POSTINSTALL_CHECK_ONLY:-${CHECK_ONLY:-0}}" EXCLUSIVE_GITHUB_SSH_KEY="${POSTINSTALL_EXCLUSIVE_GITHUB_SSH_KEY:-${EXCLUSIVE_GITHUB_SSH_KEY:-0}}" SKIP_GITHUB_SSH="${POSTINSTALL_SKIP_GITHUB_SSH:-${SKIP_GITHUB_SSH:-0}}" STEP_OUTPUT_ONLY="${POSTINSTALL_STEP_OUTPUT_ONLY:-${STEP_OUTPUT_ONLY:-1}}" STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/arch-postinstall-apps" LOCK_DIR="$STATE_DIR/lock" LOCK_HELD="${POSTINSTALL_LOCK_HELD:-0}" managed_repo_manifest_init } config_init_user_shell_paths() { BASHRC_FILE="$HOME/.bashrc" ZSHRC_FILE="$HOME/.zshrc" FISH_CONFIG_FILE="$HOME/.config/fish/config.fish" } config_init_repo_paths() { local repo_dir="$1" REPO_DIR="$repo_dir" SCRIPT_DIR="$REPO_DIR" PACKAGE_FILE="$REPO_DIR/config/packages.txt" EXTRA_PACKAGE_FILE="$REPO_DIR/config/packages-extra.txt" } config_init_runtime() { local repo_dir="$1" config_init_common_defaults config_init_user_shell_paths config_init_repo_paths "$repo_dir" SYSTEM_UPDATED="${POSTINSTALL_BOOTSTRAP_UPDATED:-${POSTINSTALL_SYSTEM_UPDATED:-0}}" } config_init_bootstrap() { config_init_common_defaults config_init_user_shell_paths config_init_repo_paths "$INSTALL_DIR" SYSTEM_UPDATED=0 } config_finalize() { readonly REPO_DIR readonly SCRIPT_DIR readonly PACKAGE_FILE readonly EXTRA_PACKAGE_FILE readonly BASHRC_FILE readonly ZSHRC_FILE readonly FISH_CONFIG_FILE readonly REPO_HTTPS_URL readonly REPO_SSH_URL readonly PROJECTS_DIR readonly REPOSITORIES_DIR readonly BACKUPS_DIR readonly INSTALL_DIR readonly YAY_REPO_DIR readonly EASY_EFFECTS_PRESET_DIR readonly EASY_EFFECTS_PRESET_REPO_HTTPS_URL readonly EASY_EFFECTS_PRESET_REPO_SSH_URL readonly TERMINAL_LYRICS_DIR readonly TERMINAL_LYRICS_REPO_HTTPS_URL readonly TERMINAL_LYRICS_REPO_SSH_URL readonly SYNTHETIC_PROFILE_GENERATOR_DIR readonly SYNTHETIC_PROFILE_GENERATOR_REPO_HTTPS_URL readonly SYNTHETIC_PROFILE_GENERATOR_REPO_SSH_URL readonly OBSLOVE_DOTS_DIR readonly OBSLOVE_DOTS_REPO_HTTPS_URL readonly OBSLOVE_DOTS_REPO_SSH_URL readonly DOTS_HYPRLAND_DIR readonly DOTS_HYPRLAND_REPO_HTTPS_URL readonly DOTS_HYPRLAND_REPO_SSH_URL readonly II_VYNX_DIR readonly II_VYNX_REPO_HTTPS_URL readonly II_VYNX_REPO_SSH_URL readonly YAY_SNAPSHOT_URL readonly SSH_KEY_PATH readonly GITHUB_SSH_KEY_NAME readonly LOG_FILE readonly SUMMARY_FILE readonly CHECK_ONLY readonly EXCLUSIVE_GITHUB_SSH_KEY readonly SKIP_GITHUB_SSH readonly STEP_OUTPUT_ONLY readonly STATE_DIR readonly LOCK_DIR readonly SYSTEM_UPDATED } # --- end: scripts/lib/runtime-config.sh --- # --- begin: scripts/lib/invocation-context.sh --- # shellcheck shell=bash load_bootstrap_invocation_context() { local parse_status=0 config_init_bootstrap parse_cli_args "$@" || parse_status=$? if (( parse_status != 0 )); then return "$parse_status" fi config_finalize } load_runtime_invocation_context() { local repo_dir="$1" local parse_status=0 shift config_init_runtime "$repo_dir" parse_cli_args "$@" || parse_status=$? if (( parse_status != 0 )); then return "$parse_status" fi config_finalize } append_runtime_invocation_env() { local array_name="$1" # shellcheck disable=SC2178 declare -n env_assignments="$array_name" env_assignments+=( "POSTINSTALL_BOOTSTRAP_UPDATED=$BOOTSTRAP_SYSTEM_UPDATED" "POSTINSTALL_LOG_FILE=$LOG_FILE" "POSTINSTALL_LOG_INITIALIZED=1" "POSTINSTALL_LOCK_HELD=1" "POSTINSTALL_SUMMARY_FILE=$SUMMARY_FILE" "POSTINSTALL_SSH_KEY_PATH=$SSH_KEY_PATH" "POSTINSTALL_YAY_SNAPSHOT_URL=$YAY_SNAPSHOT_URL" "POSTINSTALL_CHECK_ONLY=$CHECK_ONLY" "POSTINSTALL_EXCLUSIVE_GITHUB_SSH_KEY=$EXCLUSIVE_GITHUB_SSH_KEY" "POSTINSTALL_SKIP_GITHUB_SSH=$SKIP_GITHUB_SSH" "POSTINSTALL_GITHUB_SSH_KEY_NAME=$GITHUB_SSH_KEY_NAME" "POSTINSTALL_STEP_OUTPUT_ONLY=$STEP_OUTPUT_ONLY" ) } exec_runtime_with_invocation_context() { local runtime_entrypoint="$1" local runtime_env_assignments=() append_runtime_invocation_env runtime_env_assignments exec env "${runtime_env_assignments[@]}" bash "$runtime_entrypoint" } # --- end: scripts/lib/invocation-context.sh --- # --- begin: scripts/lib/step-result.sh --- # shellcheck shell=bash # shellcheck disable=SC2034 step_result_reset() { STEP_RESULT_STATUS="" STEP_RESULT_MESSAGE="" STEP_RESULT_SUMMARY_PRINTED=0 } step_result_success() { STEP_RESULT_STATUS="success" STEP_RESULT_MESSAGE="${1:-}" } step_result_skipped() { STEP_RESULT_STATUS="skipped" STEP_RESULT_MESSAGE="${1:-}" } step_result_soft_fail() { STEP_RESULT_STATUS="soft_fail" STEP_RESULT_MESSAGE="${1:-}" } step_result_hard_fail() { STEP_RESULT_STATUS="hard_fail" STEP_RESULT_MESSAGE="${1:-}" } # --- end: scripts/lib/step-result.sh --- # --- begin: scripts/lib/ui/styles.sh --- # shellcheck shell=bash # shellcheck disable=SC2034 init_output_styles() { style_reset="" style_step="" style_detail="" style_success="" style_warning="" style_error="" style_muted="" if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then style_reset=$'\033[0m' style_step=$'\033[1;36m' style_detail=$'\033[0;37m' style_success=$'\033[1;32m' style_warning=$'\033[1;33m' style_error=$'\033[1;31m' style_muted=$'\033[0;90m' fi } style_text() { local style="$1" local text="$2" if [[ -z "$style" ]]; then printf '%s' "$text" return 0 fi printf '%s%s%s' "$style" "$text" "$style_reset" } # --- end: scripts/lib/ui/styles.sh --- # --- begin: scripts/lib/ui/logging.sh --- # shellcheck shell=bash init_logging() { if [[ "${POSTINSTALL_LOG_INITIALIZED:-0}" == "1" ]]; then return fi mkdir -p "$(dirname "$LOG_FILE")" touch "$LOG_FILE" export POSTINSTALL_LOG_FILE="$LOG_FILE" export POSTINSTALL_LOG_INITIALIZED=1 exec > >(tee -a "$LOG_FILE") 2>&1 } write_log_only() { if [[ "${POSTINSTALL_LOG_INITIALIZED:-0}" != "1" ]]; then return 0 fi printf '%s\n' "$1" >>"$LOG_FILE" } # --- end: scripts/lib/ui/logging.sh --- # --- begin: scripts/lib/ui/notice.sh --- # shellcheck shell=bash emit_notice() { local symbol="$1" local style="$2" local message="$3" local line_prefix="" if [[ "$step_open" == "1" ]]; then line_prefix="│ " fi printf '%s%s %s\n' "$line_prefix" "$(style_text "$style" "$symbol")" "$message" } set_step_total() { step_total="$1" } announce_step() { local title="$1" local header="" close_step_block step_counter=$((step_counter + 1)) if (( step_total > 0 )); then header=$(printf 'Etapa %02d/%02d • %s' "$step_counter" "$step_total" "$title") else header=$(printf 'Etapa %02d • %s' "$step_counter" "$title") fi echo printf '%s %s\n' "$(style_text "$style_step" "╭─")" "$(style_text "$style_step" "$header")" step_open=1 } announce_detail() { if [[ "$STEP_OUTPUT_ONLY" == "1" ]]; then if [[ "$1" == *"Etapa ignorada."* || "$1" == Instalando\ via\ pacman:* || "$1" == Instalando\ via\ AUR:* ]]; then write_log_only "$1" return 0 fi printf '│ %s %s\n' "$(style_text "$style_detail" "•")" "$1" return 0 fi printf '│ %s %s\n' "$(style_text "$style_detail" "•")" "$1" } announce_warning() { emit_notice "!" "$style_warning" "$1" } announce_error() { emit_notice "x" "$style_error" "$1" } announce_prompt() { emit_notice "?" "$style_step" "$1" } close_step_block() { if [[ "$step_open" != "1" ]]; then return 0 fi style_text "$style_muted" "╰─" printf '\n' step_open=0 } # --- end: scripts/lib/ui/notice.sh --- # --- begin: scripts/lib/ui/summary-primitives.sh --- # shellcheck shell=bash print_summary_section() { local title="$1" echo "│" printf '│ %s\n' "$(style_text "$style_step" "$title")" } print_summary_item() { local label="$1" local value="$2" local label_length=0 local padding_width=22 label_length="$(printf '%s' "$label" | wc -m | tr -d '[:space:]')" if [[ -z "$label_length" ]]; then label_length=0 fi if (( label_length < padding_width )); then printf '│ %s %s%*s %s\n' "$(style_text "$style_detail" "•")" "$label" "$((padding_width - label_length))" "" "$value" return 0 fi printf '│ %s %s %s\n' "$(style_text "$style_detail" "•")" "$label" "$value" } # --- end: scripts/lib/ui/summary-primitives.sh --- # --- begin: scripts/lib/pipeline.sh --- # shellcheck shell=bash PIPELINE_STEP_IDS=() PIPELINE_STEP_MODES=() PIPELINE_STEP_TITLES=() PIPELINE_STEP_FUNCTIONS=() PIPELINE_STEP_COUNT_FLAGS=() PIPELINE_STEP_ARGS=() pipeline_reset() { PIPELINE_STEP_IDS=() PIPELINE_STEP_MODES=() PIPELINE_STEP_TITLES=() PIPELINE_STEP_FUNCTIONS=() PIPELINE_STEP_COUNT_FLAGS=() PIPELINE_STEP_ARGS=() } pipeline_add_step() { local step_id="$1" local step_mode="$2" local step_title="$3" local step_function="$4" local step_count_flag="${5:-1}" local step_args="${6:-}" PIPELINE_STEP_IDS+=("$step_id") PIPELINE_STEP_MODES+=("$step_mode") PIPELINE_STEP_TITLES+=("$step_title") PIPELINE_STEP_FUNCTIONS+=("$step_function") PIPELINE_STEP_COUNT_FLAGS+=("$step_count_flag") PIPELINE_STEP_ARGS+=("$step_args") } pipeline_step_matches_mode() { local step_mode="$1" local execution_mode="$2" [[ "$step_mode" == "all" || "$step_mode" == "$execution_mode" ]] } pipeline_contains_step_id() { local step_id="$1" local existing_id for existing_id in "${PIPELINE_STEP_IDS[@]}"; do [[ "$existing_id" == "$step_id" ]] && return 0 done return 1 } pipeline_count_steps_for_mode() { local execution_mode="$1" local total=0 local index for index in "${!PIPELINE_STEP_IDS[@]}"; do if ! pipeline_step_matches_mode "${PIPELINE_STEP_MODES[$index]}" "$execution_mode"; then continue fi [[ "${PIPELINE_STEP_COUNT_FLAGS[$index]:-0}" == "1" ]] || continue total=$((total + 1)) done printf '%s\n' "$total" } run_pipeline_steps() { local execution_mode="$1" local result_handler="${2:-}" local index=0 local step_title local step_function local step_count_flag local step_args while (( index < ${#PIPELINE_STEP_IDS[@]} )); do if ! pipeline_step_matches_mode "${PIPELINE_STEP_MODES[$index]}" "$execution_mode"; then index=$((index + 1)) continue fi step_title="${PIPELINE_STEP_TITLES[$index]}" step_function="${PIPELINE_STEP_FUNCTIONS[$index]}" step_count_flag="${PIPELINE_STEP_COUNT_FLAGS[$index]:-0}" step_args="${PIPELINE_STEP_ARGS[$index]}" if [[ "$step_count_flag" == "1" && -n "$step_title" ]]; then announce_step "$step_title" fi if [[ -n "$step_args" ]]; then "$step_function" "$step_args" else "$step_function" fi if [[ -n "$result_handler" ]]; then "$result_handler" fi index=$((index + 1)) done } # --- end: scripts/lib/pipeline.sh --- # --- begin: scripts/lib/step-registry.sh --- # shellcheck shell=bash # shellcheck disable=SC2034 STEP_DEFINITION_IDS=() declare -Ag STEP_DEFINITION_MODES=() declare -Ag STEP_DEFINITION_TITLES=() declare -Ag STEP_DEFINITION_FUNCTIONS=() declare -Ag STEP_DEFINITION_COUNT_FLAGS=() register_step_definition() { local step_id="$1" shift local assignment="" local step_mode="" local step_title="" local step_function="" local count_for_progress="" for assignment in "$@"; do case "$assignment" in mode=*) step_mode="${assignment#*=}" ;; title=*) step_title="${assignment#*=}" ;; function=*) step_function="${assignment#*=}" ;; count_for_progress=*) count_for_progress="${assignment#*=}" ;; *) printf 'Erro: metadado de etapa desconhecido para %s: %s\n' "$step_id" "$assignment" >&2 return 1 ;; esac done if [[ -z "$step_id" || -z "$step_mode" || -z "$step_function" || -z "$count_for_progress" ]]; then printf 'Erro: etapa incompleta: %s\n' "${step_id:-indefinida}" >&2 return 1 fi STEP_DEFINITION_IDS+=("$step_id") STEP_DEFINITION_MODES["$step_id"]="$step_mode" STEP_DEFINITION_TITLES["$step_id"]="$step_title" STEP_DEFINITION_FUNCTIONS["$step_id"]="$step_function" STEP_DEFINITION_COUNT_FLAGS["$step_id"]="$count_for_progress" } step_definition_mode() { printf '%s\n' "${STEP_DEFINITION_MODES[$1]:-}" } step_definition_title() { printf '%s\n' "${STEP_DEFINITION_TITLES[$1]:-}" } step_definition_function() { printf '%s\n' "${STEP_DEFINITION_FUNCTIONS[$1]:-}" } step_definition_count_flag() { printf '%s\n' "${STEP_DEFINITION_COUNT_FLAGS[$1]:-0}" } append_registered_step() { local step_id="$1" local step_args="${2:-}" local step_mode="" local step_title="" local step_function="" local step_count_flag="0" step_mode="$(step_definition_mode "$step_id")" step_title="$(step_definition_title "$step_id")" step_function="$(step_definition_function "$step_id")" step_count_flag="$(step_definition_count_flag "$step_id")" [[ -n "$step_mode" && -n "$step_function" ]] || { printf 'Erro: etapa não registrada: %s\n' "$step_id" >&2 return 1 } pipeline_add_step "$step_id" "$step_mode" "$step_title" "$step_function" "$step_count_flag" "$step_args" } # --- end: scripts/lib/step-registry.sh --- # --- begin: scripts/lib/step-manifest.sh --- # shellcheck shell=bash register_step_definition "runtime_validate_environment" \ "mode=all" \ "title=Validando ambiente..." \ "function=runtime_validate_environment_step" \ "count_for_progress=1" register_step_definition "load_configuration" \ "mode=all" \ "title=Carregando configuração..." \ "function=load_configuration_step" \ "count_for_progress=1" register_step_definition "check_only_verification" \ "mode=check" \ "title=Executando verificação sem alterações..." \ "function=check_only_step" \ "count_for_progress=1" register_step_definition "create_directories" \ "mode=install" \ "title=Criando diretórios..." \ "function=create_directories_step" \ "count_for_progress=1" register_step_definition "relocate_home_backups" \ "mode=install" \ "title=Organizando backups da home..." \ "function=relocate_home_backups_step" \ "count_for_progress=1" register_step_definition "relocate_home_repositories" \ "mode=install" \ "title=Reorganizando repositórios da home..." \ "function=relocate_home_repositories_step" \ "count_for_progress=1" register_step_definition "sync_managed_repositories" \ "mode=install" \ "title=Sincronizando repositórios gerenciados..." \ "function=sync_managed_repositories_step" \ "count_for_progress=1" register_step_definition "ensure_multilib" \ "mode=install" \ "title=Preparando repositório multilib..." \ "function=ensure_multilib_step" \ "count_for_progress=1" register_step_definition "update_system" \ "mode=install" \ "title=Atualizando o sistema..." \ "function=update_system_step" \ "count_for_progress=1" register_step_definition "install_local_support_packages" \ "mode=install" \ "title=Instalando ferramentas de suporte..." \ "function=install_local_support_packages_step" \ "count_for_progress=1" register_step_definition "prepare_package_installation" \ "mode=install" \ "title=" \ "function=prepare_package_installation_step" \ "count_for_progress=0" register_step_definition "install_official_packages" \ "mode=install" \ "title=Instalando apps oficiais..." \ "function=install_official_packages_step" \ "count_for_progress=1" register_step_definition "install_aur_packages" \ "mode=install" \ "title=Instalando apps AUR..." \ "function=install_aur_packages_step" \ "count_for_progress=1" register_step_definition "finalize_package_installation" \ "mode=install" \ "title=" \ "function=finalize_package_installation_step" \ "count_for_progress=0" register_step_definition "final_verification" \ "mode=install" \ "title=Validando instalação..." \ "function=final_verification_step" \ "count_for_progress=1" register_step_definition "bootstrap_validate_environment" \ "mode=bootstrap" \ "title=Validando ambiente..." \ "function=bootstrap_validate_environment_step" \ "count_for_progress=1" register_step_definition "bootstrap_check_dependencies" \ "mode=bootstrap" \ "title=Verificando dependências iniciais já instaladas..." \ "function=bootstrap_check_dependencies_step" \ "count_for_progress=1" register_step_definition "bootstrap_install_dependencies" \ "mode=bootstrap" \ "title=Instalando dependências iniciais..." \ "function=bootstrap_install_dependencies_step" \ "count_for_progress=1" register_step_definition "bootstrap_sync_repo" \ "mode=bootstrap" \ "title=Sincronizando repositório..." \ "function=bootstrap_sync_repo_step" \ "count_for_progress=1" # --- end: scripts/lib/step-manifest.sh --- # --- begin: scripts/lib/step-pipeline.sh --- # shellcheck shell=bash append_runtime_component_steps() { local phase="$1" local component_ids=() local component_id local pipeline_title="" local pipeline_function="" case "$phase" in pre_package) mapfile -t component_ids < <(component_pre_package_pipeline_ids) ;; post_package) mapfile -t component_ids < <(component_post_package_pipeline_ids) ;; *) printf 'Erro: fase de pipeline desconhecida: %s\n' "$phase" >&2 return 1 ;; esac for component_id in "${component_ids[@]}"; do pipeline_title="$(component_pipeline_title "$component_id")" pipeline_function="$(component_pipeline_step_function "$component_id")" pipeline_add_step "$component_id" "install" "$pipeline_title" "$pipeline_function" "1" done } append_runtime_install_pipeline() { local package_array_name="$1" local origin_status=0 append_registered_step "create_directories" append_registered_step "relocate_home_backups" append_registered_step "relocate_home_repositories" append_registered_step "ensure_multilib" append_registered_step "update_system" append_registered_step "install_local_support_packages" append_runtime_component_steps "pre_package" append_registered_step "sync_managed_repositories" if target_packages_have_official_entries "$package_array_name"; then append_registered_step "prepare_package_installation" append_registered_step "install_official_packages" "$package_array_name" else origin_status=$? if [[ "$origin_status" != "1" ]]; then return 1 fi fi if target_packages_have_aur_entries "$package_array_name"; then if ! pipeline_contains_step_id "prepare_package_installation"; then append_registered_step "prepare_package_installation" fi append_registered_step "install_aur_packages" "$package_array_name" else origin_status=$? if [[ "$origin_status" != "1" ]]; then return 1 fi fi if pipeline_contains_step_id "prepare_package_installation"; then append_registered_step "finalize_package_installation" fi append_runtime_component_steps "post_package" append_registered_step "final_verification" "$package_array_name" } define_runtime_pipeline() { local package_array_name="$1" pipeline_reset append_registered_step "runtime_validate_environment" append_registered_step "load_configuration" "$package_array_name" if [[ "$CHECK_ONLY" == "1" ]]; then append_registered_step "check_only_verification" "$package_array_name" fi } define_bootstrap_pipeline() { local missing_packages_array_name="$1" pipeline_reset append_registered_step "bootstrap_validate_environment" append_registered_step "bootstrap_check_dependencies" "$missing_packages_array_name" if bootstrap_missing_packages_present "$missing_packages_array_name"; then append_registered_step "bootstrap_install_dependencies" "$missing_packages_array_name" fi append_registered_step "bootstrap_sync_repo" } bootstrap_missing_packages_present() { local array_name="$1" declare -n missing_packages="$array_name" (( ${#missing_packages[@]} > 0 )) } # --- end: scripts/lib/step-pipeline.sh --- # --- begin: scripts/lib/process.sh --- # shellcheck shell=bash append_array_item() { local array_name="$1" local value="$2" local existing # shellcheck disable=SC2178 declare -n target_array="$array_name" for existing in "${target_array[@]}"; do [[ "$existing" == "$value" ]] && return 0 done target_array+=("$value") } package_is_installed() { pacman -Q "$1" >/dev/null 2>&1 } run_with_terminal_stdin() { if [[ -r /dev/tty ]]; then "$@" >"$LOG_FILE" 2>&1 return fi "$@" &1 | sed 's/^/│ /' return "${PIPESTATUS[0]}" } run_interactive_log_only() { if [[ "$STEP_OUTPUT_ONLY" == "1" ]]; then run_with_terminal_stdin "$@" >>"$LOG_FILE" 2>&1 return fi run_with_terminal_stdin "$@" 2>&1 | sed 's/^/│ /' return "${PIPESTATUS[0]}" } retry() { "$@" } retry_log_only() { run_log_only "$@" } retry_interactive_log_only() { run_interactive_log_only "$@" } # --- end: scripts/lib/process.sh --- # --- begin: scripts/lib/locking.sh --- # shellcheck shell=bash checkpoint_file() { printf '%s/checkpoints/%s.done\n' "$STATE_DIR" "$1" } has_checkpoint() { [[ -f "$(checkpoint_file "$1")" ]] } mark_checkpoint() { mkdir -p "$STATE_DIR/checkpoints" touch "$(checkpoint_file "$1")" } register_cleanup_path() { cleanup_paths+=("$1") } acquire_lock() { local existing_pid="" if [[ "$LOCK_HELD" == "1" ]]; then if [[ -f "$LOCK_DIR/pid" ]]; then existing_pid="$(<"$LOCK_DIR/pid")" fi if [[ -n "$existing_pid" && "$existing_pid" == "$$" ]]; then register_cleanup_path "$LOCK_DIR" return fi announce_error "Lock herdado inválido ou pertencente a outro processo." return 1 fi mkdir -p "$STATE_DIR" if mkdir "$LOCK_DIR" 2>/dev/null; then printf '%s\n' "$$" >"$LOCK_DIR/pid" register_cleanup_path "$LOCK_DIR" export POSTINSTALL_LOCK_HELD=1 LOCK_HELD=1 return fi if [[ -f "$LOCK_DIR/pid" ]]; then existing_pid="$(<"$LOCK_DIR/pid")" fi if [[ -z "$existing_pid" ]] || ! kill -0 "$existing_pid" 2>/dev/null; then announce_warning "Foi detectado um lock órfão. Limpando a execução anterior." rm -rf -- "$LOCK_DIR" if mkdir "$LOCK_DIR" 2>/dev/null; then printf '%s\n' "$$" >"$LOCK_DIR/pid" register_cleanup_path "$LOCK_DIR" export POSTINSTALL_LOCK_HELD=1 LOCK_HELD=1 return fi fi announce_error "Já existe outra execução do script em andamento." if [[ -f "$LOCK_DIR/pid" ]]; then announce_error "PID atual do lock: $(<"$LOCK_DIR/pid")" fi return 1 } cleanup() { local path for path in "${cleanup_paths[@]}"; do [[ -n "$path" ]] || continue rm -rf -- "$path" done } # --- end: scripts/lib/locking.sh --- # --- begin: scripts/lib/env.sh --- # shellcheck shell=bash ensure_not_root() { if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then announce_error "Execute este script como usuário comum, e não como root." return 1 fi } require_command() { if ! command -v "$1" >/dev/null 2>&1; then announce_error "Comando obrigatório não encontrado: $1" return 1 fi } canonicalize_path() { if ! command -v realpath >/dev/null 2>&1; then announce_error "Comando obrigatório não encontrado: realpath" return 1 fi realpath -m -- "$1" } require_exact_path() { local label="$1" local actual_path="$2" local expected_path="$3" local resolved_actual="" local resolved_expected="" [[ -n "$actual_path" && -n "$expected_path" ]] || { announce_error "Caminho inválido para $label." return 1 } resolved_actual="$(canonicalize_path "$actual_path")" || return 1 resolved_expected="$(canonicalize_path "$expected_path")" || return 1 if [[ "$resolved_actual" != "$resolved_expected" ]]; then announce_error "$label fora do caminho gerenciado esperado: $actual_path" announce_error "Esperado: $resolved_expected" return 1 fi } validate_managed_paths() { local expected_projects_dir="$HOME/Projects" local expected_repositories_dir="$HOME/Repositories" local expected_install_dir="$expected_projects_dir/arch-postinstall-apps" local expected_yay_repo_dir="$expected_repositories_dir/yay" local expected_easyeffects_preset_dir="$HOME/EasyEffects-Preset" local expected_state_dir="${XDG_STATE_HOME:-$HOME/.local/state}/arch-postinstall-apps" local expected_lock_dir="$expected_state_dir/lock" require_exact_path "PROJECTS_DIR" "$PROJECTS_DIR" "$expected_projects_dir" || return 1 require_exact_path "REPOSITORIES_DIR" "$REPOSITORIES_DIR" "$expected_repositories_dir" || return 1 require_exact_path "INSTALL_DIR" "$INSTALL_DIR" "$expected_install_dir" || return 1 require_exact_path "YAY_REPO_DIR" "$YAY_REPO_DIR" "$expected_yay_repo_dir" || return 1 require_exact_path "EASY_EFFECTS_PRESET_DIR" "$EASY_EFFECTS_PRESET_DIR" "$expected_easyeffects_preset_dir" || return 1 require_exact_path "STATE_DIR" "$STATE_DIR" "$expected_state_dir" || return 1 require_exact_path "LOCK_DIR" "$LOCK_DIR" "$expected_lock_dir" || return 1 } get_host_name() { if command -v hostname >/dev/null 2>&1; then hostname return fi if command -v hostnamectl >/dev/null 2>&1; then hostnamectl hostname 2>/dev/null return fi if [[ -f /etc/hostname ]]; then cat /etc/hostname return fi uname -n } sanitize_label() { printf '%s' "$1" | tr -cs '[:alnum:].@_-' '-' } is_wayland_session() { [[ "${XDG_SESSION_TYPE:-}" == "wayland" || -n "${WAYLAND_DISPLAY:-}" ]] } is_hyprland_session() { [[ -n "${HYPRLAND_INSTANCE_SIGNATURE:-}" ]] || \ [[ "${XDG_CURRENT_DESKTOP:-}" == *Hyprland* ]] || \ [[ "${DESKTOP_SESSION:-}" == "hyprland" ]] } is_supported_session() { is_wayland_session && is_hyprland_session } ensure_supported_session() { if is_supported_session; then return 0 fi announce_error "Este script foi ajustado para Wayland com Hyprland." announce_error "Sessão atual: XDG_SESSION_TYPE='${XDG_SESSION_TYPE:-}', XDG_CURRENT_DESKTOP='${XDG_CURRENT_DESKTOP:-}', DESKTOP_SESSION='${DESKTOP_SESSION:-}'" return 1 } ensure_arch() { if [[ ! -f /etc/arch-release ]]; then announce_error "Este script foi feito para Arch Linux." return 1 fi } github_ssh_expected() { [[ "$SKIP_GITHUB_SSH" != "1" ]] } github_ssh_force_reconcile() { [[ "$EXCLUSIVE_GITHUB_SSH_KEY" == "1" || -n "$GITHUB_SSH_KEY_NAME" ]] } # --- end: scripts/lib/env.sh --- # --- begin: scripts/lib/environment-validation.sh --- # shellcheck shell=bash validate_execution_environment_step() { local context_name="$1" local success_message="$2" step_result_reset if ! ensure_arch; then step_result_hard_fail "Este $context_name só pode ser executado em Arch Linux." return 0 fi if ! ensure_supported_session; then step_result_hard_fail "A sessão atual não é compatível com o $context_name." return 0 fi if ! require_command pacman; then step_result_hard_fail "O comando 'pacman' é obrigatório para continuar." return 0 fi if ! require_command sudo; then step_result_hard_fail "O comando 'sudo' é obrigatório para continuar." return 0 fi announce_prompt "Autenticando sudo..." if ! ops_sudo_auth; then step_result_hard_fail "Não foi possível autenticar o sudo." return 0 fi init_logging step_result_success "$success_message" } # --- end: scripts/lib/environment-validation.sh --- # --- begin: scripts/lib/ops/privilege.sh --- # shellcheck shell=bash ops_sudo_auth() { run_with_terminal_stdin sudo -v } # --- end: scripts/lib/ops/privilege.sh --- # --- begin: scripts/lib/ops/package.sh --- # shellcheck shell=bash ops_pacman_upgrade_and_install_needed() { retry_interactive_log_only sudo pacman -Syu --needed --noconfirm "$@" } ops_pacman_upgrade_full() { retry_interactive_log_only sudo pacman -Syu --noconfirm } ops_pacman_install_needed() { retry_interactive_log_only sudo pacman -S --needed --noconfirm "$@" } ops_pacman_remove_recursive() { retry_interactive_log_only sudo pacman -Rns --noconfirm "$@" } ops_pacman_refresh_databases() { run_interactive_log_only sudo pacman -Syy --noconfirm } ops_backup_pacman_conf() { local backup_path="$1" sudo cp /etc/pacman.conf "$backup_path" } ops_enable_multilib_config() { sudo sed -i \ '/^[[:space:]]*#\[multilib\][[:space:]]*$/,/^[[:space:]]*#Include = \/etc\/pacman.d\/mirrorlist[[:space:]]*$/ s/^[[:space:]]*#//' \ /etc/pacman.conf } ops_build_yay_package() { local yay_dir="$1" retry_log_only build_yay "$yay_dir" } ops_aur_install_needed() { local helper_name="$1" shift retry_log_only "$helper_name" -S --needed --noconfirm "$@" } # --- end: scripts/lib/ops/package.sh --- # --- begin: scripts/lib/ops/filesystem.sh --- # shellcheck shell=bash ops_download_file() { local source_url="$1" local destination_path="$2" retry curl -fsSL "$source_url" -o "$destination_path" } ops_extract_tar_gz() { local archive_path="$1" local destination_dir="$2" tar -xzf "$archive_path" -C "$destination_dir" } # --- end: scripts/lib/ops/filesystem.sh --- # --- begin: scripts/lib/ops/git.sh --- # shellcheck shell=bash ops_git_remote_add_origin() { local repo_dir="$1" local origin_url="$2" git -C "$repo_dir" remote add origin "$origin_url" } ops_git_remote_set_origin() { local repo_dir="$1" local origin_url="$2" git -C "$repo_dir" remote set-url origin "$origin_url" } ops_git_fetch_origin() { local repo_dir="$1" retry_log_only git -C "$repo_dir" fetch origin } ops_git_checkout_main() { local repo_dir="$1" run_log_only git -C "$repo_dir" checkout main } ops_git_checkout_main_from_origin() { local repo_dir="$1" run_log_only git -C "$repo_dir" checkout -b main origin/main } ops_git_pull_main_ff_only() { local repo_dir="$1" retry_log_only git -C "$repo_dir" pull --ff-only origin main } ops_git_update_submodules() { local repo_dir="$1" retry_log_only git -C "$repo_dir" submodule update --init --recursive } ops_git_clone_main() { local repo_url="$1" local repo_dir="$2" local clone_submodules="${3:-0}" local clone_args=(clone --branch main --single-branch) if [[ "$clone_submodules" == "1" ]]; then clone_args+=(--recurse-submodules) fi clone_args+=("$repo_url" "$repo_dir") retry_log_only git "${clone_args[@]}" } # --- end: scripts/lib/ops/git.sh --- # --- begin: scripts/lib/ops/github.sh --- # shellcheck shell=bash ops_gh_auth_login() { run_gh_auth_flow auth login --web --git-protocol ssh --scopes admin:public_key } ops_gh_auth_refresh_admin_public_key() { run_gh_auth_flow auth refresh -h github.com -s admin:public_key } ops_gh_get_authenticated_login() { retry gh api user --jq '.login' } ops_gh_list_ssh_keys_tsv() { retry gh api user/keys --jq '.[] | [.id, .title, .key] | @tsv' } ops_gh_delete_ssh_key() { local key_id="$1" retry gh api --method DELETE "user/keys/$key_id" } ops_gh_create_ssh_key() { local key_name="$1" local public_key="$2" retry gh api user/keys --method POST -f "title=$key_name" -f "key=$public_key" --jq '.id' } # --- end: scripts/lib/ops/github.sh --- # --- begin: scripts/lib/ops/node.sh --- # shellcheck shell=bash ops_npm_config_set_prefix() { local prefix_path="$1" run_log_only npm config set prefix "$prefix_path" } ops_npm_install_codex_cli() { retry_log_only npm install -g @openai/codex } # --- end: scripts/lib/ops/node.sh --- # --- begin: scripts/lib/ops/ssh.sh --- # shellcheck shell=bash ops_ssh_regenerate_public_key() { local private_key_path="$1" local public_key_path="$2" ssh-keygen -y -f "$private_key_path" >"$public_key_path" } ops_ssh_generate_key_pair() { local key_comment="$1" local key_path="$2" ssh-keygen -t ed25519 -C "$key_comment" -f "$key_path" -N "" } # --- end: scripts/lib/ops/ssh.sh --- # --- begin: scripts/lib/ops/systemd.sh --- # shellcheck shell=bash ops_systemctl_user_daemon_reload() { run_log_only systemctl --user daemon-reload } ops_systemctl_user_start() { run_log_only systemctl --user start "$@" } ops_systemctl_start() { run_log_only sudo systemctl start "$@" } ops_systemctl_enable() { run_log_only sudo systemctl enable "$@" } # --- end: scripts/lib/ops/systemd.sh --- # --- begin: scripts/lib/repo/manifest/store.sh --- # shellcheck shell=bash # shellcheck disable=SC2034 MANAGED_REPO_IDS=() declare -Ag MANAGED_REPO_DIRS=() declare -Ag MANAGED_REPO_HTTPS_URLS=() declare -Ag MANAGED_REPO_SSH_URLS=() declare -Ag MANAGED_REPO_LABELS=() declare -Ag MANAGED_REPO_ENVIRONMENT_FLAGS=() declare -Ag MANAGED_REPO_CLONE_SUBMODULE_FLAGS=() declare -Ag MANAGED_REPO_PREFERRED_TRANSPORTS=() managed_repo_manifest_reset() { MANAGED_REPO_IDS=() MANAGED_REPO_DIRS=() MANAGED_REPO_HTTPS_URLS=() MANAGED_REPO_SSH_URLS=() MANAGED_REPO_LABELS=() MANAGED_REPO_ENVIRONMENT_FLAGS=() MANAGED_REPO_CLONE_SUBMODULE_FLAGS=() MANAGED_REPO_PREFERRED_TRANSPORTS=() } # --- end: scripts/lib/repo/manifest/store.sh --- # --- begin: scripts/lib/repo/manifest/registration.sh --- # shellcheck shell=bash register_managed_repo() { local repo_id="$1" shift local assignment="" local repo_dir="" local https_url="" local ssh_url="" local repo_label="" local environment_repo="" local clone_submodules="0" local preferred_transport="auto" for assignment in "$@"; do case "$assignment" in dir=*) repo_dir="${assignment#*=}" ;; https_url=*) https_url="${assignment#*=}" ;; ssh_url=*) ssh_url="${assignment#*=}" ;; label=*) repo_label="${assignment#*=}" ;; environment=*) environment_repo="${assignment#*=}" ;; clone_submodules=*) clone_submodules="${assignment#*=}" ;; preferred_transport=*) preferred_transport="${assignment#*=}" ;; *) printf 'Erro: metadado de repositório desconhecido para %s: %s\n' "$repo_id" "$assignment" >&2 return 1 ;; esac done if [[ -z "$repo_id" || -z "$repo_dir" || -z "$https_url" || -z "$ssh_url" || \ -z "$repo_label" || -z "$environment_repo" ]]; then printf 'Erro: repositório gerenciado incompleto: %s\n' "${repo_id:-indefinido}" >&2 return 1 fi case "$clone_submodules" in 0|1) ;; *) printf 'Erro: clone_submodules inválido para %s: %s\n' "$repo_id" "$clone_submodules" >&2 return 1 ;; esac case "$preferred_transport" in auto|https|ssh) ;; *) printf 'Erro: preferred_transport inválido para %s: %s\n' "$repo_id" "$preferred_transport" >&2 return 1 ;; esac MANAGED_REPO_IDS+=("$repo_id") MANAGED_REPO_DIRS["$repo_id"]="$repo_dir" MANAGED_REPO_HTTPS_URLS["$repo_id"]="$https_url" MANAGED_REPO_SSH_URLS["$repo_id"]="$ssh_url" MANAGED_REPO_LABELS["$repo_id"]="$repo_label" MANAGED_REPO_ENVIRONMENT_FLAGS["$repo_id"]="$environment_repo" MANAGED_REPO_CLONE_SUBMODULE_FLAGS["$repo_id"]="$clone_submodules" MANAGED_REPO_PREFERRED_TRANSPORTS["$repo_id"]="$preferred_transport" } # --- end: scripts/lib/repo/manifest/registration.sh --- # --- begin: scripts/lib/repo/manifest/definitions.sh --- # shellcheck shell=bash managed_repo_manifest_init() { managed_repo_manifest_reset register_managed_repo "install" \ "dir=$INSTALL_DIR" \ "https_url=$REPO_HTTPS_URL" \ "ssh_url=$REPO_SSH_URL" \ "label=arch-postinstall-apps" \ "environment=0" register_managed_repo "easyeffects_preset" \ "dir=$EASY_EFFECTS_PRESET_DIR" \ "https_url=$EASY_EFFECTS_PRESET_REPO_HTTPS_URL" \ "ssh_url=$EASY_EFFECTS_PRESET_REPO_SSH_URL" \ "label=EasyEffects-Preset" \ "environment=1" register_managed_repo "terminal_lyrics" \ "dir=$TERMINAL_LYRICS_DIR" \ "https_url=$TERMINAL_LYRICS_REPO_HTTPS_URL" \ "ssh_url=$TERMINAL_LYRICS_REPO_SSH_URL" \ "label=terminal-lyrics" \ "environment=1" register_managed_repo "synthetic_profile_generator" \ "dir=$SYNTHETIC_PROFILE_GENERATOR_DIR" \ "https_url=$SYNTHETIC_PROFILE_GENERATOR_REPO_HTTPS_URL" \ "ssh_url=$SYNTHETIC_PROFILE_GENERATOR_REPO_SSH_URL" \ "label=synthetic-profile-generator" \ "environment=1" register_managed_repo "obslove_dots" \ "dir=$OBSLOVE_DOTS_DIR" \ "https_url=$OBSLOVE_DOTS_REPO_HTTPS_URL" \ "ssh_url=$OBSLOVE_DOTS_REPO_SSH_URL" \ "label=obslove" \ "environment=1" register_managed_repo "dots_hyprland" \ "dir=$DOTS_HYPRLAND_DIR" \ "https_url=$DOTS_HYPRLAND_REPO_HTTPS_URL" \ "ssh_url=$DOTS_HYPRLAND_REPO_SSH_URL" \ "label=dots-hyprland" \ "environment=1" \ "preferred_transport=ssh" register_managed_repo "ii_vynx" \ "dir=$II_VYNX_DIR" \ "https_url=$II_VYNX_REPO_HTTPS_URL" \ "ssh_url=$II_VYNX_REPO_SSH_URL" \ "label=ii-vynx" \ "environment=1" \ "clone_submodules=1" \ "preferred_transport=https" } # --- end: scripts/lib/repo/manifest/definitions.sh --- # --- begin: scripts/lib/repo/manifest/queries.sh --- # shellcheck shell=bash managed_repo_id_for_dir() { local expected_repo_dir="$1" local repo_id for repo_id in "${MANAGED_REPO_IDS[@]}"; do if [[ "${MANAGED_REPO_DIRS[$repo_id]}" == "$expected_repo_dir" ]]; then printf '%s\n' "$repo_id" return 0 fi done return 1 } managed_repo_expected_https_origin_url() { local repo_id="" repo_id="$(managed_repo_id_for_dir "$1")" || return 1 printf '%s\n' "${MANAGED_REPO_HTTPS_URLS[$repo_id]}" } managed_repo_expected_ssh_origin_url() { local repo_id="" repo_id="$(managed_repo_id_for_dir "$1")" || return 1 printf '%s\n' "${MANAGED_REPO_SSH_URLS[$repo_id]}" } managed_environment_repo_dirs() { local repo_id for repo_id in "${MANAGED_REPO_IDS[@]}"; do [[ "${MANAGED_REPO_ENVIRONMENT_FLAGS[$repo_id]}" == "1" ]] || continue printf '%s\n' "${MANAGED_REPO_DIRS[$repo_id]}" done } managed_repo_clone_submodules() { local repo_id="" repo_id="$(managed_repo_id_for_dir "$1")" || return 1 printf '%s\n' "${MANAGED_REPO_CLONE_SUBMODULE_FLAGS[$repo_id]:-0}" } managed_repo_preferred_transport() { local repo_id="" repo_id="$(managed_repo_id_for_dir "$1")" || return 1 printf '%s\n' "${MANAGED_REPO_PREFERRED_TRANSPORTS[$repo_id]:-auto}" } managed_repo_display_name() { local repo_id="" repo_id="$(managed_repo_id_for_dir "$1")" || { basename "$1" return 0 } printf '%s\n' "${MANAGED_REPO_LABELS[$repo_id]}" } # --- end: scripts/lib/repo/manifest/queries.sh --- # --- begin: scripts/lib/repo/origin/status.sh --- # shellcheck shell=bash get_repo_branch() { local repo_dir="$1" local branch_name="" if ! git -C "$repo_dir" rev-parse --is-inside-work-tree >/dev/null 2>&1; then return 1 fi branch_name="$(git -C "$repo_dir" symbolic-ref --quiet --short HEAD 2>/dev/null || true)" if [[ -n "$branch_name" ]]; then printf '%s\n' "$branch_name" return 0 fi branch_name="$(git -C "$repo_dir" rev-parse --short HEAD 2>/dev/null || true)" [[ -n "$branch_name" ]] || return 1 printf 'detached@%s\n' "$branch_name" } current_repo_origin_status() { local repo_dir="$1" local current_origin_url="" local managed_https_url="" local managed_ssh_url="" current_origin_url="$(git -C "$repo_dir" remote get-url origin 2>/dev/null || true)" managed_https_url="$(managed_repo_expected_https_origin_url "$repo_dir" 2>/dev/null || true)" managed_ssh_url="$(managed_repo_expected_ssh_origin_url "$repo_dir" 2>/dev/null || true)" if [[ -z "$current_origin_url" ]]; then printf '%s\n' "ausente" return 0 fi if [[ "$current_origin_url" == "$REPO_SSH_URL" || ( -n "$managed_ssh_url" && "$current_origin_url" == "$managed_ssh_url" ) ]]; then printf '%s\n' "ssh" return 0 fi if [[ "$current_origin_url" == "$REPO_HTTPS_URL" || ( -n "$managed_https_url" && "$current_origin_url" == "$managed_https_url" ) ]]; then printf '%s\n' "https" return 0 fi printf '%s\n' "personalizado" } current_repo_commit_short() { local repo_dir="$1" local commit_hash="" commit_hash="$(git -C "$repo_dir" rev-parse --short HEAD 2>/dev/null || true)" [[ -n "$commit_hash" ]] || { printf '%s\n' "indisponível" return 0 } printf '%s\n' "$commit_hash" } # --- end: scripts/lib/repo/origin/status.sh --- # --- begin: scripts/lib/repo/origin/remote.sh --- # shellcheck shell=bash ensure_repo_origin_remote() { local repo_dir="$1" local desired_origin_url="${2:-$REPO_HTTPS_URL}" local current_origin_url="" local allowed_origin_urls=() local managed_https_url="" local managed_ssh_url="" local allowed_origin_url="" local matches_allowed_origin=1 current_origin_url="$(git -C "$repo_dir" remote get-url origin 2>/dev/null || true)" if [[ -z "$current_origin_url" ]]; then ops_git_remote_add_origin "$repo_dir" "$desired_origin_url" return fi allowed_origin_urls=("$REPO_HTTPS_URL" "$REPO_SSH_URL") managed_https_url="$(managed_repo_expected_https_origin_url "$repo_dir" 2>/dev/null || true)" managed_ssh_url="$(managed_repo_expected_ssh_origin_url "$repo_dir" 2>/dev/null || true)" [[ -n "$managed_https_url" ]] && allowed_origin_urls+=("$managed_https_url") [[ -n "$managed_ssh_url" ]] && allowed_origin_urls+=("$managed_ssh_url") for allowed_origin_url in "${allowed_origin_urls[@]}"; do if [[ "$current_origin_url" == "$allowed_origin_url" ]]; then matches_allowed_origin=0 break fi done if [[ "$matches_allowed_origin" != "0" ]]; then announce_detail "Foi detectado um remoto origin personalizado em $repo_dir. A configuração atual será mantida." return fi if [[ "$current_origin_url" != "$desired_origin_url" ]]; then ops_git_remote_set_origin "$repo_dir" "$desired_origin_url" fi } managed_repo_origin_matches_expected() { local repo_dir="$1" local current_origin_url="" local expected_https_url="" local expected_ssh_url="" current_origin_url="$(git -C "$repo_dir" remote get-url origin 2>/dev/null || true)" expected_https_url="$(managed_repo_expected_https_origin_url "$repo_dir" 2>/dev/null || true)" expected_ssh_url="$(managed_repo_expected_ssh_origin_url "$repo_dir" 2>/dev/null || true)" [[ -n "$current_origin_url" && -n "$expected_https_url" && -n "$expected_ssh_url" ]] || return 1 [[ "$current_origin_url" == "$expected_https_url" || "$current_origin_url" == "$expected_ssh_url" ]] } # --- end: scripts/lib/repo/origin/remote.sh --- # --- begin: scripts/lib/repo/origin/transport.sh --- # shellcheck shell=bash git_ssh_transport_ready() { local repo_url="$1" [[ -n "$repo_url" ]] || return 1 command -v git >/dev/null 2>&1 || return 1 GIT_SSH_COMMAND='ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new' \ git ls-remote "$repo_url" HEAD >/dev/null 2>&1 } # --- end: scripts/lib/repo/origin/transport.sh --- # --- begin: scripts/lib/repo/origin/managed.sh --- # shellcheck shell=bash managed_repo_preferred_origin_url() { local repo_dir="$1" local https_url="" local ssh_url="" local preferred_transport="" https_url="$(managed_repo_expected_https_origin_url "$repo_dir" 2>/dev/null || true)" ssh_url="$(managed_repo_expected_ssh_origin_url "$repo_dir" 2>/dev/null || true)" preferred_transport="$(managed_repo_preferred_transport "$repo_dir" 2>/dev/null || true)" [[ -n "$https_url" && -n "$ssh_url" ]] || return 1 case "$preferred_transport" in https) printf '%s\n' "$https_url" return 0 ;; ssh) printf '%s\n' "$ssh_url" return 0 ;; esac if github_ssh_expected && git_ssh_transport_ready "$ssh_url"; then printf '%s\n' "$ssh_url" return 0 fi printf '%s\n' "$https_url" } ensure_managed_repo_origin_remote() { local repo_dir="$1" local desired_origin_url="" desired_origin_url="$(managed_repo_preferred_origin_url "$repo_dir" 2>/dev/null || true)" [[ -n "$desired_origin_url" ]] || return 1 ensure_repo_origin_remote "$repo_dir" "$desired_origin_url" } ensure_managed_repo_origin_ssh() { local repo_dir="$1" local desired_origin_url="" desired_origin_url="$(managed_repo_expected_ssh_origin_url "$repo_dir" 2>/dev/null || true)" [[ -n "$desired_origin_url" ]] || return 1 ensure_repo_origin_remote "$repo_dir" "$desired_origin_url" } reconcile_managed_repo_origin_ssh() { local repo_dir="$1" local preferred_transport="" [[ -d "$repo_dir/.git" ]] || return 2 preferred_transport="$(managed_repo_preferred_transport "$repo_dir" 2>/dev/null || true)" [[ "$preferred_transport" != "https" ]] || return 2 if ! managed_repo_origin_matches_expected "$repo_dir"; then announce_detail "Foi detectado um remoto origin personalizado em $repo_dir. A configuração atual será mantida." return 2 fi if ! ensure_managed_repo_origin_ssh "$repo_dir"; then return 1 fi return 0 } easyeffects_preset_origin_matches() { managed_repo_origin_matches_expected "$EASY_EFFECTS_PRESET_DIR" } # --- end: scripts/lib/repo/origin/managed.sh --- # --- begin: scripts/lib/repo/relocation.sh --- # shellcheck shell=bash legacy_install_dir_path() { printf '%s\n' "$REPOSITORIES_DIR/arch-postinstall-apps" } relocate_managed_install_repo() { local legacy_install_dir="" legacy_install_dir="$(legacy_install_dir_path)" [[ "$legacy_install_dir" != "$INSTALL_DIR" ]] || return 3 [[ -d "$legacy_install_dir" ]] || return 2 if [[ -e "$INSTALL_DIR" ]]; then announce_warning "O clone gerenciado legado em $legacy_install_dir não foi movido porque $INSTALL_DIR já existe." return 2 fi if [[ ! -d "$legacy_install_dir/.git" ]]; then announce_warning "$legacy_install_dir existe, mas não é um repositório git gerenciado." return 2 fi mkdir -p "$(dirname "$INSTALL_DIR")" announce_detail "Movendo clone gerenciado para $INSTALL_DIR..." if mv "$legacy_install_dir" "$INSTALL_DIR"; then return 0 fi announce_warning "Não foi possível mover o clone gerenciado para $INSTALL_DIR." return 1 } directory_is_git_repository() { local dir_path="$1" git -C "$dir_path" rev-parse --is-inside-work-tree >/dev/null 2>&1 } home_repo_relocation_allowed() { local repo_dir="$1" local repo_name="" repo_name="$(basename "$repo_dir")" case "$repo_name" in Backups|Codex|Dots|EasyEffects-Preset|Pictures|Projects|Repositories|Videos) return 1 ;; esac return 0 } collect_loose_home_git_repositories() { local array_name="$1" local candidate local nullglob_was_enabled=0 # shellcheck disable=SC2178 declare -n target_repositories="$array_name" target_repositories=() if shopt -q nullglob; then nullglob_was_enabled=1 fi shopt -s nullglob for candidate in "$HOME"/*; do [[ -d "$candidate" && ! -L "$candidate" ]] || continue home_repo_relocation_allowed "$candidate" || continue directory_is_git_repository "$candidate" || continue target_repositories+=("$candidate") done if [[ "$nullglob_was_enabled" != "1" ]]; then shopt -u nullglob fi } relocate_loose_home_git_repository() { local source_repo_dir="$1" local repo_name="" local target_repo_dir="" repo_name="$(basename "$source_repo_dir")" target_repo_dir="$REPOSITORIES_DIR/$repo_name" if [[ -e "$target_repo_dir" ]]; then announce_warning "O repositório '$repo_name' em $HOME não foi movido porque $target_repo_dir já existe." return 2 fi announce_detail "Movendo repositório git para $target_repo_dir..." if mv "$source_repo_dir" "$target_repo_dir"; then return 0 fi announce_warning "Não foi possível mover o repositório '$repo_name' para $target_repo_dir." return 1 } # --- end: scripts/lib/repo/relocation.sh --- # --- begin: scripts/lib/repo/sync.sh --- # shellcheck shell=bash managed_repo_is_dirty() { local repo_dir="$1" ! git -C "$repo_dir" diff --quiet --no-ext-diff || \ ! git -C "$repo_dir" diff --cached --quiet --no-ext-diff || \ [[ -n "$(git -C "$repo_dir" status --porcelain --untracked-files=normal)" ]] } sync_git_main_branch() { local repo_dir="$1" local repo_label="$2" local sync_mode="${3:-strict}" local fetched_origin=0 if ops_git_fetch_origin "$repo_dir"; then fetched_origin=1 elif [[ "$sync_mode" == "bootstrap" ]]; then announce_warning "Falha ao buscar atualizações de origin. O script tentará usar a cópia local." else announce_warning "Não foi possível buscar atualizações de $repo_label." return 1 fi if git -C "$repo_dir" show-ref --verify --quiet "refs/heads/main"; then if ! ops_git_checkout_main "$repo_dir"; then if [[ "$sync_mode" == "bootstrap" ]]; then announce_error "Não foi possível trocar para a branch local 'main'." else announce_warning "Não foi possível trocar $repo_label para a branch 'main'." fi return 1 fi elif git -C "$repo_dir" show-ref --verify --quiet "refs/remotes/origin/main"; then if ! ops_git_checkout_main_from_origin "$repo_dir"; then if [[ "$sync_mode" == "bootstrap" ]]; then announce_error "Não foi possível criar a branch local 'main' a partir de origin." else announce_warning "Não foi possível criar a branch local 'main' de $repo_label." fi return 1 fi elif [[ "$sync_mode" == "bootstrap" && "$fetched_origin" == "0" ]]; then announce_error "Não foi possível atualizar origin e a branch 'main' não existe localmente." announce_error "Verifique acesso ao GitHub ou recupere um clone local válido." return 1 else if [[ "$sync_mode" == "bootstrap" ]]; then announce_error "Branch 'main' não encontrada no repositório local nem em origin." else announce_warning "A branch 'main' não foi encontrada em $repo_label." fi return 1 fi if [[ "$sync_mode" == "bootstrap" && "$fetched_origin" == "0" ]]; then announce_warning "O 'git pull' será ignorado porque o fetch de origin falhou. O script continuará com a branch local." return 0 fi if ops_git_pull_main_ff_only "$repo_dir"; then return 0 fi if [[ "$sync_mode" == "bootstrap" ]]; then announce_warning "Falha ao atualizar 'main' com 'git pull --ff-only'. O script continuará com a cópia atual." return 0 fi announce_warning "Não foi possível atualizar $repo_label com 'git pull --ff-only'." return 1 } sync_managed_repo() { local repo_dir="$1" local repo_label="" local previous_commit="" local current_commit="" local clone_origin_url="" local clone_submodules="0" local label_suffix="" repo_label="$(managed_repo_display_name "$repo_dir")" clone_submodules="$(managed_repo_clone_submodules "$repo_dir" 2>/dev/null || printf '0\n')" label_suffix="do repositório $repo_label" if ! command -v git >/dev/null 2>&1; then announce_warning "O git não está disponível para sincronizar $label_suffix." return 1 fi if [[ -d "$repo_dir/.git" ]]; then previous_commit="$(git -C "$repo_dir" rev-parse HEAD 2>/dev/null || true)" if ! managed_repo_origin_matches_expected "$repo_dir"; then announce_warning "O diretório $repo_dir já é um repositório git com origin diferente. A sincronização será ignorada." return 2 fi if managed_repo_is_dirty "$repo_dir"; then announce_warning "O repositório em $repo_dir tem alterações locais. A atualização automática será ignorada." return 2 fi if ! ensure_managed_repo_origin_remote "$repo_dir"; then announce_warning "Não foi possível ajustar o remoto de $label_suffix." return 1 fi announce_detail "Atualizando $label_suffix..." if ! sync_git_main_branch "$repo_dir" "$label_suffix"; then return 1 fi if [[ "$clone_submodules" == "1" ]]; then announce_detail "Atualizando submódulos $label_suffix..." if ! ops_git_update_submodules "$repo_dir"; then announce_warning "Não foi possível atualizar os submódulos de $repo_label." return 1 fi fi current_commit="$(git -C "$repo_dir" rev-parse HEAD 2>/dev/null || true)" if [[ -n "$previous_commit" && "$previous_commit" == "$current_commit" ]]; then return 3 fi return 0 fi if [[ -e "$repo_dir" && -n "$(find "$repo_dir" -mindepth 1 -maxdepth 1 2>/dev/null)" ]]; then announce_warning "$repo_dir já existe e não está vazio. O clone de $label_suffix será ignorado." return 2 fi announce_detail "Clonando $label_suffix em $repo_dir..." clone_origin_url="$(managed_repo_preferred_origin_url "$repo_dir" 2>/dev/null || true)" if [[ -z "$clone_origin_url" ]]; then announce_warning "Não foi possível definir a URL de clone de $label_suffix." return 1 fi if ops_git_clone_main "$clone_origin_url" "$repo_dir" "$clone_submodules"; then return 0 fi announce_warning "Não foi possível clonar $label_suffix." return 1 } # --- end: scripts/lib/repo/sync.sh --- # --- begin: scripts/bootstrap/repo-sync.sh --- # shellcheck shell=bash sync_repo() { local current_branch="" local clone_origin_url="" mkdir -p "$(dirname "$INSTALL_DIR")" relocate_managed_install_repo || true if [[ -d "$INSTALL_DIR/.git" ]]; then announce_detail "Atualizando clone gerenciado..." if managed_repo_is_dirty "$INSTALL_DIR"; then current_branch="$(get_repo_branch "$INSTALL_DIR" 2>/dev/null || true)" if [[ -n "$current_branch" && "$current_branch" != "main" ]]; then announce_error "O clone gerenciado está com mudanças locais na branch '$current_branch'." announce_error "Não dá para executar com segurança a branch 'main' sem limpar ou mover essas mudanças." return 1 fi announce_warning "O repositório local tem alterações. A atualização automática será ignorada." return 0 fi if ! ensure_managed_repo_origin_remote "$INSTALL_DIR"; then announce_error "Não foi possível ajustar o remoto origin do clone gerenciado." return 1 fi sync_git_main_branch "$INSTALL_DIR" "clone gerenciado" "bootstrap" return fi if [[ -e "$INSTALL_DIR" ]]; then announce_error "$INSTALL_DIR já existe e não é um repositório git." return 1 fi announce_detail "Clonando repositório pela primeira vez..." clone_origin_url="$(managed_repo_preferred_origin_url "$INSTALL_DIR" 2>/dev/null || true)" if [[ -z "$clone_origin_url" ]]; then announce_error "Não foi possível definir a URL de clone do repositório principal." return 1 fi if ! ops_git_clone_main "$clone_origin_url" "$INSTALL_DIR"; then announce_error "Falha ao clonar 'main' de $clone_origin_url." announce_error "Verifique acesso ao GitHub e se a branch existe no remoto." return 1 fi } # --- end: scripts/bootstrap/repo-sync.sh --- # --- begin: scripts/bootstrap/config.sh --- # shellcheck shell=bash # shellcheck disable=SC2034 readonly -a BOOTSTRAP_REMOTE_PACKAGES=( ca-certificates git curl tar ) # --- end: scripts/bootstrap/config.sh --- # --- begin: scripts/bootstrap/steps/system.sh --- # shellcheck shell=bash bootstrap_validate_environment_step() { validate_execution_environment_step "bootstrap" "O ambiente de bootstrap foi validado." } # --- end: scripts/bootstrap/steps/system.sh --- # --- begin: scripts/bootstrap/steps/packages.sh --- # shellcheck shell=bash # shellcheck disable=SC2034 bootstrap_check_dependencies_step() { local array_name="$1" # shellcheck disable=SC2178 declare -n missing_packages="$array_name" step_result_reset if ((${#missing_packages[@]} == 0)); then announce_detail "As dependências iniciais já estão disponíveis." step_result_success "As dependências iniciais já estavam disponíveis." return 0 fi announce_detail "${#missing_packages[@]} dependência(s) inicial(is) ainda não instalada(s)." step_result_success "As dependências iniciais foram avaliadas." } bootstrap_install_dependencies_step() { local array_name="${1:-BOOTSTRAP_MISSING_PACKAGES}" # shellcheck disable=SC2178 declare -n missing_packages="$array_name" step_result_reset if ((${#missing_packages[@]} == 0)); then announce_detail "As dependências iniciais já estão disponíveis. Etapa ignorada." step_result_skipped "As dependências iniciais já estavam disponíveis." return 0 fi if ops_pacman_upgrade_and_install_needed "${missing_packages[@]}"; then BOOTSTRAP_SYSTEM_UPDATED=1 step_result_success "As dependências iniciais foram instaladas." return 0 fi step_result_hard_fail "Não foi possível instalar as dependências iniciais." } # --- end: scripts/bootstrap/steps/packages.sh --- # --- begin: scripts/bootstrap/steps/repo.sh --- # shellcheck shell=bash bootstrap_sync_repo_step() { step_result_reset if ! require_command git; then step_result_hard_fail "O comando 'git' é obrigatório para sincronizar o repositório." return 0 fi if ! require_command curl; then step_result_hard_fail "O comando 'curl' é obrigatório para sincronizar o repositório." return 0 fi if ! require_command tar; then step_result_hard_fail "O comando 'tar' é obrigatório para sincronizar o repositório." return 0 fi if sync_repo; then step_result_success "O repositório gerenciado foi sincronizado." return 0 fi step_result_hard_fail "Não foi possível sincronizar o repositório gerenciado." } # --- end: scripts/bootstrap/steps/repo.sh --- # --- begin: scripts/bootstrap/entrypoint.sh --- # shellcheck shell=bash # shellcheck disable=SC2034 # shellcheck source-path=SCRIPTDIR SELF_PATH="${BASH_SOURCE[0]:-$0}" BOOTSTRAP_SCRIPT_DIR="$(cd "$(dirname "$SELF_PATH")" && pwd)" resolve_local_main() { local candidate="" for candidate in \ "$BOOTSTRAP_SCRIPT_DIR/scripts/install/main.sh" \ "$BOOTSTRAP_SCRIPT_DIR/../scripts/install/main.sh"; do if [[ -f "$candidate" ]]; then printf '%s\n' "$candidate" return 0 fi done return 1 } LOCAL_MAIN="$(resolve_local_main 2>/dev/null || true)" if [[ -n "$LOCAL_MAIN" ]]; then exec bash "$LOCAL_MAIN" "$@" fi BOOTSTRAP_MISSING_PACKAGES=() BOOTSTRAP_SYSTEM_UPDATED=0 cleanup_paths=() step_counter=0 step_open=0 step_total=0 STEP_RESULT_STATUS="" STEP_RESULT_MESSAGE="" init_output_styles handle_bootstrap_step_result_or_exit() { case "${STEP_RESULT_STATUS:-}" in success|skipped|"") return 0 ;; hard_fail) if [[ -n "${STEP_RESULT_MESSAGE:-}" ]]; then announce_error "$STEP_RESULT_MESSAGE" fi exit 1 ;; *) announce_error "Resultado de etapa desconhecido no bootstrap: ${STEP_RESULT_STATUS:-indefinido}" exit 1 ;; esac } main() { local context_status=0 load_bootstrap_invocation_context "$@" || context_status=$? if (( context_status == CLI_HELP_REQUESTED_STATUS )); then exit 0 fi if (( context_status != 0 )); then exit "$context_status" fi validate_managed_paths || exit 1 trap cleanup EXIT ensure_not_root || exit 1 acquire_lock || exit 1 collect_missing_packages BOOTSTRAP_MISSING_PACKAGES "${BOOTSTRAP_REMOTE_PACKAGES[@]}" define_bootstrap_pipeline BOOTSTRAP_MISSING_PACKAGES set_step_total "$(pipeline_count_steps_for_mode bootstrap)" run_pipeline_steps "bootstrap" "handle_bootstrap_step_result_or_exit" exec_runtime_with_invocation_context "$INSTALL_DIR/scripts/install/main.sh" } main "$@" # --- end: scripts/bootstrap/entrypoint.sh ---