From ed0de4b2d9aee9aeb30deac83fcd452a0015f036 Mon Sep 17 00:00:00 2001 From: zenaku Date: Thu, 4 Jun 2026 07:38:55 -0500 Subject: [PATCH] Verify Sing-box before the connection --- core/Errors.py | 52 +-------- core/controllers/ConnectionController.py | 27 ++++- install.sh | 98 ---------------- lib/input.sh | 32 ------ lib/log.sh | 52 --------- lib/network.sh | 53 --------- lib/steps.sh | 57 --------- lib/steps/preflight.sh | 45 -------- lib/steps/python_env.sh | 67 ----------- lib/steps/repos.sh | 88 -------------- lib/steps/singbox.sh | 74 ------------ lib/steps/sudoers.sh | 69 ----------- lib/steps/wrapper.sh | 140 ----------------------- lib/ui.sh | 114 ------------------ 14 files changed, 27 insertions(+), 941 deletions(-) delete mode 100755 install.sh delete mode 100755 lib/input.sh delete mode 100755 lib/log.sh delete mode 100755 lib/network.sh delete mode 100755 lib/steps.sh delete mode 100644 lib/steps/preflight.sh delete mode 100644 lib/steps/python_env.sh delete mode 100644 lib/steps/repos.sh delete mode 100644 lib/steps/singbox.sh delete mode 100644 lib/steps/sudoers.sh delete mode 100644 lib/steps/wrapper.sh delete mode 100755 lib/ui.sh diff --git a/core/Errors.py b/core/Errors.py index cdbd85d..a0e6f1b 100644 --- a/core/Errors.py +++ b/core/Errors.py @@ -1,102 +1,54 @@ class CommandNotFoundError(OSError): - def __init__(self, subject): - self.subject = subject super().__init__(f"Command '{subject}' could not be found.") - - class UnknownClientPathError(Exception): pass - - class UnknownClientVersionError(Exception): pass - - class UnknownConnectionTypeError(Exception): pass - - class UnknownTimeZoneError(Exception): pass - - class ConnectionTerminationError(Exception): pass - - class PolicyAssignmentError(Exception): pass - - class PolicyInstatementError(Exception): pass - - class PolicyRevocationError(Exception): pass - - class ProfileDeletionError(Exception): pass - - class ProfileModificationError(Exception): pass - - class ProfileStateConflictError(Exception): pass - - class ProfileActivationError(Exception): pass - - class ProfileDeactivationError(Exception): pass - - class UnsupportedApplicationVersionError(Exception): pass - - class ApplicationAlreadyInstalledError(Exception): pass - - class MissingLocationError(Exception): pass - - class MissingSubscriptionError(Exception): pass - - class InvalidSubscriptionError(Exception): pass - - class InvoiceNotFoundError(Exception): pass - - class InvoiceExpiredError(Exception): pass - - class InvoicePaymentFailedError(Exception): pass - - class ConnectionUnprotectedError(Exception): pass - - class FileIntegrityError(Exception): pass - - class EndpointVerificationError(Exception): pass +class SingboxNotInstalledException(Exception): + pass \ No newline at end of file diff --git a/core/controllers/ConnectionController.py b/core/controllers/ConnectionController.py index f8ffef4..ae64a83 100644 --- a/core/controllers/ConnectionController.py +++ b/core/controllers/ConnectionController.py @@ -44,9 +44,10 @@ class ConnectionController: @staticmethod def establish_connection(profile: Union[SessionProfile, SystemProfile], ignore: tuple[type[Exception]] = (), connection_observer: Optional[ConnectionObserver] = None): - from core.controllers.ConnectionController import ConnectionController - + + ConnectionController.verify_singbox_installation() + connection = profile.connection if connection.needs_proxy_configuration() and not profile.has_proxy_configuration(): @@ -543,3 +544,25 @@ class ConnectionController: return True return False + + @staticmethod + def verify_singbox_installation(): + import subprocess + import os + from core.Errors import SingboxNotInstalledException + + singbox_binary = '/usr/bin/sing-box' + singbox_wrapper = '/usr/local/bin/hydraveil-singbox' + + if not os.path.exists(singbox_binary): + raise SingboxNotInstalledException(f'sing-box binary not found at {singbox_binary}') + + if not os.path.exists(singbox_wrapper): + raise SingboxNotInstalledException(f'sing-box wrapper not found at {singbox_wrapper}') + + try: + subprocess.run([singbox_binary, 'version'], capture_output=True, timeout=5, check=True) + except subprocess.CalledProcessError: + raise SingboxNotInstalledException('sing-box binary check failed') + except subprocess.TimeoutExpired: + raise SingboxNotInstalledException('sing-box verification timeout') \ No newline at end of file diff --git a/install.sh b/install.sh deleted file mode 100755 index e90c2aa..0000000 --- a/install.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash -# install.sh — hydra-veil core installer - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -LIB_DIR="$SCRIPT_DIR/lib" - -trap 'echo ""; echo " [ERROR] Installer failed at line $LINENO — check: cat $LOG_FILE" >&2' ERR - -for _lib in ui log input network steps; do - _path="$LIB_DIR/${_lib}.sh" - if [ ! -f "$_path" ]; then - echo "[ERROR] Missing lib: $_path" >&2 - exit 1 - fi - source "$_path" -done - -for arg in "$@"; do - case "$arg" in - --no-log) LOG_ENABLED="false" ;; - esac -done - -_log_init - -clear -print_header "HYDRA-VEIL CORE INSTALLER" "v2.0.0" -printf " ${DIM}%s${RESET}\n" "Simplified Privacy" -printf " ${DIM}%s${RESET}\n" "$(date '+%Y-%m-%d %H:%M:%S') — $(uname -n)" -print_divider - -print_section "Pre-flight" -log_info "User: ${USER}" -log_info "Home: ${HOME}" -log_info "Install dir: ${HV_BASE}" -require_internet -print_divider - -echo "" -label_info "hydra-veil se instalará en:" -printf " ${BCYAN}%s${RESET}\n\n" "$HV_BASE" -label_warn "sudo es requerido para sing-box, wrapper y sudoers." -echo "" -if ! ask_confirm "Continuar con la instalación?"; then - log_warn "Instalación cancelada." - exit 0 -fi -echo "" -print_divider - -step_preflight -step_repos -step_python_env -step_singbox -step_wrapper -step_sudoers - -echo "" -print_divider -printf "${BGREEN}" -cat << 'EOF' - _ - _ _|_| _ - | \_ _| | _/ | - | \__|___|__/ | - | | _ _ | | - | || | | || | - \_ || | | || _/ - \_||_| |_||_/ - ______| |______ - _/ \_ - _/ S I M P L I F I E D \_ - _/ P R I V A C Y \_ - | _____ _____ | - | _/ |_ _| \_ | - |_/ |_ _| \_| - \_____/ - -EOF -printf "${RESET}" -print_divider -echo "" -label_ok "hydra-veil instalado correctamente" -label_ok "Todos los steps completados" -echo "" -label_info "Activar entorno:" -printf " ${CYAN}source %s/core/.venv/bin/activate${RESET}\n" "$HV_BASE" -echo "" -label_info "CLI disponible:" -printf " ${CYAN}python -m cli${RESET}\n" -echo "" -label_info "Wrapper disponible:" -printf " ${CYAN}sudo /usr/local/bin/hydraveil-singbox start${RESET}\n" -echo "" -log_file_path -echo "" -print_divider -echo "" \ No newline at end of file diff --git a/lib/input.sh b/lib/input.sh deleted file mode 100755 index bd80153..0000000 --- a/lib/input.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -ask_input() { - local label="$1" - local default="${2:-}" - local prompt_default="" - [ -n "$default" ] && prompt_default=" ${DIM}[${default}]${RESET}" - - printf " ${BCYAN}?${RESET} ${BWHITE}%s${RESET}%b " "$label" "$prompt_default" - read -r INPUT_RESULT - [ -z "$INPUT_RESULT" ] && INPUT_RESULT="$default" -} - -ask_confirm() { - local label="$1" - local default="${2:-y}" - local hint - if [ "$default" = "y" ]; then hint="${BGREEN}Y${RESET}${DIM}/n${RESET}"; else hint="${DIM}y/${RESET}${BRED}N${RESET}"; fi - - printf " ${BYELLOW}?${RESET} ${BWHITE}%s${RESET} [%b] " "$label" "$hint" - read -r _confirm - _confirm="${_confirm:-$default}" - case "${_confirm,,}" in - y|yes) return 0 ;; - *) return 1 ;; - esac -} - -ask_pause() { - local msg="${1:-Press ENTER to continue...}" - printf " ${DIM}%s${RESET}" "$msg" - read -r -} diff --git a/lib/log.sh b/lib/log.sh deleted file mode 100755 index 6609658..0000000 --- a/lib/log.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -LOG_FILE="${LOG_FILE:-/tmp/hydra-veil-core.log}" -LOG_ENABLED="${LOG_ENABLED:-true}" - -_log_init() { - if [ "$LOG_ENABLED" = "true" ]; then - mkdir -p "$(dirname "$LOG_FILE")" - echo "──────────────────────────────────────────" >> "$LOG_FILE" - echo " LOG START: $(date '+%Y-%m-%d %H:%M:%S')" >> "$LOG_FILE" - echo "──────────────────────────────────────────" >> "$LOG_FILE" - fi -} - -_log_write() { - local level="$1" - local msg="$2" - if [ "$LOG_ENABLED" = "true" ]; then - printf "[%s] [%s] %s\n" "$(date '+%H:%M:%S')" "$level" "$msg" >> "$LOG_FILE" - fi -} - -log_info() { - label_info "$1" - _log_write "INFO " "$1" -} - -log_ok() { - label_ok "$1" - _log_write "OK " "$1" -} - -log_warn() { - label_warn "$1" - _log_write "WARN " "$1" -} - -log_error() { - label_error "$1" - _log_write "ERROR" "$1" -} - -log_step() { - local msg="$1" - local current="$2" - local total="$3" - label_step "$msg" "$current" "$total" - _log_write "STEP " "[$current/$total] $msg" -} - -log_file_path() { - printf " ${DIM}Log file: ${CYAN}%s${RESET}\n" "$LOG_FILE" -} diff --git a/lib/network.sh b/lib/network.sh deleted file mode 100755 index 776ca4f..0000000 --- a/lib/network.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -NET_CHECK_HOST="${NET_CHECK_HOST:-8.8.8.8}" -NET_CHECK_TIMEOUT="${NET_CHECK_TIMEOUT:-3}" - -check_internet() { - local host="${1:-$NET_CHECK_HOST}" - if ping -c 1 -W "$NET_CHECK_TIMEOUT" "$host" &>/dev/null; then - label_connected "Internet reachable ${DIM}(${host})${RESET}" - _log_write "NET " "Connected: $host" - return 0 - else - label_disconnect "No internet connection ${DIM}(${host})${RESET}" - _log_write "NET " "Disconnected: $host" - return 1 - fi -} - -check_host() { - local host="$1" - local label="${2:-$host}" - if ping -c 1 -W "$NET_CHECK_TIMEOUT" "$host" &>/dev/null 2>&1; then - label_connected "$label" - return 0 - else - label_disconnect "$label" - return 1 - fi -} - -check_url() { - local url="$1" - local label="${2:-$url}" - local http_code - http_code=$(curl -s -o /dev/null -w "%{http_code}" \ - --max-time "$NET_CHECK_TIMEOUT" "$url" 2>/dev/null || echo "000") - - if [[ "$http_code" =~ ^[23] ]]; then - label_connected "$label ${DIM}(HTTP ${http_code})${RESET}" - _log_write "NET " "URL OK [$http_code]: $url" - return 0 - else - label_disconnect "$label ${DIM}(HTTP ${http_code})${RESET}" - _log_write "NET " "URL FAIL [$http_code]: $url" - return 1 - fi -} - -require_internet() { - if ! check_internet; then - log_error "Internet connection required. Aborting." - exit 1 - fi -} diff --git a/lib/steps.sh b/lib/steps.sh deleted file mode 100755 index d5fe20d..0000000 --- a/lib/steps.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/bin/bash - -TOTAL_STEPS=6 - -HV_BASE="$HOME/.local/share/hydra-veil" -HV_CORE="$HV_BASE/core" -HV_CLI="$HV_BASE/cli" -HV_GUI="$HV_BASE/gui" -HV_ESSENTIALS="$HV_BASE/essentials" -HV_VENV="$HV_CORE/.venv" - -INSTALL_DIR="$HOME" -HV_DATA_DIR="$HOME/data/hydra-veil" -HV_CONFIG_DIR="$HOME/.config/hydra-veil" -HV_CACHE_DIR="$HOME/.cache/hydra-veil" -HV_STATE_DIR="$HOME/.state/hydra-veil" - -SINGBOX_BIN="/usr/bin/sing-box" -SINGBOX_WRAPPER="/usr/local/bin/hydraveil-singbox" -SINGBOX_VERSION="1.13.5" -SINGBOX_CONFIG_DIR="$HV_DATA_DIR/configs" -SINGBOX_PID_FILE="$HV_DATA_DIR/runtime/singbox.pid" -SINGBOX_LOG_FILE="$HV_DATA_DIR/runtime/singbox.log" - -SUDOERS_PATH="/etc/sudoers.d/hydraveil" - -declare -A REPO_URLS=( - ["core"]="https://git.simplifiedprivacy.com/Support/sp-hydra-veil-core" - ["cli"]="https://git.simplifiedprivacy.com/Support/sp-hydra-veil-cli" - ["essentials"]="https://git.simplifiedprivacy.com/Support/sp-essentials" - ["gui"]="https://git.simplifiedprivacy.com/Support/sp-hydra-veil-gui" -) - -declare -A REPO_BRANCHES=( - ["core"]="deploy" - ["cli"]="zenaku" - ["essentials"]="master" - ["gui"]="master" -) - -declare -A REPO_DIRS=( - ["core"]="$HV_CORE" - ["cli"]="$HV_CLI" - ["essentials"]="$HV_ESSENTIALS" - ["gui"]="$HV_GUI" -) - -_STEPS_DIR="$SCRIPT_DIR/lib/steps" - -for _step in preflight repos python_env singbox wrapper sudoers; do - _step_path="${_STEPS_DIR}/${_step}.sh" - if [ ! -f "$_step_path" ]; then - echo "[ERROR] Missing step: $_step_path" >&2 - exit 1 - fi - source "$_step_path" -done \ No newline at end of file diff --git a/lib/steps/preflight.sh b/lib/steps/preflight.sh deleted file mode 100644 index 994a044..0000000 --- a/lib/steps/preflight.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -# lib/steps/preflight.sh — verificaciones previas -step_preflight() { - print_section "Pre-flight" - log_step "System checks" 1 $TOTAL_STEPS - - _preflight_user - _preflight_python - _preflight_git -} - -_preflight_user() { - log_info "Running as: ${USER} (uid=$(id -u))" - if [ "$(id -u)" -eq 0 ]; then - log_error "No correr el installer como root" - exit 1 - fi - log_ok "User OK" -} - -_preflight_python() { - local found="" - for candidate in python3.12 python3.13; do - if command -v "$candidate" &>/dev/null; then - found="$candidate" - break - fi - done - if [ -z "$found" ]; then - log_error "Se necesita Python 3.12 o 3.13" - log_info "Arch: sudo pacman -S python312" - log_info "Ubuntu: sudo apt install python3.12" - exit 1 - fi - PYTHON_BIN="$found" - log_ok "Python: $($PYTHON_BIN --version 2>&1)" -} - -_preflight_git() { - if ! command -v git &>/dev/null; then - log_error "git no encontrado" - exit 1 - fi - log_ok "git: $(git --version)" -} \ No newline at end of file diff --git a/lib/steps/python_env.sh b/lib/steps/python_env.sh deleted file mode 100644 index 60fa912..0000000 --- a/lib/steps/python_env.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/bash -# lib/steps/python_env.sh — venv + pip install editable - -step_python_env() { - print_section "Python Environment" - log_step "Virtual environment + dependencies" 3 $TOTAL_STEPS - - _create_venv - _pip_install -} - -_create_venv() { - if [ -d "$HV_VENV" ]; then - log_warn "venv ya existe: ${HV_VENV}" - if ask_confirm "Recrear venv?"; then - rm -rf "$HV_VENV" - else - log_skip "venv existente conservado" - source "$HV_VENV/bin/activate" - return 0 - fi - fi - - start_spinner "Creando virtual environment (${PYTHON_BIN})..." - "$PYTHON_BIN" -m venv "$HV_VENV" - stop_spinner "venv creado → ${HV_VENV}" - - source "$HV_VENV/bin/activate" - - start_spinner "Actualizando pip..." - pip install --quiet --upgrade pip - stop_spinner "pip actualizado" -} - -_pip_install() { - source "$HV_VENV/bin/activate" - - if [ -f "$HV_GUI/pyproject.toml" ]; then - sed -i 's/sp-hydra-veil-core == [0-9.]*/sp-hydra-veil-core >= 2.3.0/' \ - "$HV_GUI/pyproject.toml" 2>/dev/null || true - fi - - for name in essentials core cli gui; do - local dir - case "$name" in - essentials) dir="$HV_ESSENTIALS" ;; - core) dir="$HV_CORE" ;; - cli) dir="$HV_CLI" ;; - gui) dir="$HV_GUI" ;; - esac - - if [ ! -d "$dir" ]; then - log_warn "${name} no encontrado en ${dir} — omitiendo" - continue - fi - - start_spinner "Instalando ${name} (editable)..." - if pip install --quiet -e "$dir" 2>/tmp/pip_err_${name}; then - stop_spinner "${name} instalado" - else - stop_spinner - log_error "pip install falló para ${name}:" - tail -5 /tmp/pip_err_${name} - exit 1 - fi - done -} \ No newline at end of file diff --git a/lib/steps/repos.sh b/lib/steps/repos.sh deleted file mode 100644 index 42bad4f..0000000 --- a/lib/steps/repos.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash - -step_repos() { - print_section "Repositories" - log_step "Cloning repositories & configuring .env" 2 $TOTAL_STEPS - - mkdir -p "$HV_BASE" - chmod 700 "$HV_BASE" - - _clone_repos - _setup_env -} - -_clone_repos() { - for name in essentials cli gui core; do - local url="${REPO_URLS[$name]}" - local branch="${REPO_BRANCHES[$name]}" - local dest="${REPO_DIRS[$name]}" - log_ok "HIJUEPUTA" - - if [ -d "$dest" ]; then - log_warn "Repo ya existe: ${dest}" - rm -rf "$dest" - fi - - if ! git clone --branch "$branch" "$url" "$dest" 2>&1; then - log_error "git clone FAILED: $name" - log_error " URL: $url" - log_error " Branch: $branch" - log_error " Dest: $dest" - exit 1 - fi - - chmod 700 "$dest" - log_ok "${name} clonado → ${dest}" - done -} - -_setup_env() { - local env_file="$HV_CORE/.env" - local example_file="$HV_CORE/.env.example" - - if [ ! -f "$example_file" ]; then - log_error ".env.example no encontrado: ${example_file}" - exit 1 - fi - - if [ -f "$env_file" ]; then - log_warn ".env ya existe — sobreescribiendo" - fi - - echo "" - printf " ${DIM}Fill in the variables. ENTER = leave empty.${RESET}\n" - print_divider - echo "" - - select_option \ - "APP_ENV — application environment" \ - "local development / testing" \ - "production production / real domain" - - case "$SELECT_RESULT" in - 0) _app_env="local" ;; - 1) _app_env="production" ;; - *) _app_env="production" ;; - esac - - echo "" - printf " ${BCYAN}┌─${RESET} ${BWHITE}SP_API_BASE_URL_PRODUCTION${RESET} ${DIM}e.g. https://fake.simplifiedprivacy.org/api/v1${RESET}\n" - printf " ${BCYAN}›${RESET} " - read -r _api_url < /dev/tty - - cat > "$env_file" << ENVEOF -APP_ENV=${_app_env} -SP_API_BASE_URL_LOCAL=http://simplifiedprivacy.test/api/v1 -SP_API_BASE_URL_PRODUCTION=${_api_url} -HV_CORE_HOME=${HV_CORE} -INSTALL_DIR=${HOME} -CORE_DIR=${HV_CORE} -ENVEOF - - chmod 600 "$env_file" - - log_ok "APP_ENV=${_app_env}" - [ -n "$_api_url" ] && log_ok "SP_API_BASE_URL_PRODUCTION=${_api_url}" || label_skip "SP_API_BASE_URL_PRODUCTION — vacío" - log_ok "INSTALL_DIR=${HOME}" - log_ok ".env escrito → ${env_file}" -} \ No newline at end of file diff --git a/lib/steps/singbox.sh b/lib/steps/singbox.sh deleted file mode 100644 index 5b27791..0000000 --- a/lib/steps/singbox.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/bash -# lib/steps/singbox.sh — instalar/verificar sing-box - -step_singbox() { - print_section "sing-box" - log_step "Checking sing-box binary" 4 $TOTAL_STEPS - - local found="" - for candidate in /usr/bin/sing-box /usr/local/bin/sing-box /bin/sing-box; do - [ -x "$candidate" ] && found="$candidate" && break - done - [ -z "$found" ] && found=$(command -v sing-box 2>/dev/null || true) - - if [ -n "$found" ]; then - local ver - ver=$("$found" version 2>/dev/null | head -1 || echo "unknown") - log_ok "Encontrado: ${found} (${ver})" - if [ "$found" != "$SINGBOX_BIN" ]; then - sudo ln -sf "$found" "$SINGBOX_BIN" - log_ok "Symlink → ${SINGBOX_BIN}" - fi - else - _install_singbox - fi - - # setcap — sing-box crea TUN sin necesitar root completo - if command -v setcap &>/dev/null; then - sudo setcap cap_net_admin,cap_net_raw+ep "$SINGBOX_BIN" - log_ok "setcap cap_net_admin,cap_net_raw+ep → ${SINGBOX_BIN}" - else - log_warn "setcap no disponible — sing-box necesitará sudo para TUN" - fi - - log_ok "sing-box listo → ${SINGBOX_BIN}" -} - -_install_singbox() { - log_info "sing-box no encontrado — instalando v${SINGBOX_VERSION}..." - - local arch - arch=$(uname -m) - case "$arch" in - x86_64) arch="amd64" ;; - aarch64) arch="arm64" ;; - *) - log_error "Arquitectura no soportada: ${arch}" - exit 1 - ;; - esac - - local url="https://github.com/SagerNet/sing-box/releases/download/v${SINGBOX_VERSION}/sing-box-${SINGBOX_VERSION}-linux-${arch}.tar.gz" - local tmp_tar="/tmp/sing-box-$$.tar.gz" - local tmp_dir="/tmp/sing-box-$$" - - start_spinner "Descargando sing-box v${SINGBOX_VERSION}..." - if wget -q --timeout=60 "$url" -O "$tmp_tar"; then - stop_spinner "Descarga completa" - else - stop_spinner - rm -f "$tmp_tar" - log_error "Descarga falló: ${url}" - exit 1 - fi - - start_spinner "Extrayendo..." - mkdir -p "$tmp_dir" - tar -xzf "$tmp_tar" -C "$tmp_dir" --strip-components=1 - stop_spinner "Extraído" - - sudo cp "$tmp_dir/sing-box" "$SINGBOX_BIN" - sudo chmod 755 "$SINGBOX_BIN" - rm -rf "$tmp_tar" "$tmp_dir" - log_ok "sing-box instalado: $($SINGBOX_BIN version | head -1)" -} \ No newline at end of file diff --git a/lib/steps/sudoers.sh b/lib/steps/sudoers.sh deleted file mode 100644 index 5753297..0000000 --- a/lib/steps/sudoers.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -# lib/steps/sudoers.sh — sudoers + directorios XDG - -step_sudoers() { - print_section "Permissions & Directories" - log_step "sudoers + XDG directories" 6 $TOTAL_STEPS - - _setup_sudoers - _create_directories -} - -_setup_sudoers() { - local tmp - tmp=$(mktemp) - - cat > "$tmp" << EOF -# hydraveil — generado por installer $(date '+%Y-%m-%d') -# NO editar manualmente -Defaults!${SINGBOX_WRAPPER} !requiretty -${USER} ALL=(root) NOPASSWD: ${SINGBOX_WRAPPER} -EOF - - if ! sudo visudo -c -f "$tmp" &>/dev/null; then - rm -f "$tmp" - log_error "sudoers inválido — abortando para evitar lockout" - exit 1 - fi - - sudo cp "$tmp" "$SUDOERS_PATH" - sudo chmod 440 "$SUDOERS_PATH" - rm -f "$tmp" - log_ok "sudoers instalado → ${SUDOERS_PATH}" -} - -_create_directories() { - local -a dirs=( - # XDG data - "$HV_DATA_DIR/configs" - "$HV_DATA_DIR/runtime" - "$HV_DATA_DIR/applications" - "$HV_DATA_DIR/profiles" - "$HV_DATA_DIR/ticket_data" - "$HV_DATA_DIR/incidents" - # XDG config - "$HV_CONFIG_DIR/profiles" - "$HV_CONFIG_DIR/ticketing" - # XDG state - "$HV_STATE_DIR/sessions" - "$HV_STATE_DIR/tor" - # XDG cache - "$HV_CACHE_DIR" - ) - - for dir in "${dirs[@]}"; do - mkdir -p "$dir" - chmod 700 "$dir" - log_ok "→ ${dir}" - done - - # Log file con permisos de usuario ANTES de que el wrapper lo toque - local log_file="$SINGBOX_LOG_FILE" - if [ ! -f "$log_file" ]; then - touch "$log_file" - chmod 600 "$log_file" - log_ok "Log file creado → ${log_file}" - else - log_ok "Log file ya existe → ${log_file}" - fi -} \ No newline at end of file diff --git a/lib/steps/wrapper.sh b/lib/steps/wrapper.sh deleted file mode 100644 index 0ca3fb0..0000000 --- a/lib/steps/wrapper.sh +++ /dev/null @@ -1,140 +0,0 @@ -#!/bin/bash -# lib/steps/wrapper.sh — generar wrapper de privilegios para sing-box - -step_wrapper() { - print_section "Security Wrapper" - log_step "Installing hydraveil-singbox wrapper" 5 $TOTAL_STEPS - - [ -f "$SINGBOX_WRAPPER" ] && sudo rm -f "$SINGBOX_WRAPPER" && log_ok "Wrapper anterior eliminado" - - sudo tee "$SINGBOX_WRAPPER" > /dev/null << WRAPPER -#!/bin/bash -# hydraveil-singbox — wrapper de privilegios para sing-box -# Generado por el installer. NO editar manualmente. -# Solo este binario tiene NOPASSWD en sudoers. -# -# Uso: -# sudo hydraveil-singbox start -# sudo hydraveil-singbox "" stop -# sudo hydraveil-singbox "" log -set -euo pipefail - -CONFIG="\${1:-}" -ACTION="\${2:-}" - -# ── Paths fijos (rutas XDG correctas — INSTALL_DIR=\$HOME) ──────────────────── -SINGBOX_BIN="${SINGBOX_BIN}" -PID_FILE="${SINGBOX_PID_FILE}" -LOG_FILE="${SINGBOX_LOG_FILE}" -CONFIG_DIR="${SINGBOX_CONFIG_DIR}" -STOP_TIMEOUT=8 - -# ── Validar acción ──────────────────────────────────────────────────────────── -if [[ "\$ACTION" != "start" && "\$ACTION" != "stop" && "\$ACTION" != "log" ]]; then - echo "Error: acción inválida '\$ACTION'. Uso: start|stop|log" >&2 - exit 1 -fi - -# ── Helpers ─────────────────────────────────────────────────────────────────── -_pid_running() { - local pid="\$1" - [[ "\$pid" =~ ^[0-9]+$ ]] && kill -0 "\$pid" 2>/dev/null -} - -_delete_tun() { - if ip link show tun0 &>/dev/null 2>&1; then - ip link delete tun0 2>/dev/null || true - echo "[wrapper] tun0 eliminada" >> "\$LOG_FILE" 2>/dev/null || true - fi -} - -_kill_by_pidfile() { - [ -f "\$PID_FILE" ] || return 0 - local pid - pid=\$(cat "\$PID_FILE" 2>/dev/null || echo "") - if ! _pid_running "\$pid"; then - rm -f "\$PID_FILE" - return 0 - fi - kill -TERM "\$pid" 2>/dev/null || true - echo "[wrapper] SIGTERM → PID \$pid" >> "\$LOG_FILE" 2>/dev/null || true - local elapsed=0 - while _pid_running "\$pid" && (( elapsed < STOP_TIMEOUT * 2 )); do - sleep 0.5 - (( elapsed++ )) || true - done - if _pid_running "\$pid"; then - kill -KILL "\$pid" 2>/dev/null || true - echo "[wrapper] SIGKILL → PID \$pid" >> "\$LOG_FILE" 2>/dev/null || true - sleep 0.3 - fi - rm -f "\$PID_FILE" -} - -# ── log ─────────────────────────────────────────────────────────────────────── -if [[ "\$ACTION" == "log" ]]; then - [ -f "\$LOG_FILE" ] && tail -80 "\$LOG_FILE" || echo "Sin logs disponibles" - exit 0 -fi - -# ── stop ────────────────────────────────────────────────────────────────────── -if [[ "\$ACTION" == "stop" ]]; then - _kill_by_pidfile - _delete_tun - echo "[wrapper] sing-box detenido — \$(date '+%Y-%m-%d %H:%M:%S')" >> "\$LOG_FILE" 2>/dev/null || true - exit 0 -fi - -# ── start ───────────────────────────────────────────────────────────────────── -[[ -z "\$CONFIG" ]] && { echo "Error: config requerido para start" >&2; exit 1; } -[[ ! -f "\$CONFIG" ]] && { echo "Error: config no encontrado: \$CONFIG" >&2; exit 1; } -[[ ! -r "\$CONFIG" ]] && { echo "Error: config no legible: \$CONFIG" >&2; exit 1; } - -_real_config=\$(realpath "\$CONFIG") -_real_config_dir=\$(realpath "\$CONFIG_DIR") -if [[ "\$_real_config" != "\$_real_config_dir"/* ]]; then - echo "Error: config fuera del directorio permitido" >&2 - echo " Config: \$_real_config" >&2 - echo " Permitido: \$_real_config_dir" >&2 - exit 1 -fi - -if ! python3 -c "import json,sys; json.load(open(sys.argv[1]))" "\$CONFIG" 2>/dev/null; then - echo "Error: JSON inválido: \$CONFIG" >&2 - exit 1 -fi - -[[ ! -x "\$SINGBOX_BIN" ]] && { echo "Error: sing-box no encontrado: \$SINGBOX_BIN" >&2; exit 1; } - -_kill_by_pidfile -_delete_tun - -mkdir -p "\$(dirname "\$PID_FILE")" && chmod 700 "\$(dirname "\$PID_FILE")" -mkdir -p "\$(dirname "\$LOG_FILE")" && chmod 700 "\$(dirname "\$LOG_FILE")" - -"\$SINGBOX_BIN" run -c "\$CONFIG" >> "\$LOG_FILE" 2>&1 & -_new_pid=\$! - -sleep 0.3 -if ! _pid_running "\$_new_pid"; then - echo "Error: sing-box terminó inmediatamente. Ver: \$LOG_FILE" >&2 - exit 1 -fi - -echo "\$_new_pid" > "\$PID_FILE" -chmod 600 "\$PID_FILE" -echo "[wrapper] sing-box iniciado — PID \$_new_pid — \$(date '+%Y-%m-%d %H:%M:%S')" >> "\$LOG_FILE" -echo "[wrapper] config: \$_real_config" >> "\$LOG_FILE" -exit 0 -WRAPPER - - sudo chmod 755 "$SINGBOX_WRAPPER" - log_ok "Wrapper instalado → ${SINGBOX_WRAPPER}" - - # Verificar que el wrapper funciona - if sudo "$SINGBOX_WRAPPER" "" stop &>/dev/null; then - log_ok "Wrapper verificado (stop OK)" - else - log_warn "Wrapper instalado pero verificación falló — revisar manualmente" - fi -} \ No newline at end of file diff --git a/lib/ui.sh b/lib/ui.sh deleted file mode 100755 index bb1ed54..0000000 --- a/lib/ui.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/bin/bash - -RESET="\033[0m" -BOLD="\033[1m" -DIM="\033[2m" - -BLACK="\033[0;30m" -RED="\033[0;31m" -GREEN="\033[0;32m" -YELLOW="\033[0;33m" -BLUE="\033[0;34m" -CYAN="\033[0;36m" -WHITE="\033[0;37m" - -BRED="\033[1;31m" -BGREEN="\033[1;32m" -BYELLOW="\033[1;33m" -BBLUE="\033[1;34m" -BCYAN="\033[1;36m" -BWHITE="\033[1;37m" - -print_header() { - local title="${1:-INSTALLER}" - local version="${2:-}" - local width=60 - local line - printf -v line '%*s' "$width" '' - line="${line// /─}" - - echo "" - printf "${BCYAN}${line}${RESET}\n" - printf "${BCYAN} %-56s${RESET}\n" "" - printf "${BCYAN} ${BWHITE}%-54s${BCYAN} ${RESET}\n" "$title ${DIM}${version}${RESET}" - printf "${BCYAN} %-56s${RESET}\n" "" - printf "${BCYAN}${line}${RESET}\n" - echo "" -} - -print_section() { - local label="$1" - echo "" - printf "${DIM}${CYAN}┌─${RESET} ${BWHITE}${label}${RESET}\n" -} - -print_divider() { - printf "${DIM}────────────────────────────────────────────────────────────${RESET}\n" -} - -label_ok() { printf " ${BGREEN}[ OK ]${RESET} %s\n" "$1"; } -label_warn() { printf " ${BYELLOW}[ WARN ]${RESET} %s\n" "$1"; } -label_error() { printf " ${BRED}[ ERROR]${RESET} %s\n" "$1"; } -label_info() { printf " ${BCYAN}[ INFO ]${RESET} %s\n" "$1"; } -label_skip() { printf " ${DIM}[ SKIP ]${RESET} %s\n" "$1"; } -label_simple() { printf " ${DIM}[SIMPLE]${RESET} %s\n" "$1"; } -label_connected() { printf " ${BGREEN}[CONNECTED ]${RESET} %s\n" "$1"; } -label_disconnect() { printf " ${BRED}[DISCONNECTED]${RESET} %s\n" "$1"; } -label_step() { printf " ${BBLUE}[ %-2s/%-2s ]${RESET} %s\n" "$2" "$3" "$1"; } - -progress_bar() { - local current=$1 - local total=$2 - local label="${3:-}" - local width=40 - local filled=$(( current * width / total )) - local empty=$(( width - filled )) - local pct=$(( current * 100 / total )) - - local bar_filled bar_empty - printf -v bar_filled '%*s' "$filled" '' - printf -v bar_empty '%*s' "$empty" '' - bar_filled="${bar_filled// /#}" - bar_empty="${bar_empty// /·}" - - printf "\r ${DIM}[${RESET}${BGREEN}${bar_filled}${RESET}${DIM}${bar_empty}${RESET}${DIM}]${RESET} ${BWHITE}%3d%%${RESET} ${DIM}%s${RESET} " \ - "$pct" "$label" -} - -progress_simulate() { - local steps="${1:-20}" - local label="${2:-loading}" - for i in $(seq 1 "$steps"); do - progress_bar "$i" "$steps" "$label" - sleep 0.04 - done - echo "" -} - -SPINNER_PID="" - -start_spinner() { - local msg="${1:-Processing...}" - local frames=('─' '\\' '│' '/') - ( - local i=0 - while true; do - printf "\r ${BCYAN}${frames[$i]}${RESET} ${DIM}%s${RESET} " "$msg" - i=$(( (i + 1) % 4 )) - sleep 0.1 - done - ) & - SPINNER_PID=$! - disown "$SPINNER_PID" 2>/dev/null -} - -stop_spinner() { - local result_msg="${1:-}" - if [ -n "$SPINNER_PID" ]; then - kill "$SPINNER_PID" 2>/dev/null - wait "$SPINNER_PID" 2>/dev/null - SPINNER_PID="" - fi - printf "\r%*s\r" 60 "" - [ -n "$result_msg" ] && label_ok "$result_msg" -}