#!/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 e sem autenticar sudo interativamente. --dry-run Mostra o plano de execução local sem aplicar alterações reais. --allow-home-changes Permite reorganizar arquivos/repositórios em HOME sem confirmação interativa. --allow-system-config Permite editar configurações de sistema confirmáveis em modo não interativo. -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 ;; --dry-run) DRY_RUN=1 shift ;; --allow-home-changes) ALLOW_HOME_CHANGES=1 shift ;; --allow-system-config) ALLOW_SYSTEM_CONFIG=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 } [[ -n "$2" ]] || { printf 'Erro: faltou informar o valor de %s.\n' "$1" >&2 return 1 } GITHUB_SSH_KEY_NAME="$2" shift 2 ;; --ssh-name=*) [[ -n "${1#*=}" ]] || { printf 'Erro: faltou informar o valor de --ssh-name.\n' >&2 return 1 } 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 if [[ "$CHECK_ONLY" == "1" && "$DRY_RUN" == "1" ]]; then printf 'Erro: --check e --dry-run têm semânticas diferentes e não podem ser combinados.\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}}" DRY_RUN="${POSTINSTALL_DRY_RUN:-${DRY_RUN:-0}}" ALLOW_HOME_CHANGES="${POSTINSTALL_ALLOW_HOME_CHANGES:-${ALLOW_HOME_CHANGES:-0}}" ALLOW_SYSTEM_CONFIG="${POSTINSTALL_ALLOW_SYSTEM_CONFIG:-${ALLOW_SYSTEM_CONFIG:-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 DRY_RUN readonly ALLOW_HOME_CHANGES readonly ALLOW_SYSTEM_CONFIG 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_DRY_RUN=$DRY_RUN" "POSTINSTALL_ALLOW_HOME_CHANGES=$ALLOW_HOME_CHANGES" "POSTINSTALL_ALLOW_SYSTEM_CONFIG=$ALLOW_SYSTEM_CONFIG" "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" } increment_step_total() { step_total=$((step_total + ${1:-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 "dry_run_plan" \ "mode=dry-run" \ "title=Montando plano sem alterações..." \ "function=dry_run_plan_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 if [[ "$DRY_RUN" == "1" ]]; then append_registered_step "load_configuration" "$package_array_name" append_registered_step "dry_run_plan" "$package_array_name" return 0 fi 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_ref="$array_name" for existing in "${target_array_ref[@]}"; do [[ "$existing" == "$value" ]] && return 0 done target_array_ref+=("$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 if [[ "${CHECK_ONLY:-0}" == "1" ]]; then announce_detail "Check: sudo encontrado; autenticação interativa não será solicitada." announce_detail "Uma execução real ainda exigirá sudo/root para pacman, systemd e arquivos de sistema." if declare -F operation_plan_add >/dev/null; then operation_plan_add \ "check-sudo-requirement" \ "privilege" \ "sudo/root" \ "Validar requisito de sudo para execução real" \ "high" \ "" \ "allow" \ "" \ "executar novamente em terminal interativo se a instalação real precisar autenticar sudo" || true operation_plan_set_command "check-sudo-requirement" "sudo -v" "1" operation_plan_set_status "check-sudo-requirement" "skipped" "check-only: autenticação não solicitada" fi init_logging step_result_success "$success_message" 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/common.sh --- # shellcheck shell=bash ops_sanitize_id() { if declare -F sanitize_label >/dev/null; then sanitize_label "$1" return 0 fi printf '%s' "$1" | tr -cs '[:alnum:].@_-' '-' } ops_redact_command_arg() { local arg="$1" case "$arg" in *token=*|*TOKEN=*|*secret=*|*SECRET=*|*password=*|*PASSWORD=*) printf '%s\n' "${arg%%=*}=" ;; ghp_*|github_pat_*|glpat-*|xoxb-*|xoxp-*) printf '\n' ;; *) printf '%s\n' "$arg" ;; esac } ops_command_line() { local command_line="" local part local rendered_part local quoted_part for part in "$@"; do rendered_part="$(ops_redact_command_arg "$part")" printf -v quoted_part '%q' "$rendered_part" command_line="${command_line:+$command_line }$quoted_part" done printf '%s\n' "$command_line" } # --- end: scripts/lib/ops/common.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 declare -Ag OPS_PACMAN_OPERATION_ACTIONS=() declare -Ag OPS_PACMAN_OPERATION_PACKAGES=() declare -Ag OPS_AUR_OPERATION_HELPERS=() declare -Ag OPS_AUR_OPERATION_PACKAGES=() declare -Ag OPS_YAY_BUILD_OPERATION_DIRS=() ops_package_planner_available() { declare -F operation_plan_add >/dev/null && declare -F operation_plan_execute >/dev/null } ops_pacman_db_lock_file() { printf '%s\n' "${POSTINSTALL_PACMAN_DB_LOCK_FILE:-/var/lib/pacman/db.lck}" } ops_pacman_db_lock_present() { [[ -e "$(ops_pacman_db_lock_file)" ]] } ops_warn_pacman_lock() { local lock_file lock_file="$(ops_pacman_db_lock_file)" if declare -F announce_warning >/dev/null; then announce_warning "Operação bloqueada: lock do pacman encontrado em $lock_file." fi } ops_block_planned_package_operation_if_locked() { local operation_id="$1" [[ "${DRY_RUN:-0}" == "1" ]] && return 1 ops_pacman_db_lock_present || return 1 ops_warn_pacman_lock operation_plan_set_status "$operation_id" "blocked" "lock do pacman encontrado em $(ops_pacman_db_lock_file)" return 0 } ops_run_legacy_pacman_operation() { local action="$1" shift case "$action" in upgrade-install) retry_interactive_log_only sudo pacman -Syu --needed --noconfirm "$@" ;; upgrade-full) retry_interactive_log_only sudo pacman -Syu --noconfirm ;; install-needed) retry_interactive_log_only sudo pacman -S --needed --noconfirm "$@" ;; remove-recursive) retry_interactive_log_only sudo pacman -Rns --noconfirm "$@" ;; refresh-databases) run_interactive_log_only sudo pacman -Syy --noconfirm ;; *) return 1 ;; esac } ops_pacman_operation_executor() { local operation_id="$1" local action="${OPS_PACMAN_OPERATION_ACTIONS[$operation_id]:-}" local packages_text="${OPS_PACMAN_OPERATION_PACKAGES[$operation_id]:-}" local packages=() if [[ -n "$packages_text" ]]; then read -r -a packages <<<"$packages_text" fi ops_run_legacy_pacman_operation "$action" "${packages[@]}" } ops_plan_pacman_operation() { local action="$1" local description="$2" local command_line="$3" local target="$4" shift 4 local packages=("$@") local packages_label="${packages[*]:-sistema}" local operation_id operation_id="pacman-$action:$(ops_sanitize_id "$packages_label")" OPS_PACMAN_OPERATION_ACTIONS["$operation_id"]="$action" OPS_PACMAN_OPERATION_PACKAGES["$operation_id"]="${packages[*]}" operation_plan_add \ "$operation_id" \ "pacman" \ "$target" \ "$description" \ "high" \ "" \ "allow" \ "ops_pacman_operation_executor" \ "reexecutar pacman após resolver a causa da falha" operation_plan_set_command "$operation_id" "$command_line" "1" if ops_block_planned_package_operation_if_locked "$operation_id"; then return 2 fi operation_plan_execute "$operation_id" } ops_pacman_upgrade_and_install_needed() { local packages=("$@") local target_label="${packages[*]:-sistema}" if ! ops_package_planner_available; then ops_run_legacy_pacman_operation upgrade-install "${packages[@]}" return $? fi ops_plan_pacman_operation \ "upgrade-install" \ "Instalar pacotes oficiais necessários após atualização" \ "$(ops_command_line sudo pacman -Syu --needed --noconfirm "${packages[@]}")" \ "$target_label" \ "${packages[@]}" } ops_pacman_upgrade_full() { if ! ops_package_planner_available; then ops_run_legacy_pacman_operation upgrade-full return $? fi ops_plan_pacman_operation \ "upgrade-full" \ "Atualizar sistema com pacman" \ "$(ops_command_line sudo pacman -Syu --noconfirm)" \ "sistema" } ops_pacman_install_needed() { local packages=("$@") local target_label="${packages[*]:-pacotes não informados}" if ! ops_package_planner_available; then ops_run_legacy_pacman_operation install-needed "${packages[@]}" return $? fi ops_plan_pacman_operation \ "install-needed" \ "Instalar pacotes oficiais necessários" \ "$(ops_command_line sudo pacman -S --needed --noconfirm "${packages[@]}")" \ "$target_label" \ "${packages[@]}" } ops_pacman_remove_recursive() { local packages=("$@") local target_label="${packages[*]:-pacotes não informados}" if ! ops_package_planner_available; then ops_run_legacy_pacman_operation remove-recursive "${packages[@]}" return $? fi ops_plan_pacman_operation \ "remove-recursive" \ "Remover pacotes oficiais recursivamente" \ "$(ops_command_line sudo pacman -Rns --noconfirm "${packages[@]}")" \ "$target_label" \ "${packages[@]}" } ops_pacman_refresh_databases() { if ! ops_package_planner_available; then ops_run_legacy_pacman_operation refresh-databases return $? fi ops_plan_pacman_operation \ "refresh-databases" \ "Atualizar bancos de dados do pacman" \ "$(ops_command_line sudo pacman -Syy --noconfirm)" \ "bancos de dados do pacman" } 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" local operation_id if ! ops_package_planner_available; then retry_log_only build_yay "$yay_dir" return $? fi operation_id="aur-build-yay:$(ops_sanitize_id "$yay_dir")" OPS_YAY_BUILD_OPERATION_DIRS["$operation_id"]="$yay_dir" operation_plan_add \ "$operation_id" \ "aur" \ "$yay_dir" \ "Compilar e instalar helper AUR yay" \ "high" \ "" \ "allow" \ "ops_build_yay_package_executor" \ "remover diretório temporário e repetir build se necessário" operation_plan_set_command "$operation_id" "$(ops_command_line build_yay "$yay_dir")" "0" if ops_block_planned_package_operation_if_locked "$operation_id"; then return 2 fi operation_plan_execute "$operation_id" } ops_aur_install_needed() { local helper_name="$1" shift local packages=("$@") local target_label="${packages[*]:-pacotes AUR não informados}" local operation_id if ! ops_package_planner_available; then retry_log_only "$helper_name" -S --needed --noconfirm "${packages[@]}" return $? fi operation_id="aur-install-needed:$(ops_sanitize_id "$helper_name:${packages[*]}")" OPS_AUR_OPERATION_HELPERS["$operation_id"]="$helper_name" OPS_AUR_OPERATION_PACKAGES["$operation_id"]="${packages[*]}" operation_plan_add \ "$operation_id" \ "aur" \ "$target_label" \ "Instalar pacotes AUR necessários com $helper_name" \ "high" \ "" \ "allow" \ "ops_aur_install_needed_executor" \ "resolver falha do helper AUR e repetir instalação" operation_plan_set_command "$operation_id" "$(ops_command_line "$helper_name" -S --needed --noconfirm "${packages[@]}")" "0" if ops_block_planned_package_operation_if_locked "$operation_id"; then return 2 fi operation_plan_execute "$operation_id" } ops_build_yay_package_executor() { local operation_id="$1" local yay_dir="${OPS_YAY_BUILD_OPERATION_DIRS[$operation_id]:-}" retry_log_only build_yay "$yay_dir" } ops_aur_install_needed_executor() { local operation_id="$1" local helper_name="${OPS_AUR_OPERATION_HELPERS[$operation_id]:-}" local packages_text="${OPS_AUR_OPERATION_PACKAGES[$operation_id]:-}" local packages=() if [[ -n "$packages_text" ]]; then read -r -a packages <<<"$packages_text" fi retry_log_only "$helper_name" -S --needed --noconfirm "${packages[@]}" } # --- 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" local member_path="" tar -tzf "$archive_path" >/dev/null || return 1 while IFS= read -r member_path; do case "$member_path" in ""|/*|..|../*|*/..|*/../*) announce_error "Arquivo tar contém caminho inseguro: $member_path" return 1 ;; esac done < <(tar -tzf "$archive_path") tar -xzf "$archive_path" -C "$destination_dir" } # --- end: scripts/lib/ops/filesystem.sh --- # --- begin: scripts/lib/ops/git.sh --- # shellcheck shell=bash declare -Ag OPS_GIT_OPERATION_ACTIONS=() declare -Ag OPS_GIT_OPERATION_REPO_DIRS=() declare -Ag OPS_GIT_OPERATION_ORIGINS=() declare -Ag OPS_GIT_OPERATION_SUBMODULES=() ops_git_planner_available() { declare -F operation_plan_add >/dev/null && declare -F operation_plan_execute >/dev/null } ops_run_legacy_git_operation() { local action="$1" local repo_dir="$2" local origin_url="${3:-}" local clone_submodules="${4:-0}" local clone_args=(clone --branch main --single-branch) case "$action" in remote-add-origin) git -C "$repo_dir" remote add origin "$origin_url" ;; remote-set-origin) git -C "$repo_dir" remote set-url origin "$origin_url" ;; fetch-origin) retry_log_only git -C "$repo_dir" fetch origin ;; checkout-main) run_log_only git -C "$repo_dir" checkout main ;; checkout-main-from-origin) run_log_only git -C "$repo_dir" checkout -b main origin/main ;; pull-main-ff-only) retry_log_only git -C "$repo_dir" pull --ff-only origin main ;; update-submodules) retry_log_only git -C "$repo_dir" submodule update --init --recursive ;; clone-main) if [[ "$clone_submodules" == "1" ]]; then clone_args+=(--recurse-submodules) fi clone_args+=("$origin_url" "$repo_dir") retry_log_only git "${clone_args[@]}" ;; *) return 1 ;; esac } ops_git_operation_executor() { local operation_id="$1" local action="${OPS_GIT_OPERATION_ACTIONS[$operation_id]:-}" local repo_dir="${OPS_GIT_OPERATION_REPO_DIRS[$operation_id]:-}" local origin_url="${OPS_GIT_OPERATION_ORIGINS[$operation_id]:-}" local clone_submodules="${OPS_GIT_OPERATION_SUBMODULES[$operation_id]:-0}" ops_run_legacy_git_operation "$action" "$repo_dir" "$origin_url" "$clone_submodules" } ops_plan_git_operation() { local action="$1" local repo_dir="$2" local origin_url="$3" local clone_submodules="$4" local description="$5" local command_line="$6" local target="$7" local operation_id operation_id="git-$action:$(ops_sanitize_id "$target")" OPS_GIT_OPERATION_ACTIONS["$operation_id"]="$action" OPS_GIT_OPERATION_REPO_DIRS["$operation_id"]="$repo_dir" OPS_GIT_OPERATION_ORIGINS["$operation_id"]="$origin_url" OPS_GIT_OPERATION_SUBMODULES["$operation_id"]="$clone_submodules" operation_plan_add \ "$operation_id" \ "git" \ "$target" \ "$description" \ "medium" \ "" \ "allow" \ "ops_git_operation_executor" \ "revisar o repositório local e repetir o comando git se necessário" operation_plan_set_command "$operation_id" "$command_line" "0" operation_plan_execute "$operation_id" } ops_git_remote_add_origin() { local repo_dir="$1" local origin_url="$2" if ! ops_git_planner_available; then ops_run_legacy_git_operation remote-add-origin "$repo_dir" "$origin_url" return $? fi ops_plan_git_operation \ "remote-add-origin" \ "$repo_dir" \ "$origin_url" \ "0" \ "Adicionar origin git" \ "$(ops_command_line git -C "$repo_dir" remote add origin "$origin_url")" \ "$repo_dir <- $origin_url" } ops_git_remote_set_origin() { local repo_dir="$1" local origin_url="$2" if ! ops_git_planner_available; then ops_run_legacy_git_operation remote-set-origin "$repo_dir" "$origin_url" return $? fi ops_plan_git_operation \ "remote-set-origin" \ "$repo_dir" \ "$origin_url" \ "0" \ "Atualizar origin git" \ "$(ops_command_line git -C "$repo_dir" remote set-url origin "$origin_url")" \ "$repo_dir <- $origin_url" } ops_git_fetch_origin() { local repo_dir="$1" if ! ops_git_planner_available; then ops_run_legacy_git_operation fetch-origin "$repo_dir" return $? fi ops_plan_git_operation \ "fetch-origin" \ "$repo_dir" \ "" \ "0" \ "Buscar atualizações de origin" \ "$(ops_command_line git -C "$repo_dir" fetch origin)" \ "$repo_dir" } ops_git_checkout_main() { local repo_dir="$1" if ! ops_git_planner_available; then ops_run_legacy_git_operation checkout-main "$repo_dir" return $? fi ops_plan_git_operation \ "checkout-main" \ "$repo_dir" \ "" \ "0" \ "Trocar repositório para main" \ "$(ops_command_line git -C "$repo_dir" checkout main)" \ "$repo_dir" } ops_git_checkout_main_from_origin() { local repo_dir="$1" if ! ops_git_planner_available; then ops_run_legacy_git_operation checkout-main-from-origin "$repo_dir" return $? fi ops_plan_git_operation \ "checkout-main-from-origin" \ "$repo_dir" \ "" \ "0" \ "Criar main local a partir de origin/main" \ "$(ops_command_line git -C "$repo_dir" checkout -b main origin/main)" \ "$repo_dir" } ops_git_pull_main_ff_only() { local repo_dir="$1" if ! ops_git_planner_available; then ops_run_legacy_git_operation pull-main-ff-only "$repo_dir" return $? fi ops_plan_git_operation \ "pull-main-ff-only" \ "$repo_dir" \ "" \ "0" \ "Atualizar main com fast-forward" \ "$(ops_command_line git -C "$repo_dir" pull --ff-only origin main)" \ "$repo_dir" } ops_git_update_submodules() { local repo_dir="$1" if ! ops_git_planner_available; then ops_run_legacy_git_operation update-submodules "$repo_dir" return $? fi ops_plan_git_operation \ "update-submodules" \ "$repo_dir" \ "" \ "0" \ "Atualizar submódulos git" \ "$(ops_command_line git -C "$repo_dir" submodule update --init --recursive)" \ "$repo_dir" } 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") if ! ops_git_planner_available; then ops_run_legacy_git_operation clone-main "$repo_dir" "$repo_url" "$clone_submodules" return $? fi ops_plan_git_operation \ "clone-main" \ "$repo_dir" \ "$repo_url" \ "$clone_submodules" \ "Clonar repositório git" \ "$(ops_command_line git "${clone_args[@]}")" \ "$repo_dir <- $repo_url" } # --- end: scripts/lib/ops/git.sh --- # --- begin: scripts/lib/ops/github.sh --- # shellcheck shell=bash ops_github_planner_available() { declare -F operation_plan_add >/dev/null && declare -F operation_plan_execute >/dev/null } ops_github_record_direct_operation() { local operation_id="$1" local target="$2" local description="$3" local command_line="$4" local risk="${5:-medium}" ops_github_planner_available || return 1 operation_plan_add \ "$operation_id" \ "github" \ "$target" \ "$description" \ "$risk" \ "" \ "allow" \ "" \ "repetir comando gh após resolver autenticação ou conectividade" || return 1 operation_plan_set_command "$operation_id" "$command_line" "0" if [[ "${DRY_RUN:-0}" == "1" ]]; then operation_plan_set_status "$operation_id" "planned" "dry-run" return 2 fi return 0 } ops_gh_auth_login() { local operation_id="github-auth-login:github.com" if ops_github_record_direct_operation \ "$operation_id" \ "github.com" \ "Autenticar GitHub CLI" \ "$(ops_command_line gh auth login --web --git-protocol ssh --scopes admin:public_key)" \ "medium"; then : elif [[ "$?" == "2" ]]; then return 2 fi run_gh_auth_flow auth login --web --git-protocol ssh --scopes admin:public_key local status=$? if ops_github_planner_available; then if [[ "$status" == "0" ]]; then operation_plan_set_status "$operation_id" "succeeded" "executada" else operation_plan_set_status "$operation_id" "failed" "falha na execução" fi fi return "$status" } ops_gh_auth_refresh_admin_public_key() { local operation_id="github-auth-refresh:admin-public-key" if ops_github_record_direct_operation \ "$operation_id" \ "github.com admin:public_key" \ "Renovar escopo GitHub CLI" \ "$(ops_command_line gh auth refresh -h github.com -s admin:public_key)" \ "medium"; then : elif [[ "$?" == "2" ]]; then return 2 fi run_gh_auth_flow auth refresh -h github.com -s admin:public_key local status=$? if ops_github_planner_available; then if [[ "$status" == "0" ]]; then operation_plan_set_status "$operation_id" "succeeded" "executada" else operation_plan_set_status "$operation_id" "failed" "falha na execução" fi fi return "$status" } ops_gh_get_authenticated_login() { local operation_id="github-api-user-login" if ops_github_record_direct_operation \ "$operation_id" \ "user" \ "Consultar login autenticado no GitHub" \ "$(ops_command_line gh api user --jq '.login')" \ "low"; then : elif [[ "$?" == "2" ]]; then return 2 fi retry gh api user --jq '.login' local status=$? if ops_github_planner_available; then if [[ "$status" == "0" ]]; then operation_plan_set_status "$operation_id" "succeeded" "executada" else operation_plan_set_status "$operation_id" "failed" "falha na execução" fi fi return "$status" } ops_gh_list_ssh_keys_tsv() { local operation_id="github-api-list-ssh-keys" if ops_github_record_direct_operation \ "$operation_id" \ "user/keys" \ "Listar chaves SSH do GitHub" \ "$(ops_command_line gh api user/keys --jq '.[] | [.id, .title, .key] | @tsv')" \ "medium"; then : elif [[ "$?" == "2" ]]; then return 2 fi retry gh api user/keys --jq '.[] | [.id, .title, .key] | @tsv' local status=$? if ops_github_planner_available; then if [[ "$status" == "0" ]]; then operation_plan_set_status "$operation_id" "succeeded" "executada" else operation_plan_set_status "$operation_id" "failed" "falha na execução" fi fi return "$status" } ops_gh_delete_ssh_key() { local key_id="$1" local operation_id="" operation_id="github-api-delete-ssh-key:$(ops_sanitize_id "$key_id")" if ops_github_record_direct_operation \ "$operation_id" \ "user/keys/$key_id" \ "Remover chave SSH do GitHub" \ "$(ops_command_line gh api --method DELETE "user/keys/$key_id")" \ "high"; then : elif [[ "$?" == "2" ]]; then return 2 fi retry gh api --method DELETE "user/keys/$key_id" local status=$? if ops_github_planner_available; then if [[ "$status" == "0" ]]; then operation_plan_set_status "$operation_id" "succeeded" "executada" else operation_plan_set_status "$operation_id" "failed" "falha na execução" fi fi return "$status" } ops_gh_create_ssh_key() { local key_name="$1" local public_key="$2" local operation_id="" operation_id="github-api-create-ssh-key:$(ops_sanitize_id "$key_name")" if ops_github_record_direct_operation \ "$operation_id" \ "user/keys:$key_name" \ "Enviar chave SSH ao GitHub" \ "$(ops_command_line gh api user/keys --method POST -f "title=$key_name" -f "key=" --jq '.id')" \ "medium"; then : elif [[ "$?" == "2" ]]; then return 2 fi retry gh api user/keys --method POST -f "title=$key_name" -f "key=$public_key" --jq '.id' local status=$? if ops_github_planner_available; then if [[ "$status" == "0" ]]; then operation_plan_set_status "$operation_id" "succeeded" "executada" else operation_plan_set_status "$operation_id" "failed" "falha na execução" fi fi return "$status" } # --- end: scripts/lib/ops/github.sh --- # --- begin: scripts/lib/ops/node.sh --- # shellcheck shell=bash declare -Ag OPS_NODE_OPERATION_ACTIONS=() declare -Ag OPS_NODE_OPERATION_PREFIXES=() ops_node_planner_available() { declare -F operation_plan_add >/dev/null && declare -F operation_plan_execute >/dev/null } ops_run_legacy_node_operation() { local action="$1" local prefix_path="${2:-}" case "$action" in npm-config-prefix) run_log_only npm config set prefix "$prefix_path" ;; npm-install-codex) retry_log_only npm install -g @openai/codex ;; *) return 1 ;; esac } ops_node_operation_executor() { local operation_id="$1" local action="${OPS_NODE_OPERATION_ACTIONS[$operation_id]:-}" local prefix_path="${OPS_NODE_OPERATION_PREFIXES[$operation_id]:-}" ops_run_legacy_node_operation "$action" "$prefix_path" } ops_plan_node_operation() { local action="$1" local target="$2" local description="$3" local command_line="$4" local prefix_path="${5:-}" local operation_id operation_id="node-$action:$(ops_sanitize_id "$target")" OPS_NODE_OPERATION_ACTIONS["$operation_id"]="$action" OPS_NODE_OPERATION_PREFIXES["$operation_id"]="$prefix_path" operation_plan_add \ "$operation_id" \ "npm" \ "$target" \ "$description" \ "medium" \ "" \ "allow" \ "ops_node_operation_executor" \ "remover ou ajustar prefixo npm/Codex manualmente se necessário" operation_plan_set_command "$operation_id" "$command_line" "0" operation_plan_execute "$operation_id" } ops_npm_config_set_prefix() { local prefix_path="$1" if ! ops_node_planner_available; then ops_run_legacy_node_operation npm-config-prefix "$prefix_path" return $? fi ops_plan_node_operation \ "npm-config-prefix" \ "$prefix_path" \ "Configurar prefixo npm para Codex" \ "$(ops_command_line npm config set prefix "$prefix_path")" \ "$prefix_path" } ops_npm_install_codex_cli() { if ! ops_node_planner_available; then ops_run_legacy_node_operation npm-install-codex return $? fi ops_plan_node_operation \ "npm-install-codex" \ "@openai/codex" \ "Instalar Codex CLI via npm" \ "$(ops_command_line npm install -g @openai/codex)" } # --- end: scripts/lib/ops/node.sh --- # --- begin: scripts/lib/ops/ssh.sh --- # shellcheck shell=bash declare -Ag OPS_SSH_OPERATION_ACTIONS=() declare -Ag OPS_SSH_OPERATION_KEY_PATHS=() declare -Ag OPS_SSH_OPERATION_PUBLIC_PATHS=() declare -Ag OPS_SSH_OPERATION_COMMENTS=() ops_ssh_planner_available() { declare -F operation_plan_add >/dev/null && declare -F operation_plan_execute >/dev/null } ops_run_legacy_ssh_operation() { local action="$1" local private_key_path="$2" local public_key_path="${3:-}" local key_comment="${4:-}" case "$action" in regenerate-public-key) ssh-keygen -y -f "$private_key_path" >"$public_key_path" ;; generate-key-pair) ssh-keygen -t ed25519 -C "$key_comment" -f "$private_key_path" -N "" ;; *) return 1 ;; esac } ops_ssh_operation_executor() { local operation_id="$1" local action="${OPS_SSH_OPERATION_ACTIONS[$operation_id]:-}" local private_key_path="${OPS_SSH_OPERATION_KEY_PATHS[$operation_id]:-}" local public_key_path="${OPS_SSH_OPERATION_PUBLIC_PATHS[$operation_id]:-}" local key_comment="${OPS_SSH_OPERATION_COMMENTS[$operation_id]:-}" ops_run_legacy_ssh_operation "$action" "$private_key_path" "$public_key_path" "$key_comment" } ops_plan_ssh_operation() { local action="$1" local private_key_path="$2" local public_key_path="$3" local key_comment="$4" local description="$5" local command_line="$6" local target="$7" local operation_id operation_id="ssh-$action:$(ops_sanitize_id "$target")" OPS_SSH_OPERATION_ACTIONS["$operation_id"]="$action" OPS_SSH_OPERATION_KEY_PATHS["$operation_id"]="$private_key_path" OPS_SSH_OPERATION_PUBLIC_PATHS["$operation_id"]="$public_key_path" OPS_SSH_OPERATION_COMMENTS["$operation_id"]="$key_comment" operation_plan_add \ "$operation_id" \ "ssh" \ "$target" \ "$description" \ "medium" \ "" \ "allow" \ "ops_ssh_operation_executor" \ "remover chave gerada manualmente se necessário" operation_plan_set_command "$operation_id" "$command_line" "0" operation_plan_execute "$operation_id" } ops_ssh_regenerate_public_key() { local private_key_path="$1" local public_key_path="$2" if ! ops_ssh_planner_available; then ops_run_legacy_ssh_operation regenerate-public-key "$private_key_path" "$public_key_path" return $? fi ops_plan_ssh_operation \ "regenerate-public-key" \ "$private_key_path" \ "$public_key_path" \ "" \ "Recriar chave pública SSH local" \ "$(ops_command_line ssh-keygen -y -f "$private_key_path" '>' "$public_key_path")" \ "$public_key_path" } ops_ssh_generate_key_pair() { local key_comment="$1" local key_path="$2" if ! ops_ssh_planner_available; then ops_run_legacy_ssh_operation generate-key-pair "$key_path" "" "$key_comment" return $? fi ops_plan_ssh_operation \ "generate-key-pair" \ "$key_path" \ "${key_path}.pub" \ "$key_comment" \ "Gerar par de chaves SSH local" \ "$(ops_command_line ssh-keygen -t ed25519 -C "$key_comment" -f "$key_path" -N "")" \ "$key_path" } # --- end: scripts/lib/ops/ssh.sh --- # --- begin: scripts/lib/ops/systemd.sh --- # shellcheck shell=bash declare -Ag OPS_SYSTEMD_OPERATION_SCOPES=() declare -Ag OPS_SYSTEMD_OPERATION_ACTIONS=() declare -Ag OPS_SYSTEMD_OPERATION_UNITS=() ops_systemd_planner_available() { declare -F operation_plan_add >/dev/null && declare -F operation_plan_execute >/dev/null } ops_run_legacy_systemd_operation() { local scope="$1" local action="$2" shift 2 case "$scope:$action" in user:daemon-reload) run_log_only systemctl --user daemon-reload ;; user:start) run_log_only systemctl --user start "$@" ;; system:start) run_log_only sudo systemctl start "$@" ;; system:enable) run_log_only sudo systemctl enable "$@" ;; *) return 1 ;; esac } ops_systemd_operation_executor() { local operation_id="$1" local scope="${OPS_SYSTEMD_OPERATION_SCOPES[$operation_id]:-}" local action="${OPS_SYSTEMD_OPERATION_ACTIONS[$operation_id]:-}" local units_text="${OPS_SYSTEMD_OPERATION_UNITS[$operation_id]:-}" local units=() if [[ -n "$units_text" ]]; then read -r -a units <<<"$units_text" fi ops_run_legacy_systemd_operation "$scope" "$action" "${units[@]}" } ops_plan_systemd_operation() { local scope="$1" local action="$2" local description="$3" local command_line="$4" local requires_root="$5" shift 5 local units=("$@") local units_label="${units[*]:-daemon-reload}" local operation_id operation_id="systemd-$scope-$action:$(ops_sanitize_id "$units_label")" OPS_SYSTEMD_OPERATION_SCOPES["$operation_id"]="$scope" OPS_SYSTEMD_OPERATION_ACTIONS["$operation_id"]="$action" OPS_SYSTEMD_OPERATION_UNITS["$operation_id"]="${units[*]}" operation_plan_add \ "$operation_id" \ "systemd" \ "$units_label" \ "$description" \ "medium" \ "" \ "allow" \ "ops_systemd_operation_executor" \ "verificar unidade e repetir systemctl manualmente" operation_plan_set_command "$operation_id" "$command_line" "$requires_root" operation_plan_execute "$operation_id" } ops_systemctl_user_daemon_reload() { if ! ops_systemd_planner_available; then ops_run_legacy_systemd_operation user daemon-reload return $? fi ops_plan_systemd_operation \ "user" \ "daemon-reload" \ "Recarregar unidades systemd do usuário" \ "$(ops_command_line systemctl --user daemon-reload)" \ "0" } ops_systemctl_user_start() { local units=("$@") if ! ops_systemd_planner_available; then ops_run_legacy_systemd_operation user start "${units[@]}" return $? fi ops_plan_systemd_operation \ "user" \ "start" \ "Iniciar unidades systemd do usuário" \ "$(ops_command_line systemctl --user start "${units[@]}")" \ "0" \ "${units[@]}" } ops_systemctl_start() { local units=("$@") if ! ops_systemd_planner_available; then ops_run_legacy_systemd_operation system start "${units[@]}" return $? fi ops_plan_systemd_operation \ "system" \ "start" \ "Iniciar unidades systemd do sistema" \ "$(ops_command_line sudo systemctl start "${units[@]}")" \ "1" \ "${units[@]}" } ops_systemctl_enable() { local units=("$@") if ! ops_systemd_planner_available; then ops_run_legacy_systemd_operation system enable "${units[@]}" return $? fi ops_plan_systemd_operation \ "system" \ "enable" \ "Habilitar unidades systemd do sistema" \ "$(ops_command_line sudo systemctl enable "${units[@]}")" \ "1" \ "${units[@]}" } # --- 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 ---