diff --git a/INSTALLER_V3/Dune_Rise.otf b/INSTALLER/Dune_Rise.otf similarity index 100% rename from INSTALLER_V3/Dune_Rise.otf rename to INSTALLER/Dune_Rise.otf diff --git a/INSTALLER/Dune_Rise.ttf b/INSTALLER/Dune_Rise.ttf new file mode 100644 index 0000000..4da6799 Binary files /dev/null and b/INSTALLER/Dune_Rise.ttf differ diff --git a/INSTALLER/Screencast from 2025-09-25 22-14-05.webm b/INSTALLER/Screencast from 2025-09-25 22-14-05.webm new file mode 100644 index 0000000..c2e0b9d Binary files /dev/null and b/INSTALLER/Screencast from 2025-09-25 22-14-05.webm differ diff --git a/INSTALLER/Screencast from 2025-09-25 22-18-23.webm b/INSTALLER/Screencast from 2025-09-25 22-18-23.webm new file mode 100644 index 0000000..3b4fb5f Binary files /dev/null and b/INSTALLER/Screencast from 2025-09-25 22-18-23.webm differ diff --git a/INSTALLER/Screencast from 2025-09-25 22-46-23.webm b/INSTALLER/Screencast from 2025-09-25 22-46-23.webm new file mode 100644 index 0000000..dd274a1 Binary files /dev/null and b/INSTALLER/Screencast from 2025-09-25 22-46-23.webm differ diff --git a/INSTALLER_V3/ecoin.png b/INSTALLER/ecoin.png similarity index 100% rename from INSTALLER_V3/ecoin.png rename to INSTALLER/ecoin.png diff --git a/INSTALLER/gtk.css b/INSTALLER/gtk.css new file mode 100644 index 0000000..d5b065e --- /dev/null +++ b/INSTALLER/gtk.css @@ -0,0 +1,106 @@ +/* ========================================================== + SOLAR NET HUB – GTK3 Theme (YAD) + ========================================================== */ + +@define-color bg #000000; +@define-color fg #E6E6E6; +@define-color muted #9AA0A6; +@define-color accent #FF4E00; /* Naranja corporativo */ +@define-color success #27D980; /* Verde OK */ +@define-color field_bg #0B0B0B; +@define-color stroke #202020; + +/* ---------- Tipografía / color global ---------- */ +* { + color: @fg; + font-family: "Dune Rise", "Cantarell", "Ubuntu", "DejaVu Sans", sans-serif; + font-size: 12.1pt; /* un pelín más pequeño */ +} + +/* ---------- Fondos de contenedores ---------- */ +window, dialog, grid, box, scrolledwindow, viewport { background-color: @bg; } +grid { padding: 11px 12px 14px 12px; } /* menos padding para que quepa más */ + +/* ---------- Evitar fondos dentro de textos/botones ---------- */ +label, accellabel, +button label, button accellabel, +button * { background: transparent; background-color: transparent; } + +/* ========================================================== + BOTONES: negro + borde/ texto naranja → hover VERDE + texto negro + ========================================================== */ +/* ========================================================== + CTAs (botones de acción) — versión compacta + ========================================================== */ +button { + background: @bg; /* fondo negro */ + color: @accent; /* texto naranja */ + border: 2px solid @accent; /* borde naranja */ + border-radius: 10px; + padding: 5px 5px; /* ↓ padding */ + font-weight: 200; + font-size: 5pt; /* ↓ tamaño de fuente */ + letter-spacing: .2px; + min-width: 100px; /* ↓ ancho mínimo */ + min-height: 20px; /* ↓ alto mínimo */ + margin: 3px 6px; /* ↓ separación vertical */ +} + +/* Hover: botón verde y texto negro (como pediste) */ +button:hover { + background-color: @success; /* verde */ + color: #000; /* texto negro */ + border-color: @success; +} + +button:active { + background-color: shade(@success, 0.85); + color: #000; + border-color: shade(@success, 0.85); +} + +button:disabled { + background-color: #101010; + color: #444; + border-color: #222; +} + +/* Icono dentro del botón (si lo hay), más contenido y pequeño */ +button image { + margin-right: 6px; + -gtk-icon-transform: scale(0.95); +} + +/* ========================================================== + Entradas / listas (por si las usas) + ========================================================== */ +entry, spinbutton, combobox, textview, treeview { + background-color: @field_bg; + color: @fg; + border: 1px solid @stroke; + border-radius: 10px; + padding: 8px 10px; +} +entry:focus, spinbutton:focus, combobox:focus, textview:focus, treeview:focus { + border-color: @accent; +} + +/* ========================================================== + Progreso YAD + ========================================================== */ +trough { background-color: #121212; border-radius: 6px; } +progressbar progress { background-color: @accent; border-radius: 6px; } + +/* ========================================================== + Tooltips + ========================================================== */ +tooltip, tooltip * { + background-color: #111111; + color: @fg; + border: 1px solid @stroke; + border-radius: 8px; + padding: 6px 8px; +} + +/* Labels sueltos dentro del grid con separación mínima */ +grid label { margin: 3px 0 5px; } diff --git a/INSTALLER/gtk_oasis.css b/INSTALLER/gtk_oasis.css new file mode 100644 index 0000000..1bf6e57 --- /dev/null +++ b/INSTALLER/gtk_oasis.css @@ -0,0 +1,42 @@ +/* ========================================================== + SOLAR NET HUB – GTK3 Theme (YAD) – OASIS simple (fluido) + ========================================================== */ + +@define-color bg #000000; +@define-color fg #E6E6E6; +@define-color accent #FF4E00; +@define-color accent_hover #27D980; + +/* Tipografía y color global: tamaño moderado (mejor en HiDPI) */ +* { + color: @fg; + font-family: "Dune Rise", "Cantarell", "Ubuntu", "DejaVu Sans", sans-serif; + font-size: 11pt; /* antes 13pt */ +} + +window, dialog, grid, box, scrolledwindow, viewport { + background-color: @bg; +} +grid { padding: 16px; } /* un poco menos de padding */ + +/* Botones sin tamaños rígidos: usa padding, no min-width/height */ +button { + background: #000000; + color: @accent; + border: 2px solid @accent; + border-radius: 14px; + padding: 10px 14px; /* sustituye min-width/height por padding */ + margin: 8px 0; + letter-spacing: 0.5px; +} +button:hover { background: @accent_hover; color: #000; } +button:active { background: @accent; color: #000; } + +/* Limpieza de fondos parásitos en textos dentro de botones */ +label, accellabel, +button label, button accellabel, +button * { + background: transparent; + background-color: transparent; +} + diff --git a/INSTALLER_V3/icons/hicolor/256x256/apps/ecoin.png b/INSTALLER/icons/hicolor/256x256/apps/ecoin.png similarity index 100% rename from INSTALLER_V3/icons/hicolor/256x256/apps/ecoin.png rename to INSTALLER/icons/hicolor/256x256/apps/ecoin.png diff --git a/INSTALLER/icons/hicolor/256x256/apps/oasis-hero.png b/INSTALLER/icons/hicolor/256x256/apps/oasis-hero.png new file mode 100644 index 0000000..f28986c Binary files /dev/null and b/INSTALLER/icons/hicolor/256x256/apps/oasis-hero.png differ diff --git a/INSTALLER_V3/icons/hicolor/256x256/apps/oasis-logo.png b/INSTALLER/icons/hicolor/256x256/apps/oasis-logo.png similarity index 100% rename from INSTALLER_V3/icons/hicolor/256x256/apps/oasis-logo.png rename to INSTALLER/icons/hicolor/256x256/apps/oasis-logo.png diff --git a/INSTALLER_V3/icons/hicolor/index.theme b/INSTALLER/icons/hicolor/index.theme similarity index 100% rename from INSTALLER_V3/icons/hicolor/index.theme rename to INSTALLER/icons/hicolor/index.theme diff --git a/INSTALLER_V3/installer.sh b/INSTALLER/installer.sh similarity index 100% rename from INSTALLER_V3/installer.sh rename to INSTALLER/installer.sh diff --git a/INSTALLER/installer.sh.bak.1757923726 b/INSTALLER/installer.sh.bak.1757923726 new file mode 100755 index 0000000..a72bf07 --- /dev/null +++ b/INSTALLER/installer.sh.bak.1757923726 @@ -0,0 +1,346 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ========================= +# SOLAR NET HUB - Installer +# ========================= + +# --- Rutas --- +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)" +INSTALLER_DIR="$SCRIPT_DIR" +CSS_SOURCE="$INSTALLER_DIR/gtk.css" # (lo añadimos luego) +ICON_OASIS="$INSTALLER_DIR/oasis-logo.webp" +ICON_ECOIN="$INSTALLER_DIR/ecoin.png" + +APPS_DIR="$HOME/.local/share/applications" +DESKTOP_DIR="$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")" + +# --- Theming local (no toca el sistema) --- +NAME="SolarHubInstaller" # --name/--class (CSS scope) +THEME_NAME="SolarHub" # ~/.themes/SolarHub/gtk-3.0/gtk.css +YAD_THEME="${THEME_NAME}:dark" +ORANGE="#FF4E00" +GREEN="#27D980" +GREY="#b9b9b9" + +# --- Helpers básicos --- +have(){ command -v "$1" >/dev/null 2>&1; } +pkg_install(){ + local pkgs=("$@") + if have apt-get; then + pkexec bash -lc "apt-get update && apt-get install -y ${pkgs[*]}" + elif have pacman; then + pkexec bash -lc "pacman -Sy --noconfirm ${pkgs[*]}" + elif have dnf; then + pkexec bash -lc "dnf install -y ${pkgs[*]}" + elif have zypper; then + pkexec bash -lc "zypper --non-interactive install ${pkgs[*]}" + else + echo "❌ Gestor de paquetes no soportado. Instala manualmente: yad, policykit-1, hicolor-icon-theme, xdg-user-dirs" >&2 + exit 1 + fi +} + + +ensure_theme(){ + local tdir="$HOME/.themes/${THEME_NAME}/gtk-3.0" + mkdir -p "$tdir" + if [ -f "$CSS_SOURCE" ]; then + sed 's/\r$//' "$CSS_SOURCE" > "$tdir/gtk.css" + else + # CSS mínimo por si aún no creaste gtk.css + cat > "$tdir/gtk.css" <<'CSS' +*{background:#000;color:#E6E6E6;font-family:"Cantarell","Ubuntu","DejaVu Sans",sans-serif;font-size:12pt} +button{background:#FF4E00;color:#000;border:none;border-radius:16px;padding:12px 18px;font-weight:800} +button:hover{background:#ff6a26} button:active{background:#e24a00} +CSS + fi +} + +yad_cmd(){ GTK_THEME="$YAD_THEME" yad --name="$NAME" --class="$NAME" "$@"; } + +progress_user(){ # $1=title $2=cmd (como string) + bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons \ + --width=940 --height=260 --center +} +progress_root(){ # $1=title $2=cmd (string) -> pkexec + pkexec bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons \ + --width=940 --height=260 --center +} + +ensure_launcher(){ + mkdir -p "$APPS_DIR" "$DESKTOP_DIR" + cat > "$APPS_DIR/INSTALLER.desktop" </dev/null || true + chmod +x "$DESKTOP_DIR/INSTALLER.desktop" 2>/dev/null || true +} + +# ========= Detección de estado ========= +OASIS_DIR_DEFAULT="${HOME}/oasis" +OASIS_REPO_DEFAULT="https://code.03c8.net/KrakensLab/oasis.git" +OASIS_MODEL_FILE="oasis-42-1-chat.Q4_K_M.gguf" +OASIS_MODEL_TAR="${OASIS_MODEL_FILE}.tar.gz" +OASIS_MODEL_URL="https://solarnethub.com/code/models/${OASIS_MODEL_TAR}" +oasis_installed(){ local d="${1:-$OASIS_DIR_DEFAULT}"; [[ -d "$d/src/server/node_modules" && -f "$d/AI/$OASIS_MODEL_FILE" ]]; } + +ECOIN_DIR_DEFAULT="${HOME}/ecoin" +ECOIN_REPO_DEFAULT="https://github.com/epsylon/ecoin" +ecoin_installed(){ local d="${1:-$ECOIN_DIR_DEFAULT}"; [[ -x "$d/ecoin/ecoin-qt" || -x "$d/ecoin/src/ecoind" ]]; } +ecoin_wallet_exists(){ [ -f "$HOME/.ecoin/wallet.dat" ]; } + +# ========= Comandos OASIS ========= +node_setup_cmd(){ # $1=major (22/18) +cat < "$CONF" </dev/null 2>&1; then + KEY="$(shuf -i 1-2000000000 -n 1)" + else + KEY=$(( (RANDOM<<15) | RANDOM )); KEY=$((KEY%2000000000+1)) + fi + + # --- MITAD IZQUIERDA: OASIS --- + local O_TEXT O_BTN + if oasis_installed; then + O_TEXT="OASIS\nInstalado" + O_BTN="bash -lc '$0 --oasis-start'!$ICON_OASIS!Abrir OASIS" + else + O_TEXT="OASIS\nNo instalado" + O_BTN="bash -lc '$0 --oasis-install'!$ICON_OASIS!Instalar OASIS" + fi + + yad_cmd --plug="$KEY" --form --borders=28 --columns=1 \ + ${ICON_OASIS:+--image="$ICON_OASIS"} --image-on-top \ + --text="$O_TEXT" \ + --field=":BTN" "$O_BTN" \ + --no-buttons --buttons-layout=center & + PID_L=$! + + # --- MITAD DERECHA: ECOIN --- + local E_TEXT E_BTN1 E_BTN2 + if ! ecoin_installed; then + E_TEXT="ECOIN\nNo instalado" + E_BTN1="bash -lc '$0 --ecoin-install'!$ICON_ECOIN!Instalar ECOIN" + E_BTN2="" + else + if ecoin_wallet_exists; then + E_TEXT="ECOIN\nCartera detectada" + E_BTN1="bash -lc '$0 --wallet-connect'!$ICON_ECOIN!Conectar cartera" + E_BTN2="bash -lc '$0 --ecoin-gui'!$ICON_ECOIN!Abrir ECOIN GUI" + else + E_TEXT="ECOIN\nSin cartera" + E_BTN1="bash -lc '$0 --wallet-create'!$ICON_ECOIN!Crear cartera" + E_BTN2="" + fi + fi + + if [ -n "$E_BTN2" ]; then + yad_cmd --plug="$KEY" --form --borders=28 --columns=1 \ + ${ICON_ECOIN:+--image="$ICON_ECOIN"} --image-on-top \ + --text="$E_TEXT" \ + --field=":BTN" "$E_BTN1" \ + --field=":BTN" "$E_BTN2" \ + --no-buttons --buttons-layout=center & + else + yad_cmd --plug="$KEY" --form --borders=28 --columns=1 \ + ${ICON_ECOIN:+--image="$ICON_ECOIN"} --image-on-top \ + --text="$E_TEXT" \ + --field=":BTN" "$E_BTN1" \ + --no-buttons --buttons-layout=center & + fi + PID_R=$! + + sleep 0.35 + yad_cmd --paned --key="$KEY" \ + --title="SOLAR NET HUB" --width=1180 --height=740 --center \ + --button="Cerrar:0" + + kill "$PID_L" "$PID_R" 2>/dev/null || true +} + +# ========= Entrypoint ========= +ensure_basics +ensure_theme +ensure_launcher + +case "${1:-}" in + --oasis-install) oasis_quick_install 22 "$OASIS_REPO_DEFAULT" "$OASIS_DIR_DEFAULT" ;; + --oasis-start) oasis_quick_start "$OASIS_DIR_DEFAULT" ;; + --ecoin-install) ecoin_quick_install "$ECOIN_REPO_DEFAULT" "$ECOIN_DIR_DEFAULT" ;; + --wallet-create) progress_user "ECOIN: Crear cartera" "$(ecoin_create_wallet_cmd)" ;; + --wallet-connect)progress_user "ECOIN: Conectar cartera" "$(ecoin_connect_wallet_cmd)" ;; + --ecoin-gui) progress_user "ECOIN: Wallet (Qt)" "$(ecoin_qt_cmd "$ECOIN_DIR_DEFAULT")" ;; + *) home ;; +esac +ensure_basics(){ + local miss=() + command -v yad >/dev/null 2>&1 || miss+=("yad") + command -v pkexec >/dev/null 2>&1 || miss+=("policykit-1") + command -v xdg-user-dirs-update >/dev/null 2>&1 || miss+=("xdg-user-dirs") + if command -v dpkg >/dev/null 2>&1; then + dpkg -s hicolor-icon-theme >/dev/null 2>&1 || miss+=("hicolor-icon-theme") + elif command -v rpm >/dev/null 2>&1; then + rpm -q hicolor-icon-theme >/dev/null 2>&1 || miss+=("hicolor-icon-theme") + fi + ((${#miss[@]})) && pkg_install "${miss[@]}" +} diff --git a/INSTALLER/installer_oasis.sh b/INSTALLER/installer_oasis.sh new file mode 100755 index 0000000..6dcd815 --- /dev/null +++ b/INSTALLER/installer_oasis.sh @@ -0,0 +1,265 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ========================= +# OASIS Installer (simple) +# ========================= + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +: "${SELF:=$SCRIPT_DIR/installer_oasis.sh}" + +INSTALLER_DIR="$SCRIPT_DIR" +CSS_SOURCE="$INSTALLER_DIR/gtk_oasis.css" + +# Imagen/icono +HERO_FILE="$INSTALLER_DIR/oasis-logito.png"; [ -f "$HERO_FILE" ] || HERO_FILE="$INSTALLER_DIR/oasis-logo.png" +WINDOW_ICON="$INSTALLER_DIR/oasis-logo.png" + +# Tema / ventana +NAME="OasisInstaller" +THEME_NAME="SolarOasis" +YAD_THEME="${THEME_NAME}:dark" + +# Repo (se mantiene por compatibilidad con funciones existentes) +OASIS_REPO_DEFAULT="https://code.03c8.net/KrakensLab/oasis.git" + +# Estado persistente +STATE_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/oasis-installer" +STATE_FILE="$STATE_DIR/state" +mkdir -p "$STATE_DIR" + +have(){ command -v "$1" >/dev/null 2>&1; } + +# ===== NUEVO: chequeo de npm (único cambio funcional pedido) ===== +npm_ok(){ + # Permite forzar el estado "no instalado" para pruebas: + # OASIS_FORCE_INSTALL=1 ./INSTALLER/installer_oasis.sh + if [ "${OASIS_FORCE_INSTALL:-0}" = "1" ]; then + return 1 + fi + have npm && npm -v >/dev/null 2>&1 +} + +# ---------- Preflight ---------- +preflight(){ + # DISPLAY “de cortesía” + if [ -z "${DISPLAY:-}" ]; then export DISPLAY=":0"; fi + # YAD disponible? + if ! have yad; then + echo "✘ Falta 'yad'. Instálalo y vuelve a ejecutar." >&2 + exit 1 + fi +} + +ensure_basics(){ + local miss=() + have yad || miss+=("yad") + command -v pkexec >/dev/null 2>&1 || miss+=("policykit-1") + have xdg-user-dirs-update || miss+=("xdg-user-dirs") + if command -v dpkg >/dev/null 2>&1; then + dpkg -s hicolor-icon-theme >/dev/null 2>&1 || miss+=("hicolor-icon-theme") + fi + if ((${#miss[@]})); then + if have apt-get; then + if have pkexec; then pkexec bash -lc "apt-get update && apt-get install -y ${miss[*]}"; + else sudo apt-get update && sudo apt-get install -y "${miss[@]}"; fi + fi + fi +} + +ensure_theme(){ + local tdir="$HOME/.themes/${THEME_NAME}/gtk-3.0" + mkdir -p "$tdir" + if [ -f "$CSS_SOURCE" ]; then + sed 's/\r$//' "$CSS_SOURCE" > "$tdir/gtk.css" + else + cat > "$tdir/gtk.css" <<'CSS' +*{background:#000;color:#E6E6E6;font-family:"Dune Rise","Cantarell","Ubuntu","DejaVu Sans",sans-serif;font-size:13pt} +grid{padding:18px} +button{ + border:2px solid #FF4E00; border-radius:14px; background:#000; color:#FF4E00; + min-width:380px; min-height:46px; margin:10px 0; letter-spacing:.5px; +} +button:hover{ background:#27D980; color:#000; } +button:active{ background:#FF4E00; color:#000; } +image{ margin:8px auto 6px auto; } +CSS + fi +} + +# YAD wrapper +yad_cmd(){ GTK_THEME="$YAD_THEME" NO_AT_BRIDGE=1 yad --name="$NAME" --class="$NAME" "$@"; } + +# -------- Detección / estado de OASIS -------- +oasis_is_installed_dir(){ + local d="$1" + [[ -f "$d/oasis.sh" ]] || [[ -f "$d/src/server/server.js" ]] || [[ -d "$d/src/server/node_modules" ]] +} + +save_oasis_dir(){ + local d="$1" + mkdir -p "$STATE_DIR" + { + echo "OASIS_DIR=$d" + date +"SAVED=%Y-%m-%dT%H:%M:%S" + } > "$STATE_FILE" +} + +read_saved_dir(){ + [ -f "$STATE_FILE" ] && sed -n 's/^OASIS_DIR=//p' "$STATE_FILE" | head -n1 || true +} + +guess_oasis_dir(){ + local cands=() + + # 1) Padre del installer (estructura estándar) + cands+=("$SCRIPT_DIR/..") + + # 2) Guardado previo + local saved; saved="$(read_saved_dir || true)" + [ -n "${saved:-}" ] && cands+=("$saved") + + # 3) Candidatas típicas + cands+=("$HOME/COFRE/CODERS/oasis" "$HOME/oasis" "$HOME/Projects/oasis" "$HOME/Documentos/oasis") + + local d + for d in "${cands[@]}"; do + d="$(cd "$d" 2>/dev/null && pwd || true)" + [ -n "$d" ] || continue + if oasis_is_installed_dir "$d"; then + echo "$d"; return 0 + fi + done + echo "$HOME/oasis" +} + +# -------- Comandos auxiliares existentes (sin cambios) -------- +node_setup_cmd(){ cat <<'EOF' +set -e +apt-get update +apt-get install -y git curl ca-certificates tar gnupg +curl -fsSL https://deb.nodesource.com/setup_22.x | bash - +apt-get install -y nodejs +node -v && npm -v +EOF +} + +oasis_install_cmd(){ cat < /tmp/oasis_gui.log 2>&1 & + for i in $(seq 1 30); do + if ss -lnt sport = :$PORT | grep -q "$PORT"; then + echo "Oasis levantado en $PORT" + break + fi + sleep 1 + done +fi + +# Abrir navegador siempre +xdg-open "http://localhost:$PORT" >/dev/null 2>&1 || true +EOF +} + +progress_user(){ bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons --width=700 --height=180 --center; } +progress_root(){ pkexec bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons --width=700 --height=180 --center; } + +oasis_quick_install(){ + local dir="$1" + progress_root "OASIS: Node.js 22" "$(node_setup_cmd)" + progress_user "OASIS: Clonar + npm" "$(oasis_install_cmd "$OASIS_REPO_DEFAULT" "$dir")" + save_oasis_dir "$dir" +} + +oasis_quick_start(){ + local dir="$1" + progress_user "OASIS: Abrir" "$(oasis_start_cmd "$dir" 3000)" +} + +# -------- UI -------- +home_oasis_dialog(){ + local OASIS_DIR; OASIS_DIR="$(guess_oasis_dir)" + save_oasis_dir "$OASIS_DIR" + + local O_VER="—" + if [ -f "$OASIS_DIR/src/server/package.json" ]; then + O_VER="$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$OASIS_DIR/src/server/package.json" | head -n1)" + [ -z "$O_VER" ] && O_VER="—" + fi + + # Subcomandos (OPEN se mantiene EXACTO como antes) + local BTN_OPEN_SITE="/bin/bash -lc '$SELF --open-site'" + local BTN_INSTALL="/bin/bash -lc '$SELF --do-install'" + local BTN_OPEN="/bin/bash -lc '$SELF --open-now'" + + # ===== ÚNICO CAMBIO: decidir etiqueta/acción de INSTALL según npm ===== + local INSTALL_LABEL="INSTALL:BTN" + if npm_ok; then + INSTALL_LABEL="ALREADY INSTALLED:BTN" + BTN_INSTALL="/bin/true" + fi + # (No tocamos BTN_OPEN) + + yad_cmd \ + --title="SOLAR NET HUB" --window-icon="$WINDOW_ICON" \ + --center --width=720 --height=600 \ + --image="$HERO_FILE" --image-on-top \ + --form --borders=16 --columns=1 --align=center \ + \ + --field="OASIS PROJECT:BTN" "$BTN_OPEN_SITE" \ + --field="$INSTALL_LABEL" "$BTN_INSTALL" \ + --field="OPEN:BTN" "$BTN_OPEN" \ + --field="VERSION ( $O_VER ):BTN" "/bin/true" \ + \ + --buttons-layout=center \ + --button="CERRAR:0" +} + +# -------- Subcomandos -------- +case "${1:-}" in + --open-site) + xdg-open "https://oasis-project.pub" >/dev/null 2>&1 || true + exit 0 + ;; + --do-install) + preflight; ensure_basics; ensure_theme + # Ejecutar el install.sh del raíz del repo (padre de INSTALLER) + if [ -f "$SCRIPT_DIR/../install.sh" ]; then + /bin/bash -lc "cd \"$SCRIPT_DIR/..\" && bash install.sh" + else + yad_cmd --error --title="OASIS Installer" --text="No se encontró install.sh en $SCRIPT_DIR/.." + exit 1 + fi + # Guardar ruta para OPEN + dir="$(cd "$SCRIPT_DIR/.." && pwd)" + save_oasis_dir "$dir" + exit 0 + ;; + --open-now) + preflight; ensure_basics; ensure_theme + dir="$(read_saved_dir)"; [ -n "${dir:-}" ] || dir="$(guess_oasis_dir)" + save_oasis_dir "$dir" + oasis_quick_start "$dir" + exit 0 + ;; + *) + preflight; ensure_basics; ensure_theme + home_oasis_dialog + ;; +esac diff --git a/INSTALLER/oasis-ecoin.png b/INSTALLER/oasis-ecoin.png new file mode 100644 index 0000000..4d870e8 Binary files /dev/null and b/INSTALLER/oasis-ecoin.png differ diff --git a/INSTALLER/oasis-logito.png b/INSTALLER/oasis-logito.png new file mode 100644 index 0000000..f28986c Binary files /dev/null and b/INSTALLER/oasis-logito.png differ diff --git a/INSTALLER_V3/oasis-logo.png b/INSTALLER/oasis-logo.png similarity index 100% rename from INSTALLER_V3/oasis-logo.png rename to INSTALLER/oasis-logo.png diff --git a/INSTALLER/panel.py b/INSTALLER/panel.py new file mode 100755 index 0000000..a3cc7ce --- /dev/null +++ b/INSTALLER/panel.py @@ -0,0 +1,695 @@ +#!/usr/bin/env python3 +""" +OASIS Control Panel — Solar Net Hub +Panel de control compacto estilo Mullvad para gestionar OASIS y ECOIN en Linux. +""" + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib, Gdk, GdkPixbuf + +import subprocess +import os +import sys +import threading +import json +import shutil +from pathlib import Path + +# ── Rutas ────────────────────────────────────────────────────────────────── +SCRIPT_DIR = Path(__file__).parent.resolve() +INSTALLER_SH = SCRIPT_DIR / "installer.sh" +OASIS_DIR = Path.home() / "oasis" +ECOIN_DIR = Path.home() / "ecoin" +MODEL_FILE = "oasis-42-1-chat.Q4_K_M.gguf" + +# ── CSS ──────────────────────────────────────────────────────────────────── +CSS = """ +* { + font-family: "Dune Rise", "Cantarell", "Ubuntu", "DejaVu Sans", sans-serif; + color: #E6E6E6; +} + +window { + background-color: #000000; +} + +/* Header */ +box.panel-header { + background-color: #080808; + border-bottom: 1px solid #1C1C1C; +} + +label.panel-title { + font-size: 11pt; + font-weight: bold; + color: #FF4E00; + letter-spacing: 3px; +} + +/* Status card */ +box.status-card { + background-color: #0D0D0D; + border: 1px solid #1C1C1C; + border-radius: 12px; +} + +label.status-main { + font-size: 14pt; + font-weight: bold; +} + +label.state-running { color: #27D980; } +label.state-stopped { color: #FF4E00; } +label.state-unknown { color: #555555; } + +label.status-sub { + font-size: 8pt; + color: #666666; +} + +/* Dots */ +label.dot { font-size: 9pt; } +label.dot-running { color: #27D980; } +label.dot-stopped { color: #FF4E00; } +label.dot-unknown { color: #333333; } + +/* Botones */ +button.btn { + background-color: #000000; + color: #FF4E00; + border: 1px solid #FF4E00; + border-radius: 18px; + padding: 8px 16px; + margin: 5px 6px; + font-size: 8pt; + font-weight: bold; + letter-spacing: 1px; + min-width: 130px; +} + +button.btn:hover { + background-color: #27D980; + color: #000000; + border-color: #27D980; +} + +button.btn:active { + background-color: #1fa865; + color: #000000; + border-color: #1fa865; +} + +button.btn:disabled { + background-color: #080808; + color: #2A2A2A; + border-color: #181818; +} + +button.btn-primary { + background-color: #FF4E00; + color: #000000; + border: 1px solid #FF4E00; + border-radius: 18px; + padding: 8px 16px; + margin: 5px 6px; + font-size: 8pt; + font-weight: bold; + letter-spacing: 1px; + min-width: 130px; +} + +button.btn-primary:hover { + background-color: #27D980; + color: #000000; + border-color: #27D980; +} + +button.btn-primary:disabled { + background-color: #301200; + color: #553322; + border-color: #301200; +} + +/* Notebook / Tabs */ +notebook > header { + background-color: #000000; + border-bottom: 1px solid #1C1C1C; + padding: 0px; +} + +notebook > header tabs { + background-color: #000000; +} + +tab { + background-color: #000000; + color: #444444; + border: none; + border-bottom: 2px solid transparent; + padding: 10px 22px; + font-size: 8pt; + letter-spacing: 1px; + font-weight: bold; +} + +tab:checked { + background-color: #000000; + color: #FF4E00; + border-bottom: 2px solid #FF4E00; +} + +tab:hover { + color: #AAAAAA; +} + +/* Info grid */ +label.info-key { + color: #555555; + font-size: 8pt; + min-width: 90px; +} + +label.info-val { + color: #CCCCCC; + font-size: 8pt; +} + +/* Log area */ +textview.log-view { + background-color: #050505; + color: #27D980; + font-family: "DejaVu Sans Mono", "Monospace", monospace; + font-size: 7.5pt; + padding: 8px; +} + +textview.log-view text { + background-color: #050505; + color: #27D980; +} + +scrolledwindow.log-scroll { + border: 1px solid #1C1C1C; + border-radius: 8px; + margin: 4px 14px 8px 14px; +} + +/* Separadores */ +separator { + background-color: #141414; + min-height: 1px; +} +""" + + +# ── Helpers de estado ────────────────────────────────────────────────────── +def oasis_installed(): + return ( + (OASIS_DIR / "src" / "server" / "node_modules").is_dir() + and (OASIS_DIR / "AI" / MODEL_FILE).is_file() + ) + + +def oasis_version(): + pkg = OASIS_DIR / "src" / "server" / "package.json" + if pkg.is_file(): + try: + return json.loads(pkg.read_text()).get("version", "—") + except Exception: + pass + return "—" + + +def oasis_running(): + try: + r = subprocess.run(["pgrep", "-f", "node.*server.js"], + capture_output=True, timeout=2) + return r.returncode == 0 + except Exception: + return False + + +def ecoin_installed(): + return ( + (ECOIN_DIR / "ecoin" / "ecoin-qt").is_file() + or (ECOIN_DIR / "ecoin" / "src" / "ecoind").is_file() + ) + + +def ecoin_wallet_exists(): + return (Path.home() / ".ecoin" / "wallet.dat").is_file() + + +def node_version(): + try: + r = subprocess.run(["node", "--version"], + capture_output=True, text=True, timeout=3) + return r.stdout.strip() + except Exception: + return "—" + + +# ── Panel principal ──────────────────────────────────────────────────────── +class OasisPanel(Gtk.ApplicationWindow): + + def __init__(self, app): + super().__init__(application=app, title="SOLAR NET HUB") + self.set_default_size(390, 610) + self.set_resizable(False) + self.set_position(Gtk.WindowPosition.CENTER) + + self._build_ui() + # Poll de estado cada 3 s + GLib.timeout_add_seconds(3, self._trigger_poll) + self._trigger_poll() + + # ── Construcción UI ─────────────────────────────────────────────────── + def _build_ui(self): + root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + self.add(root) + + root.pack_start(self._make_header(), False, False, 0) + + self.notebook = Gtk.Notebook() + self.notebook.set_show_border(False) + root.pack_start(self.notebook, True, True, 0) + + self.notebook.append_page(self._make_oasis_tab(), self._tab_lbl("OASIS")) + self.notebook.append_page(self._make_ecoin_tab(), self._tab_lbl("ECOIN")) + self.notebook.append_page(self._make_sistema_tab(), self._tab_lbl("SISTEMA")) + + def _tab_lbl(self, txt): + return Gtk.Label(label=txt) + + # ── Header ──────────────────────────────────────────────────────────── + def _make_header(self): + box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) + box.get_style_context().add_class("panel-header") + box.set_margin_top(14) + box.set_margin_bottom(14) + box.set_margin_start(18) + box.set_margin_end(18) + + logo = SCRIPT_DIR / "oasis-logo.png" + if logo.is_file(): + try: + pb = GdkPixbuf.Pixbuf.new_from_file_at_scale(str(logo), 30, 30, True) + box.pack_start(Gtk.Image.new_from_pixbuf(pb), False, False, 0) + except Exception: + pass + + title = Gtk.Label(label="SOLAR NET HUB") + title.get_style_context().add_class("panel-title") + box.pack_start(title, False, False, 0) + + box.pack_start(Gtk.Box(), True, True, 0) # spacer + + self.header_dot = Gtk.Label(label="●") + self.header_dot.get_style_context().add_class("dot") + self.header_dot.get_style_context().add_class("dot-unknown") + box.pack_end(self.header_dot, False, False, 4) + + return box + + # ── Tab OASIS ───────────────────────────────────────────────────────── + def _make_oasis_tab(self): + scroll = Gtk.ScrolledWindow() + scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + scroll.add(box) + + # — Status card — + card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + card.get_style_context().add_class("status-card") + card.set_margin_top(16) + card.set_margin_bottom(4) + card.set_margin_start(14) + card.set_margin_end(14) + card.set_margin_top(14) + for edge in ("top", "bottom", "start", "end"): + getattr(card, f"set_margin_{edge}")(14) + + row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + row.set_margin_top(14) + row.set_margin_start(16) + row.set_margin_end(16) + + self.oasis_dot = Gtk.Label(label="●") + self.oasis_dot.get_style_context().add_class("dot") + self.oasis_dot.get_style_context().add_class("dot-unknown") + row.pack_start(self.oasis_dot, False, False, 0) + + self.oasis_state_lbl = Gtk.Label(label="Comprobando…") + self.oasis_state_lbl.get_style_context().add_class("status-main") + self.oasis_state_lbl.get_style_context().add_class("state-unknown") + row.pack_start(self.oasis_state_lbl, False, False, 0) + card.pack_start(row, False, False, 0) + + self.oasis_sub_lbl = Gtk.Label(label="") + self.oasis_sub_lbl.get_style_context().add_class("status-sub") + self.oasis_sub_lbl.set_halign(Gtk.Align.START) + self.oasis_sub_lbl.set_margin_start(16) + self.oasis_sub_lbl.set_margin_bottom(14) + card.pack_start(self.oasis_sub_lbl, False, False, 0) + + box.pack_start(card, False, False, 0) + + # — Botones fila 1 — + r1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + r1.set_halign(Gtk.Align.CENTER) + r1.set_margin_top(10) + self.btn_o_start = self._btn("▶ INICIAR", self._on_oasis_start, primary=True) + self.btn_o_stop = self._btn("■ DETENER", self._on_oasis_stop) + r1.pack_start(self.btn_o_start, False, False, 0) + r1.pack_start(self.btn_o_stop, False, False, 0) + box.pack_start(r1, False, False, 0) + + # — Botones fila 2 — + r2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + r2.set_halign(Gtk.Align.CENTER) + r2.set_margin_bottom(6) + self.btn_o_install = self._btn("⬇ INSTALAR", self._on_oasis_install) + self.btn_o_browser = self._btn("◎ ABRIR WEB", self._on_oasis_browser) + r2.pack_start(self.btn_o_install, False, False, 0) + r2.pack_start(self.btn_o_browser, False, False, 0) + box.pack_start(r2, False, False, 0) + + # — Separator + info — + sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) + sep.set_margin_top(10) + box.pack_start(sep, False, False, 0) + + grid = Gtk.Grid() + grid.set_column_spacing(14) + grid.set_row_spacing(6) + grid.set_margin_start(18) + grid.set_margin_end(18) + grid.set_margin_top(10) + grid.set_margin_bottom(14) + self.oasis_ver_val = self._info_row(grid, 0, "Versión") + self.oasis_node_val = self._info_row(grid, 1, "Node.js") + self.oasis_dir_val = self._info_row(grid, 2, "Ruta") + box.pack_start(grid, False, False, 0) + + return scroll + + # ── Tab ECOIN ───────────────────────────────────────────────────────── + def _make_ecoin_tab(self): + scroll = Gtk.ScrolledWindow() + scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + scroll.add(box) + + # — Status card — + card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) + card.get_style_context().add_class("status-card") + for edge in ("top", "bottom", "start", "end"): + getattr(card, f"set_margin_{edge}")(14) + + row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8) + row.set_margin_top(14) + row.set_margin_start(16) + row.set_margin_end(16) + + self.ecoin_dot = Gtk.Label(label="●") + self.ecoin_dot.get_style_context().add_class("dot") + self.ecoin_dot.get_style_context().add_class("dot-unknown") + row.pack_start(self.ecoin_dot, False, False, 0) + + self.ecoin_state_lbl = Gtk.Label(label="Comprobando…") + self.ecoin_state_lbl.get_style_context().add_class("status-main") + self.ecoin_state_lbl.get_style_context().add_class("state-unknown") + row.pack_start(self.ecoin_state_lbl, False, False, 0) + card.pack_start(row, False, False, 0) + + self.ecoin_sub_lbl = Gtk.Label(label="") + self.ecoin_sub_lbl.get_style_context().add_class("status-sub") + self.ecoin_sub_lbl.set_halign(Gtk.Align.START) + self.ecoin_sub_lbl.set_margin_start(16) + self.ecoin_sub_lbl.set_margin_bottom(14) + card.pack_start(self.ecoin_sub_lbl, False, False, 0) + + box.pack_start(card, False, False, 0) + + # — Botones fila 1 — + r1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + r1.set_halign(Gtk.Align.CENTER) + r1.set_margin_top(10) + self.btn_e_install = self._btn("⬇ INSTALAR", self._on_ecoin_install, primary=True) + self.btn_e_gui = self._btn("◈ ABRIR GUI", self._on_ecoin_gui) + r1.pack_start(self.btn_e_install, False, False, 0) + r1.pack_start(self.btn_e_gui, False, False, 0) + box.pack_start(r1, False, False, 0) + + # — Botones fila 2 — + r2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + r2.set_halign(Gtk.Align.CENTER) + r2.set_margin_bottom(6) + self.btn_e_wallet = self._btn("✦ CREAR WALLET", self._on_ecoin_wallet) + self.btn_e_connect = self._btn("⟳ CONECTAR", self._on_ecoin_connect) + r2.pack_start(self.btn_e_wallet, False, False, 0) + r2.pack_start(self.btn_e_connect, False, False, 0) + box.pack_start(r2, False, False, 0) + + # — Separator + info — + sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) + sep.set_margin_top(10) + box.pack_start(sep, False, False, 0) + + grid = Gtk.Grid() + grid.set_column_spacing(14) + grid.set_row_spacing(6) + grid.set_margin_start(18) + grid.set_margin_end(18) + grid.set_margin_top(10) + grid.set_margin_bottom(14) + self.ecoin_wallet_val = self._info_row(grid, 0, "Wallet") + self.ecoin_qt_val = self._info_row(grid, 1, "ecoin-qt") + self.ecoin_daemon_val = self._info_row(grid, 2, "ecoind") + box.pack_start(grid, False, False, 0) + + return scroll + + # ── Tab SISTEMA ─────────────────────────────────────────────────────── + def _make_sistema_tab(self): + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + + hdr = Gtk.Label(label="Log de actividad") + hdr.get_style_context().add_class("info-key") + hdr.set_halign(Gtk.Align.START) + hdr.set_margin_start(18) + hdr.set_margin_top(12) + hdr.set_margin_bottom(4) + box.pack_start(hdr, False, False, 0) + + scroll = Gtk.ScrolledWindow() + scroll.get_style_context().add_class("log-scroll") + scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroll.set_vexpand(True) + + self.log_view = Gtk.TextView() + self.log_view.get_style_context().add_class("log-view") + self.log_view.set_editable(False) + self.log_view.set_cursor_visible(False) + self.log_view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) + self.log_buf = self.log_view.get_buffer() + self._end_mark = self.log_buf.create_mark( + "end", self.log_buf.get_end_iter(), False + ) + scroll.add(self.log_view) + box.pack_start(scroll, True, True, 0) + + btn = self._btn("LIMPIAR LOG", self._on_clear_log) + btn.set_margin_start(18) + btn.set_margin_end(18) + btn.set_margin_top(8) + btn.set_margin_bottom(12) + btn.set_halign(Gtk.Align.CENTER) + box.pack_start(btn, False, False, 0) + + return box + + # ── Widget helpers ──────────────────────────────────────────────────── + def _btn(self, label, cb, primary=False): + b = Gtk.Button(label=label) + b.get_style_context().add_class("btn-primary" if primary else "btn") + b.connect("clicked", cb) + return b + + def _info_row(self, grid, row, key): + k = Gtk.Label(label=key) + k.get_style_context().add_class("info-key") + k.set_halign(Gtk.Align.START) + grid.attach(k, 0, row, 1, 1) + + v = Gtk.Label(label="—") + v.get_style_context().add_class("info-val") + v.set_halign(Gtk.Align.START) + grid.attach(v, 1, row, 1, 1) + return v + + # ── Dot / state helpers ─────────────────────────────────────────────── + def _dot(self, lbl, state): + for c in ("dot-running", "dot-stopped", "dot-unknown"): + lbl.get_style_context().remove_class(c) + lbl.get_style_context().add_class(f"dot-{state}") + + def _state(self, lbl, text, state): + for c in ("state-running", "state-stopped", "state-unknown"): + lbl.get_style_context().remove_class(c) + lbl.set_text(text) + lbl.get_style_context().add_class(f"state-{state}") + + # ── Status polling ──────────────────────────────────────────────────── + def _trigger_poll(self): + threading.Thread(target=self._poll_thread, daemon=True).start() + return True + + def _poll_thread(self): + o_inst = oasis_installed() + o_run = oasis_running() + o_ver = oasis_version() + o_node = node_version() + e_inst = ecoin_installed() + e_wall = ecoin_wallet_exists() + e_qt = (ECOIN_DIR / "ecoin" / "ecoin-qt").is_file() + e_dmn = (ECOIN_DIR / "ecoin" / "src" / "ecoind").is_file() + GLib.idle_add(self._apply_status, + o_inst, o_run, o_ver, o_node, + e_inst, e_wall, e_qt, e_dmn) + + def _apply_status(self, o_inst, o_run, o_ver, o_node, + e_inst, e_wall, e_qt, e_dmn): + # — Header dot — + hstate = "running" if o_run else ("stopped" if o_inst else "unknown") + self._dot(self.header_dot, hstate) + + # — OASIS — + self._dot(self.oasis_dot, "running" if o_run else ("stopped" if o_inst else "unknown")) + if o_run: + self._state(self.oasis_state_lbl, "ACTIVO", "running") + self.oasis_sub_lbl.set_text("servidor en puerto 3000") + self.btn_o_start.set_sensitive(False) + self.btn_o_stop.set_sensitive(True) + self.btn_o_browser.set_sensitive(True) + elif o_inst: + self._state(self.oasis_state_lbl, "INSTALADO", "stopped") + self.oasis_sub_lbl.set_text("servidor detenido") + self.btn_o_start.set_sensitive(True) + self.btn_o_stop.set_sensitive(False) + self.btn_o_browser.set_sensitive(False) + else: + self._state(self.oasis_state_lbl, "NO INSTALADO", "unknown") + self.oasis_sub_lbl.set_text("instala OASIS para comenzar") + self.btn_o_start.set_sensitive(False) + self.btn_o_stop.set_sensitive(False) + self.btn_o_browser.set_sensitive(False) + + self.oasis_ver_val.set_text(f"v{o_ver}" if o_ver != "—" else "—") + self.oasis_node_val.set_text(o_node if o_node != "—" else "no instalado") + self.oasis_dir_val.set_text(str(OASIS_DIR) if o_inst else "—") + + # — ECOIN — + self._dot(self.ecoin_dot, "running" if e_qt else ("stopped" if e_inst else "unknown")) + if e_inst: + self._state(self.ecoin_state_lbl, "COMPILADO", "running") + self.ecoin_sub_lbl.set_text("wallet ECOIN disponible") + self.btn_e_gui.set_sensitive(True) + self.btn_e_wallet.set_sensitive(True) + self.btn_e_connect.set_sensitive(True) + else: + self._state(self.ecoin_state_lbl, "NO INSTALADO", "unknown") + self.ecoin_sub_lbl.set_text("instala ECOIN para comenzar") + self.btn_e_gui.set_sensitive(False) + self.btn_e_wallet.set_sensitive(False) + self.btn_e_connect.set_sensitive(False) + + self.ecoin_wallet_val.set_text("Sí" if e_wall else "No") + self.ecoin_qt_val.set_text("Sí" if e_qt else "No") + self.ecoin_daemon_val.set_text("Sí" if e_dmn else "No") + + # ── Acciones ───────────────────────────────────────────────────────── + def _run(self, *args): + """Lanza installer.sh con args, redirige output al log.""" + cmd = [str(INSTALLER_SH)] + list(args) + self._log(f"$ {' '.join(cmd)}") + threading.Thread(target=self._exec_log, args=(cmd,), daemon=True).start() + GLib.idle_add(self.notebook.set_current_page, 2) + + def _exec_log(self, cmd): + try: + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, bufsize=1 + ) + for line in iter(proc.stdout.readline, ""): + GLib.idle_add(self._log, line.rstrip()) + proc.wait() + GLib.idle_add(self._log, f"── proceso terminado (código {proc.returncode}) ──") + except Exception as e: + GLib.idle_add(self._log, f"[Error]: {e}") + GLib.idle_add(self._trigger_poll) + + def _log(self, text): + self.log_buf.insert(self.log_buf.get_end_iter(), text + "\n") + self.log_view.scroll_to_mark(self._end_mark, 0.0, True, 0.0, 1.0) + return False + + # OASIS + def _on_oasis_start(self, _): self._run("--oasis-start") + def _on_oasis_install(self, _): self._run("--oasis-install") + + def _on_oasis_stop(self, _): + self._log("Deteniendo OASIS…") + threading.Thread(target=self._kill_oasis, daemon=True).start() + + def _kill_oasis(self): + try: + subprocess.run(["pkill", "-f", "node.*server.js"], timeout=5) + GLib.idle_add(self._log, "Servidor OASIS detenido.") + except Exception as e: + GLib.idle_add(self._log, f"[Error al detener]: {e}") + GLib.idle_add(self._trigger_poll) + + def _on_oasis_browser(self, _): + subprocess.Popen(["xdg-open", "http://localhost:3000"]) + self._log("Abriendo http://localhost:3000 …") + + # ECOIN + def _on_ecoin_install(self, _): self._run("--ecoin-install") + def _on_ecoin_gui(self, _): self._run("--ecoin-gui") + def _on_ecoin_wallet(self, _): self._run("--wallet-create") + def _on_ecoin_connect(self, _): self._run("--wallet-connect") + + # Sistema + def _on_clear_log(self, _): + self.log_buf.set_text("") + + +# ── Aplicación ───────────────────────────────────────────────────────────── +class OasisApp(Gtk.Application): + def __init__(self): + super().__init__(application_id="net.solarnethub.panel") + + def do_activate(self): + win = OasisPanel(self) + win.show_all() + + +def main(): + # Cargar CSS + provider = Gtk.CssProvider() + provider.load_from_data(CSS.encode("utf-8")) + Gtk.StyleContext.add_provider_for_screen( + Gdk.Screen.get_default(), + provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, + ) + + app = OasisApp() + sys.exit(app.run(sys.argv)) + + +if __name__ == "__main__": + main() diff --git a/INSTALLER_V2/Dune_Rise.otf b/INSTALLER_V2/Dune_Rise.otf new file mode 100644 index 0000000..df025f4 Binary files /dev/null and b/INSTALLER_V2/Dune_Rise.otf differ diff --git a/INSTALLER_V2/ecoin.png b/INSTALLER_V2/ecoin.png new file mode 100644 index 0000000..4308a29 Binary files /dev/null and b/INSTALLER_V2/ecoin.png differ diff --git a/INSTALLER_V2/installer.sh b/INSTALLER_V2/installer.sh new file mode 100755 index 0000000..d9a46e6 --- /dev/null +++ b/INSTALLER_V2/installer.sh @@ -0,0 +1,400 @@ +#!/usr/bin/env bash +set -euo pipefail + +# ========================= +# SOLAR NET HUB - Installer +# ========================= + +# --- Rutas --- +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +: "${SELF:=$SCRIPT_DIR/installer.sh}" # ruta absoluta a este script +INSTALLER_DIR="$SCRIPT_DIR" +CSS_SOURCE="$INSTALLER_DIR/gtk.css" +HERO_IMAGE="$INSTALLER_DIR/oasis-ecoin.png" # imagen con los dos logos (head) + +# ICONOS INDIVIDUALES (se usan en ensure_local_icons y en el .desktop) +ICON_OASIS="$INSTALLER_DIR/oasis-logo.png" +ICON_ECOIN="$INSTALLER_DIR/ecoin.png" + +APPS_DIR="$HOME/.local/share/applications" +DESKTOP_DIR="$(xdg-user-dir DESKTOP 2>/dev/null || echo "$HOME/Desktop")" + +# --- Tema local (no toca sistema) --- +NAME="SolarHubInstaller" +THEME_NAME="SolarHub" +YAD_THEME="${THEME_NAME}:dark" +ORANGE="#FF4E00" +GREEN="#27D980" + +# ========== Helpers ========== +have(){ command -v "$1" >/dev/null 2>&1; } + +# Iconos locales (sin instalar nada en el sistema/usuario) +ensure_local_icons(){ + local base="$INSTALLER_DIR/icons/hicolor/256x256/apps" + local idx="$INSTALLER_DIR/icons/hicolor/index.theme" + mkdir -p "$base" + install -m 0644 "$ICON_OASIS" "$base/oasis-logo.png" + install -m 0644 "$ICON_ECOIN" "$base/ecoin.png" + cat > "$idx" <<'EOF' +[Icon Theme] +Name=Hicolor +Comment=Local fallback for Solar Net Hub +Directories=256x256/apps + +[256x256/apps] +Size=256 +Context=Applications +Type=Fixed +EOF + # Hacer visible este tema SOLO para este proceso y descendientes + export GTK_ICON_THEME_PATH="$INSTALLER_DIR/icons${GTK_ICON_THEME_PATH:+:$GTK_ICON_THEME_PATH}" +} + +pkg_install(){ + local pkgs=("$@") + if have apt-get; then + if have pkexec; then pkexec bash -lc "apt-get update && apt-get install -y ${pkgs[*]}" || sudo apt-get update && sudo apt-get install -y "${pkgs[@]}"; + else sudo apt-get update && sudo apt-get install -y "${pkgs[@]}"; fi + elif have pacman; then + if have pkexec; then pkexec bash -lc "pacman -Sy --noconfirm ${pkgs[*]}" || sudo pacman -Sy --noconfirm "${pkgs[@]}"; + else sudo pacman -Sy --noconfirm "${pkgs[@]}"; fi + elif have dnf; then + if have pkexec; then pkexec bash -lc "dnf install -y ${pkgs[*]}" || sudo dnf install -y "${pkgs[@]}"; + else sudo dnf install -y "${pkgs[@]}"; fi + elif have zypper; then + if have pkexec; then pkexec bash -lc "zypper --non-interactive install ${pkgs[*]}" || sudo zypper --non-interactive install "${pkgs[@]}"; + else sudo zypper --non-interactive install "${pkgs[@]}"; fi + else + echo "❌ Gestor de paquetes no soportado." >&2; exit 1 + fi +} + +ensure_basics(){ + local miss=() + have yad || miss+=("yad") + if ! command -v pkexec >/dev/null 2>&1; then + if have apt-get; then miss+=("policykit-1"); else miss+=("polkit"); fi + fi + have xdg-user-dirs-update || miss+=("xdg-user-dirs") + if command -v dpkg >/dev/null 2>&1; then + dpkg -s hicolor-icon-theme >/dev/null 2>&1 || miss+=("hicolor-icon-theme") + elif command -v rpm >/dev/null 2>&1; then + rpm -q hicolor-icon-theme >/dev/null 2>&1 || miss+=("hicolor-icon-theme") + fi + if ((${#miss[@]})); then + pkg_install "${miss[@]}" + fi +} + +ensure_theme(){ + local tdir="$HOME/.themes/${THEME_NAME}/gtk-3.0" + mkdir -p "$tdir" + if [ -f "$CSS_SOURCE" ]; then + sed 's/\r$//' "$CSS_SOURCE" > "$tdir/gtk.css" + else + cat > "$tdir/gtk.css" <<'CSS' +*{background:#000;color:#E6E6E6;font-family:"Cantarell","Ubuntu","DejaVu Sans",sans-serif;font-size:12pt} +button{background:#FF4E00;color:#000;border:none;border-radius:16px;padding:12px 18px;font-weight:800} +button:hover{background:#ff6a26} button:active{background:#e24a00} +CSS + fi +} + +yad_cmd(){ GTK_THEME="$YAD_THEME" NO_AT_BRIDGE=1 yad --name="$NAME" --class="$NAME" "$@"; } + +progress_user(){ # $1 título, $2 comando (string) + bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons \ + --width=800 --height=260 --center +} +progress_root(){ # $1 título, $2 comando (string, root) + pkexec bash -lc "$2" 2>&1 | yad_cmd --progress --title="$1" --pulsate --auto-close --no-buttons \ + --width=800 --height=260 --center +} + +ensure_launcher(){ + mkdir -p "$APPS_DIR" "$DESKTOP_DIR" + local ABS_INSTALLER="$SCRIPT_DIR/installer.sh" + local ABS_ICON="$ICON_OASIS"; [ -f "$ABS_ICON" ] || ABS_ICON="$HERO_IMAGE" + + cat > "$APPS_DIR/INSTALLER.desktop" </dev/null || true + chmod 644 "$APPS_DIR/INSTALLER.desktop" 2>/dev/null || true + chmod +x "$DESKTOP_DIR/INSTALLER.desktop" 2>/dev/null || true + command -v update-desktop-database >/dev/null 2>&1 && \ + update-desktop-database "$HOME/.local/share/applications" >/dev/null 2>&1 || true +} + +ensure_user_fonts(){ + local FONTS_SRC_DIR="$INSTALLER_DIR" + local FONTS_DST_DIR="$HOME/.local/share/fonts" + local copied=0 + + mkdir -p "$FONTS_DST_DIR" + + # Copia Dune Rise (OTF/TTF) si existe en INSTALLER + for f in "$FONTS_SRC_DIR"/Dune_Rise.otf "$FONTS_SRC_DIR"/Dune_Rise.ttf; do + if [ -f "$f" ]; then + cp -f "$f" "$FONTS_DST_DIR/" && copied=1 + fi + done + + # Refresca caché si hemos copiado algo + if [ "$copied" = 1 ]; then + fc-cache -f >/dev/null 2>&1 || true + fi +} + +# ========== Estado ========== +OASIS_DIR_DEFAULT="${HOME}/oasis" +OASIS_REPO_DEFAULT="https://code.03c8.net/KrakensLab/oasis.git" +OASIS_MODEL_FILE="oasis-42-1-chat.Q4_K_M.gguf" +OASIS_MODEL_TAR="${OASIS_MODEL_FILE}.tar.gz" +OASIS_MODEL_URL="https://solarnethub.com/code/models/${OASIS_MODEL_TAR}" +oasis_installed(){ local d="${1:-$OASIS_DIR_DEFAULT}"; [[ -d "$d/src/server/node_modules" && -f "$d/AI/$OASIS_MODEL_FILE" ]]; } + +ECOIN_DIR_DEFAULT="${HOME}/ecoin" +ECOIN_REPO_DEFAULT="https://github.com/epsylon/ecoin" +ecoin_installed(){ local d="${1:-$ECOIN_DIR_DEFAULT}"; [[ -x "$d/ecoin/ecoin-qt" || -x "$d/ecoin/src/ecoind" ]]; } +ecoin_wallet_exists(){ [ -f "$HOME/.ecoin/wallet.dat" ]; } + +# ========== OASIS ========== +node_setup_cmd(){ cat < "$CONF" </dev/null || true +ensure_local_icons +ensure_theme +ensure_launcher + +case "${1:-}" in + --oasis-install) oasis_quick_install 22 "$OASIS_REPO_DEFAULT" "$OASIS_DIR_DEFAULT" ;; + --oasis-start) oasis_quick_start "$OASIS_DIR_DEFAULT" ;; + --ecoin-install) ecoin_quick_install "$ECOIN_REPO_DEFAULT" "$ECOIN_DIR_DEFAULT" ;; + --wallet-create) progress_user "ECOIN: Crear cartera" "$(ecoin_create_wallet_cmd)" ;; + --wallet-connect)progress_user "ECOIN: Conectar cartera" "$(ecoin_connect_wallet_cmd)" ;; + --ecoin-gui) progress_user "ECOIN: Wallet (Qt)" "$(ecoin_qt_cmd "$ECOIN_DIR_DEFAULT")" ;; + *) home_single_dialog ;; +esac diff --git a/INSTALLER_V2/oasis-ecoin.png b/INSTALLER_V2/oasis-ecoin.png new file mode 100644 index 0000000..4d870e8 Binary files /dev/null and b/INSTALLER_V2/oasis-ecoin.png differ diff --git a/INSTALLER_V2/oasis-logito.png b/INSTALLER_V2/oasis-logito.png new file mode 100644 index 0000000..f28986c Binary files /dev/null and b/INSTALLER_V2/oasis-logito.png differ diff --git a/INSTALLER_V2/oasis-logo.png b/INSTALLER_V2/oasis-logo.png new file mode 100644 index 0000000..ccf33ce Binary files /dev/null and b/INSTALLER_V2/oasis-logo.png differ diff --git a/INSTALLER_V2/panel.py b/INSTALLER_V2/panel.py new file mode 100644 index 0000000..a58c74f --- /dev/null +++ b/INSTALLER_V2/panel.py @@ -0,0 +1,746 @@ +#!/usr/bin/env python3 +""" +OASIS Control Panel v2 — Solar Net Hub +Panel compacto estilo Mullvad, version mejorada. +""" + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib, Gdk, GdkPixbuf + +import subprocess +import os +import sys +import signal +import threading +import json +import shutil +from pathlib import Path + +# ── Rutas ────────────────────────────────────────────────────────────────── +SCRIPT_DIR = Path(__file__).parent.resolve() +INSTALLER_SH = SCRIPT_DIR / "installer.sh" +OASIS_DIR = Path.home() / "oasis" +ECOIN_DIR = Path.home() / "ecoin" +MODEL_FILE = "oasis-42-1-chat.Q4_K_M.gguf" + +# ── CSS ──────────────────────────────────────────────────────────────────── +CSS = """ +/* === FUERZA NEGRO EN TODO — sin excepciones === */ +*, +widget, box, grid, stack, overlay, +scrolledwindow, viewport, clamp, +notebook, frame, paned, +label, button, entry, textview, +separator, headerbar, actionbar, +menubar, menu, menuitem, +treeview, iconview, flowbox, +progressbar, levelbar, scale, +spinbutton, combobox, popover, +revealer, expander, listbox, row { + background-color: #000000; + background: #000000; + color: #FF4E00; + border-color: #1A1A1A; + outline-color: transparent; + box-shadow: none; + text-shadow: none; + font-family: "Dune Rise", "Cantarell", "Ubuntu", "DejaVu Sans", sans-serif; +} + +/* El texto dentro de textview tambien */ +textview text, +textview text selection { + background-color: #000000; + color: #FF4E00; +} + +/* === HEADER === */ +box.header { + background-color: #080808; + border-bottom: 1px solid #1E1E1E; +} + +label.header-title { + font-size: 13pt; + font-weight: bold; + color: #FF4E00; + letter-spacing: 4px; + background-color: transparent; +} + +label.header-sub { + font-size: 7pt; + color: #552200; + letter-spacing: 2px; + background-color: transparent; +} + +/* === STATUS CARD === */ +box.status-card { + background-color: #080808; + border-radius: 10px; + border: 1px solid #1A1A1A; +} + +box.card-border-running { + background-color: #27D980; + border-radius: 8px 0px 0px 8px; + min-width: 5px; +} +box.card-border-stopped { + background-color: #FF4E00; + border-radius: 8px 0px 0px 8px; + min-width: 5px; +} +box.card-border-unknown { + background-color: #1A1A1A; + border-radius: 8px 0px 0px 8px; + min-width: 5px; +} + +label.state-text { + font-size: 17pt; + font-weight: bold; + letter-spacing: 2px; + background-color: transparent; +} +label.state-running { color: #27D980; } +label.state-stopped { color: #FF4E00; } +label.state-unknown { color: #883300; } + +label.state-sub { + font-size: 8pt; + color: #552200; + letter-spacing: 1px; + background-color: transparent; +} + +/* Dot */ +label.header-dot { font-size: 8pt; background-color: transparent; } +label.dot-running { color: #27D980; } +label.dot-stopped { color: #FF4E00; } +label.dot-unknown { color: #441100; } + +/* === TABS === */ +notebook > header, +notebook > header tabs, +notebook > header tab, +tab { + background-color: #000000; + background: #000000; + border: none; + box-shadow: none; +} + +tab { + color: #552200; + border-bottom: 3px solid transparent; + padding: 11px 24px; + font-size: 8pt; + letter-spacing: 2px; + font-weight: bold; +} +tab:checked { + color: #FF4E00; + border-bottom: 3px solid #FF4E00; +} +tab:hover { color: #BB3300; } + +/* === BOTONES === */ +button, button * { + background-color: #000000; + background: #000000; + color: #FF4E00; + box-shadow: none; + text-shadow: none; +} + +button.btn { + border: 1px solid #FF4E00; + border-radius: 20px; + padding: 9px 18px; + margin: 5px 5px; + font-size: 7.5pt; + font-weight: bold; + letter-spacing: 1px; + min-width: 128px; +} +button.btn:hover, button.btn:hover * { + background-color: #27D980; + background: #27D980; + color: #000000; + border-color: #27D980; +} +button.btn:active, button.btn:active * { + background-color: #1fa865; + background: #1fa865; + color: #000000; +} +button.btn:disabled, button.btn:disabled * { + color: #2A0E00; + border-color: #1A0A00; +} + +button.btn-primary { + background-color: #FF4E00; + background: #FF4E00; + color: #000000; + border: 1px solid #FF4E00; + border-radius: 20px; + padding: 9px 18px; + margin: 5px 5px; + font-size: 7.5pt; + font-weight: bold; + letter-spacing: 1px; + min-width: 128px; +} +button.btn-primary * { background-color: transparent; color: #000000; } +button.btn-primary:hover, button.btn-primary:hover * { + background-color: #27D980; + background: #27D980; + color: #000000; + border-color: #27D980; +} +button.btn-primary:disabled, button.btn-primary:disabled * { + background-color: #1A0800; + background: #1A0800; + color: #2A1000; + border-color: #1A0800; +} + +/* === INFO === */ +box.info-box { + background-color: #050505; + border-top: 1px solid #111111; +} +label.info-key { + color: #662200; + font-size: 8pt; + letter-spacing: 1px; + min-width: 80px; + background-color: transparent; +} +label.info-val { + color: #FF5500; + font-size: 8pt; + background-color: transparent; +} + +/* === LOG === */ +textview.log-view, +textview.log-view text { + background-color: #030303; + color: #27D980; + font-family: "DejaVu Sans Mono", "Monospace", monospace; + font-size: 7.5pt; + padding: 10px; +} +scrolledwindow.log-scroll { + border: 1px solid #181818; + border-radius: 8px; + margin: 6px 14px; +} + +separator { + background-color: #0F0F0F; + min-height: 1px; +} + +label.section-lbl { + color: #441100; + font-size: 7pt; + letter-spacing: 2px; + background-color: transparent; +} +""" + +# ── Helpers de estado ────────────────────────────────────────────────────── +def oasis_installed(): + return ( + (OASIS_DIR / "src" / "server" / "node_modules").is_dir() + and (OASIS_DIR / "AI" / MODEL_FILE).is_file() + ) + +def oasis_version(): + pkg = OASIS_DIR / "src" / "server" / "package.json" + if pkg.is_file(): + try: + return json.loads(pkg.read_text()).get("version", "—") + except Exception: + pass + return "—" + +def oasis_running(): + try: + r = subprocess.run(["pgrep", "-f", "node.*server.js"], + capture_output=True, timeout=2) + return r.returncode == 0 + except Exception: + return False + +def ecoin_installed(): + return ( + (ECOIN_DIR / "ecoin" / "ecoin-qt").is_file() + or (ECOIN_DIR / "ecoin" / "src" / "ecoind").is_file() + ) + +def ecoin_wallet_exists(): + return (Path.home() / ".ecoin" / "wallet.dat").is_file() + +def node_version(): + try: + r = subprocess.run(["node", "--version"], + capture_output=True, text=True, timeout=3) + return r.stdout.strip() + except Exception: + return "—" + +# ── Panel ────────────────────────────────────────────────────────────────── +class OasisPanel(Gtk.ApplicationWindow): + + def __init__(self, app): + super().__init__(application=app, title="SOLAR NET HUB") + self.set_default_size(390, 620) + self.set_resizable(False) + self.set_position(Gtk.WindowPosition.CENTER) + + self._build_ui() + GLib.timeout_add_seconds(3, self._trigger_poll) + self._trigger_poll() + + # ── Layout ──────────────────────────────────────────────────────────── + def _build_ui(self): + root = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + self.add(root) + root.pack_start(self._make_header(), False, False, 0) + nb = Gtk.Notebook() + nb.set_show_border(False) + root.pack_start(nb, True, True, 0) + self.notebook = nb + nb.append_page(self._make_oasis_tab(), self._tab_lbl("OASIS")) + nb.append_page(self._make_ecoin_tab(), self._tab_lbl("ECOIN")) + nb.append_page(self._make_sistema_tab(), self._tab_lbl("SISTEMA")) + + def _tab_lbl(self, t): + return Gtk.Label(label=t) + + # ── Header ──────────────────────────────────────────────────────────── + def _make_header(self): + outer = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + outer.get_style_context().add_class("header") + outer.set_margin_top(12) + outer.set_margin_bottom(12) + outer.set_margin_start(16) + outer.set_margin_end(16) + + # Logo + logo_path = SCRIPT_DIR / "oasis-logo.png" + if logo_path.is_file(): + try: + pb = GdkPixbuf.Pixbuf.new_from_file_at_scale(str(logo_path), 38, 38, True) + outer.pack_start(Gtk.Image.new_from_pixbuf(pb), False, False, 0) + except Exception: + pass + + # Titulo + subtitulo + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) + vbox.set_margin_start(10) + vbox.set_valign(Gtk.Align.CENTER) + + title = Gtk.Label(label="SOLAR NET HUB") + title.get_style_context().add_class("header-title") + title.set_halign(Gtk.Align.START) + vbox.pack_start(title, False, False, 0) + + sub = Gtk.Label(label="OASIS + ECOIN CONTROL PANEL") + sub.get_style_context().add_class("header-sub") + sub.set_halign(Gtk.Align.START) + vbox.pack_start(sub, False, False, 0) + + outer.pack_start(vbox, True, True, 0) + + # Dot de estado global + self.header_dot = Gtk.Label(label="●") + self.header_dot.get_style_context().add_class("header-dot") + self.header_dot.get_style_context().add_class("dot-unknown") + outer.pack_end(self.header_dot, False, False, 0) + + return outer + + # ── OASIS Tab ───────────────────────────────────────────────────────── + def _make_oasis_tab(self): + scroll = Gtk.ScrolledWindow() + scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + scroll.add(box) + + # Status card con borde lateral de color + card_wrap = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + card_wrap.get_style_context().add_class("status-card") + card_wrap.set_margin_top(14) + card_wrap.set_margin_bottom(6) + card_wrap.set_margin_start(14) + card_wrap.set_margin_end(14) + + # Borde lateral (color dinamico) + self.oasis_border = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.oasis_border.get_style_context().add_class("card-border-unknown") + card_wrap.pack_start(self.oasis_border, False, False, 0) + + # Contenido de la card + card_inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4) + card_inner.set_margin_top(14) + card_inner.set_margin_bottom(14) + card_inner.set_margin_start(14) + card_inner.set_margin_end(14) + + self.oasis_state_lbl = Gtk.Label(label="COMPROBANDO") + self.oasis_state_lbl.get_style_context().add_class("state-text") + self.oasis_state_lbl.get_style_context().add_class("state-unknown") + self.oasis_state_lbl.set_halign(Gtk.Align.START) + card_inner.pack_start(self.oasis_state_lbl, False, False, 0) + + self.oasis_sub_lbl = Gtk.Label(label="") + self.oasis_sub_lbl.get_style_context().add_class("state-sub") + self.oasis_sub_lbl.set_halign(Gtk.Align.START) + card_inner.pack_start(self.oasis_sub_lbl, False, False, 0) + + card_wrap.pack_start(card_inner, True, True, 0) + box.pack_start(card_wrap, False, False, 0) + + # Botones fila 1 + r1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + r1.set_halign(Gtk.Align.CENTER) + r1.set_margin_top(8) + self.btn_o_start = self._btn("▶ INICIAR", self._on_oasis_start, primary=True) + self.btn_o_stop = self._btn("■ DETENER", self._on_oasis_stop) + r1.pack_start(self.btn_o_start, False, False, 0) + r1.pack_start(self.btn_o_stop, False, False, 0) + box.pack_start(r1, False, False, 0) + + # Botones fila 2 + r2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + r2.set_halign(Gtk.Align.CENTER) + r2.set_margin_bottom(8) + self.btn_o_install = self._btn("⬇ INSTALAR", self._on_oasis_install) + self.btn_o_browser = self._btn("◎ ABRIR WEB", self._on_oasis_browser) + r2.pack_start(self.btn_o_install, False, False, 0) + r2.pack_start(self.btn_o_browser, False, False, 0) + box.pack_start(r2, False, False, 0) + + # Info + info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + info_box.get_style_context().add_class("info-box") + info_box.set_margin_top(6) + grid = Gtk.Grid() + grid.set_column_spacing(12) + grid.set_row_spacing(7) + grid.set_margin_start(18) + grid.set_margin_end(18) + grid.set_margin_top(12) + grid.set_margin_bottom(12) + self.oasis_ver_val = self._info_row(grid, 0, "VERSION") + self.oasis_node_val = self._info_row(grid, 1, "NODE.JS") + self.oasis_dir_val = self._info_row(grid, 2, "RUTA") + info_box.pack_start(grid, False, False, 0) + box.pack_start(info_box, False, False, 0) + + return scroll + + # ── ECOIN Tab ───────────────────────────────────────────────────────── + def _make_ecoin_tab(self): + scroll = Gtk.ScrolledWindow() + scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + scroll.add(box) + + # Status card + card_wrap = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + card_wrap.get_style_context().add_class("status-card") + for edge in ("top", "bottom", "start", "end"): + getattr(card_wrap, f"set_margin_{edge}")(14) + card_wrap.set_margin_bottom(6) + + self.ecoin_border = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.ecoin_border.get_style_context().add_class("card-border-unknown") + card_wrap.pack_start(self.ecoin_border, False, False, 0) + + card_inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4) + card_inner.set_margin_top(14) + card_inner.set_margin_bottom(14) + card_inner.set_margin_start(14) + card_inner.set_margin_end(14) + + self.ecoin_state_lbl = Gtk.Label(label="COMPROBANDO") + self.ecoin_state_lbl.get_style_context().add_class("state-text") + self.ecoin_state_lbl.get_style_context().add_class("state-unknown") + self.ecoin_state_lbl.set_halign(Gtk.Align.START) + card_inner.pack_start(self.ecoin_state_lbl, False, False, 0) + + self.ecoin_sub_lbl = Gtk.Label(label="") + self.ecoin_sub_lbl.get_style_context().add_class("state-sub") + self.ecoin_sub_lbl.set_halign(Gtk.Align.START) + card_inner.pack_start(self.ecoin_sub_lbl, False, False, 0) + + card_wrap.pack_start(card_inner, True, True, 0) + box.pack_start(card_wrap, False, False, 0) + + r1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + r1.set_halign(Gtk.Align.CENTER) + r1.set_margin_top(8) + self.btn_e_install = self._btn("⬇ INSTALAR", self._on_ecoin_install, primary=True) + self.btn_e_gui = self._btn("◈ ABRIR GUI", self._on_ecoin_gui) + r1.pack_start(self.btn_e_install, False, False, 0) + r1.pack_start(self.btn_e_gui, False, False, 0) + box.pack_start(r1, False, False, 0) + + r2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0) + r2.set_halign(Gtk.Align.CENTER) + r2.set_margin_bottom(8) + self.btn_e_wallet = self._btn("✦ CREAR WALLET", self._on_ecoin_wallet) + self.btn_e_connect = self._btn("⟳ CONECTAR", self._on_ecoin_connect) + r2.pack_start(self.btn_e_wallet, False, False, 0) + r2.pack_start(self.btn_e_connect, False, False, 0) + box.pack_start(r2, False, False, 0) + + info_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + info_box.get_style_context().add_class("info-box") + info_box.set_margin_top(6) + grid = Gtk.Grid() + grid.set_column_spacing(12) + grid.set_row_spacing(7) + grid.set_margin_start(18) + grid.set_margin_end(18) + grid.set_margin_top(12) + grid.set_margin_bottom(12) + self.ecoin_wallet_val = self._info_row(grid, 0, "WALLET") + self.ecoin_qt_val = self._info_row(grid, 1, "ECOIN-QT") + self.ecoin_daemon_val = self._info_row(grid, 2, "ECOIND") + info_box.pack_start(grid, False, False, 0) + box.pack_start(info_box, False, False, 0) + + return scroll + + # ── SISTEMA Tab ─────────────────────────────────────────────────────── + def _make_sistema_tab(self): + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + + hdr = Gtk.Label(label="LOG DE ACTIVIDAD") + hdr.get_style_context().add_class("section-lbl") + hdr.set_halign(Gtk.Align.START) + hdr.set_margin_start(18) + hdr.set_margin_top(14) + hdr.set_margin_bottom(6) + box.pack_start(hdr, False, False, 0) + + scroll = Gtk.ScrolledWindow() + scroll.get_style_context().add_class("log-scroll") + scroll.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroll.set_vexpand(True) + + self.log_view = Gtk.TextView() + self.log_view.get_style_context().add_class("log-view") + self.log_view.set_editable(False) + self.log_view.set_cursor_visible(False) + self.log_view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) + self.log_buf = self.log_view.get_buffer() + self._end_mark = self.log_buf.create_mark("end", self.log_buf.get_end_iter(), False) + scroll.add(self.log_view) + box.pack_start(scroll, True, True, 0) + + btn = self._btn("LIMPIAR LOG", self._on_clear_log) + btn.set_halign(Gtk.Align.CENTER) + btn.set_margin_top(8) + btn.set_margin_bottom(12) + box.pack_start(btn, False, False, 0) + + return box + + # ── Widget helpers ──────────────────────────────────────────────────── + def _btn(self, label, cb, primary=False): + b = Gtk.Button(label=label) + b.get_style_context().add_class("btn-primary" if primary else "btn") + b.connect("clicked", cb) + return b + + def _info_row(self, grid, row, key): + k = Gtk.Label(label=key) + k.get_style_context().add_class("info-key") + k.set_halign(Gtk.Align.START) + grid.attach(k, 0, row, 1, 1) + v = Gtk.Label(label="—") + v.get_style_context().add_class("info-val") + v.set_halign(Gtk.Align.START) + grid.attach(v, 1, row, 1, 1) + return v + + # ── Card border helper ──────────────────────────────────────────────── + def _set_border(self, widget, state): + for c in ("card-border-running", "card-border-stopped", "card-border-unknown"): + widget.get_style_context().remove_class(c) + widget.get_style_context().add_class(f"card-border-{state}") + + def _set_dot(self, lbl, state): + for c in ("dot-running", "dot-stopped", "dot-unknown"): + lbl.get_style_context().remove_class(c) + lbl.get_style_context().add_class(f"dot-{state}") + + def _set_state(self, lbl, text, state): + for c in ("state-running", "state-stopped", "state-unknown"): + lbl.get_style_context().remove_class(c) + lbl.set_text(text) + lbl.get_style_context().add_class(f"state-{state}") + + # ── Status polling ──────────────────────────────────────────────────── + def _trigger_poll(self): + threading.Thread(target=self._poll_thread, daemon=True).start() + return True + + def _poll_thread(self): + o_inst = oasis_installed() + o_run = oasis_running() + o_ver = oasis_version() + o_node = node_version() + e_inst = ecoin_installed() + e_wall = ecoin_wallet_exists() + e_qt = (ECOIN_DIR / "ecoin" / "ecoin-qt").is_file() + e_dmn = (ECOIN_DIR / "ecoin" / "src" / "ecoind").is_file() + GLib.idle_add(self._apply_status, + o_inst, o_run, o_ver, o_node, + e_inst, e_wall, e_qt, e_dmn) + + def _apply_status(self, o_inst, o_run, o_ver, o_node, + e_inst, e_wall, e_qt, e_dmn): + # Header dot + h = "running" if o_run else ("stopped" if o_inst else "unknown") + self._set_dot(self.header_dot, h) + + # OASIS card + o_state = "running" if o_run else ("stopped" if o_inst else "unknown") + self._set_border(self.oasis_border, o_state) + if o_run: + self._set_state(self.oasis_state_lbl, "ACTIVO", "running") + self.oasis_sub_lbl.set_text("servidor corriendo en puerto 3000") + self.btn_o_start.set_sensitive(False) + self.btn_o_stop.set_sensitive(True) + self.btn_o_browser.set_sensitive(True) + elif o_inst: + self._set_state(self.oasis_state_lbl, "INSTALADO", "stopped") + self.oasis_sub_lbl.set_text("servidor detenido") + self.btn_o_start.set_sensitive(True) + self.btn_o_stop.set_sensitive(False) + self.btn_o_browser.set_sensitive(False) + else: + self._set_state(self.oasis_state_lbl, "NO INSTALADO", "unknown") + self.oasis_sub_lbl.set_text("instala OASIS para comenzar") + self.btn_o_start.set_sensitive(False) + self.btn_o_stop.set_sensitive(False) + self.btn_o_browser.set_sensitive(False) + + self.oasis_ver_val.set_text(f"v{o_ver}" if o_ver != "—" else "—") + self.oasis_node_val.set_text(o_node if o_node != "—" else "no instalado") + self.oasis_dir_val.set_text(str(OASIS_DIR) if o_inst else "—") + + # ECOIN card + e_state = "running" if e_qt else ("stopped" if e_inst else "unknown") + self._set_border(self.ecoin_border, e_state) + if e_inst: + self._set_state(self.ecoin_state_lbl, "COMPILADO", "running") + self.ecoin_sub_lbl.set_text("wallet ECOIN disponible") + self.btn_e_gui.set_sensitive(True) + self.btn_e_wallet.set_sensitive(True) + self.btn_e_connect.set_sensitive(True) + else: + self._set_state(self.ecoin_state_lbl, "NO INSTALADO", "unknown") + self.ecoin_sub_lbl.set_text("instala ECOIN para comenzar") + self.btn_e_gui.set_sensitive(False) + self.btn_e_wallet.set_sensitive(False) + self.btn_e_connect.set_sensitive(False) + + self.ecoin_wallet_val.set_text("Si" if e_wall else "No") + self.ecoin_qt_val.set_text("Si" if e_qt else "No") + self.ecoin_daemon_val.set_text("Si" if e_dmn else "No") + + # ── Acciones ───────────────────────────────────────────────────────── + def _run(self, *args): + cmd = [str(INSTALLER_SH)] + list(args) + self._log(f"$ {' '.join(cmd)}") + threading.Thread(target=self._exec_log, args=(cmd,), daemon=True).start() + GLib.idle_add(self.notebook.set_current_page, 2) + + def _exec_log(self, cmd): + try: + proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + text=True, bufsize=1 + ) + for line in iter(proc.stdout.readline, ""): + GLib.idle_add(self._log, line.rstrip()) + proc.wait() + GLib.idle_add(self._log, f"-- proceso terminado (codigo {proc.returncode}) --") + except Exception as e: + GLib.idle_add(self._log, f"[Error]: {e}") + GLib.idle_add(self._trigger_poll) + + def _log(self, text): + self.log_buf.insert(self.log_buf.get_end_iter(), text + "\n") + self.log_view.scroll_to_mark(self._end_mark, 0.0, True, 0.0, 1.0) + return False + + def _on_oasis_start(self, _): self._run("--oasis-start") + def _on_oasis_install(self, _): self._run("--oasis-install") + + def _on_oasis_stop(self, _): + self._log("Deteniendo OASIS...") + threading.Thread(target=self._kill_oasis, daemon=True).start() + + def _kill_oasis(self): + try: + subprocess.run(["pkill", "-f", "node.*server.js"], timeout=5) + GLib.idle_add(self._log, "Servidor OASIS detenido.") + except Exception as e: + GLib.idle_add(self._log, f"[Error al detener]: {e}") + GLib.idle_add(self._trigger_poll) + + def _on_oasis_browser(self, _): + subprocess.Popen(["xdg-open", "http://localhost:3000"]) + self._log("Abriendo http://localhost:3000 ...") + + def _on_ecoin_install(self, _): self._run("--ecoin-install") + def _on_ecoin_gui(self, _): self._run("--ecoin-gui") + def _on_ecoin_wallet(self, _): self._run("--wallet-create") + def _on_ecoin_connect(self, _): self._run("--wallet-connect") + + def _on_clear_log(self, _): + self.log_buf.set_text("") + + +# ── App ──────────────────────────────────────────────────────────────────── +class OasisApp(Gtk.Application): + def __init__(self): + super().__init__(application_id="net.solarnethub.panel.v2") + + def do_activate(self): + win = OasisPanel(self) + win.show_all() + + +def main(): + # Manejo limpio de Ctrl+C — sin traceback + signal.signal(signal.SIGINT, signal.SIG_DFL) + + provider = Gtk.CssProvider() + provider.load_from_data(CSS.encode("utf-8")) + Gtk.StyleContext.add_provider_for_screen( + Gdk.Screen.get_default(), + provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, + ) + + app = OasisApp() + sys.exit(app.run(sys.argv)) + + +if __name__ == "__main__": + main() diff --git a/INSTALLER_V3/__pycache__/panel.cpython-311.pyc b/INSTALLER_V3/__pycache__/panel.cpython-311.pyc deleted file mode 100644 index f213c4f..0000000 Binary files a/INSTALLER_V3/__pycache__/panel.cpython-311.pyc and /dev/null differ diff --git a/INSTALLER_V3/panel.py b/INSTALLER_V3/panel.py deleted file mode 100755 index 78bf949..0000000 --- a/INSTALLER_V3/panel.py +++ /dev/null @@ -1,532 +0,0 @@ -#!/usr/bin/env python3 -""" -OASIS Control Panel v3 — WebKit2GTK -UI en HTML/CSS/JS dentro de ventana GTK nativa. -Sin interferencias del tema GTK — control visual total como Electron. -""" - -import gi -gi.require_version('Gtk', '3.0') - -# Intentar WebKit2 4.1 primero, luego 4.0 -_wk = None -for _v in ('4.1', '4.0'): - try: - gi.require_version('WebKit2', _v) - _wk = _v - break - except ValueError: - pass -if _wk is None: - print("ERROR: WebKit2GTK no encontrado. Instala: gir1.2-webkit2-4.0") - raise SystemExit(1) - -from gi.repository import Gtk, WebKit2, GLib, Gdk - -import subprocess -import sys -import signal -import threading -import json -from pathlib import Path - -# ── Rutas base ───────────────────────────────────────────────────────────── -SCRIPT_DIR = Path(__file__).parent.resolve() -INSTALLER_SH = SCRIPT_DIR / "installer.sh" -ECOIN_DIR = Path.home() / "ecoin" - -# Estado persistente (mismo fichero que usa el installer original) -_STATE_FILE = Path.home() / ".config" / "oasis-installer" / "state" - -# Candidatos donde puede estar OASIS instalado -_OASIS_CANDIDATES = [ - Path.home() / "COFRE" / "CODERS" / "oasis", - Path.home() / "oasis", - Path.home() / "Projects" / "oasis", - Path.home() / "Proyectos" / "oasis", - Path.home() / "Documentos" / "oasis", - Path.home() / "Documents" / "oasis", - Path.home() / "src" / "oasis", -] - -# ── Detección OASIS ──────────────────────────────────────────────────────── -def _oasis_valid(d: Path) -> bool: - """Flexible: cualquiera de estas señales indica que OASIS está ahí.""" - return ( - (d / "oasis.sh").is_file() - or (d / "src" / "server" / "server.js").is_file() - or (d / "src" / "server" / "node_modules").is_dir() - ) - -def _read_saved_dir() -> Path | None: - """Lee la ruta guardada por el installer original.""" - if _STATE_FILE.is_file(): - for line in _STATE_FILE.read_text().splitlines(): - if line.startswith("OASIS_DIR="): - p = Path(line[10:].strip()) - if p.is_dir(): - return p - return None - -def find_oasis_dir() -> Path: - """Devuelve la ruta de OASIS encontrada o ~/oasis como fallback.""" - candidates = [] - saved = _read_saved_dir() - if saved: - candidates.append(saved) - candidates.extend(_OASIS_CANDIDATES) - for d in candidates: - if d.is_dir() and _oasis_valid(d): - return d - return Path.home() / "oasis" - -def oasis_installed() -> bool: - return _oasis_valid(find_oasis_dir()) - -def oasis_version() -> str: - pkg = find_oasis_dir() / "src" / "server" / "package.json" - if pkg.is_file(): - try: - return json.loads(pkg.read_text()).get("version", "") - except Exception: - pass - return "" - -def oasis_running(port: int = 3000) -> bool: - """Comprueba si hay algo escuchando en el puerto de OASIS (igual que el installer).""" - try: - r = subprocess.run( - ["ss", "-lnt", f"sport = :{port}"], - capture_output=True, text=True, timeout=2 - ) - return str(port) in r.stdout - except Exception: - return False - -# ── Detección ECOIN ──────────────────────────────────────────────────────── -_ECOIN_CANDIDATES = [ - Path.home() / "ecoin", - Path.home() / "COFRE" / "CODERS" / "ecoin", - Path.home() / "Projects" / "ecoin", -] - -def find_ecoin_dir() -> Path: - for d in _ECOIN_CANDIDATES: - if (d / "ecoin" / "ecoin-qt").is_file() or \ - (d / "ecoin" / "src" / "ecoind").is_file(): - return d - return Path.home() / "ecoin" - -def ecoin_installed() -> bool: - d = find_ecoin_dir() - return (d / "ecoin" / "ecoin-qt").is_file() or \ - (d / "ecoin" / "src" / "ecoind").is_file() - -def ecoin_wallet_exists() -> bool: - return (Path.home() / ".ecoin" / "wallet.dat").is_file() - -def ecoin_rpc_config() -> dict: - """Lee credenciales RPC de ~/.ecoin/ecoin.conf.""" - conf = Path.home() / ".ecoin" / "ecoin.conf" - cfg = {"rpcuser": "", "rpcpassword": "", "rpcport": "7474", "rpchost": "127.0.0.1"} - if conf.is_file(): - for line in conf.read_text().splitlines(): - line = line.strip() - if line.startswith("#") or "=" not in line: - continue - k, v = line.split("=", 1) - k = k.strip() - if k in cfg: - cfg[k] = v.strip() - return cfg - -def ecoin_rpc(method, params=None): - """Llama al RPC de ecoind. Devuelve (result, error).""" - import http.client, base64 as _b64 - cfg = ecoin_rpc_config() - auth = _b64.b64encode(f"{cfg['rpcuser']}:{cfg['rpcpassword']}".encode()).decode() - body = json.dumps({"method": method, "params": params or [], "id": 1}).encode() - try: - conn = http.client.HTTPConnection(cfg["rpchost"], int(cfg["rpcport"]), timeout=3) - conn.request("POST", "/", body, - {"Content-Type": "application/json", - "Authorization": f"Basic {auth}"}) - resp = conn.getresponse() - data = json.loads(resp.read()) - return data.get("result"), data.get("error") - except Exception as e: - return None, str(e) - -def ecoin_running() -> bool: - result, _ = ecoin_rpc("getinfo") - return result is not None - -def node_version() -> str: - try: - r = subprocess.run(["node", "--version"], - capture_output=True, text=True, timeout=3) - return r.stdout.strip() - except Exception: - return "" - - -# ── Panel principal ──────────────────────────────────────────────────────── -class OasisPanel: - - def __init__(self): - self._alive = True - self._oasis_proc = None # proceso OASIS activo - - self.win = Gtk.Window() - self.win.set_title("SOLAR NET HUB") - self.win.set_default_size(390, 620) - self.win.set_resizable(False) - self.win.set_position(Gtk.WindowPosition.CENTER) - self.win.connect("destroy", self._on_destroy) - - # Fondo negro en la ventana GTK para evitar parpadeo blanco al cargar - css = b"window { background-color: #000000; }" - provider = Gtk.CssProvider() - provider.load_from_data(css) - self.win.get_style_context().add_provider( - provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION - ) - - # ── WebKit2 ─────────────────────────────────────────────────────── - settings = WebKit2.Settings() - settings.set_enable_javascript(True) - settings.set_allow_file_access_from_file_urls(True) - settings.set_allow_universal_access_from_file_urls(True) - settings.set_enable_page_cache(False) - - self.webview = WebKit2.WebView() - self.webview.set_settings(settings) - self.webview.set_background_color(Gdk.RGBA(0, 0, 0, 1)) - - # Bridge JS→Python: interceptar navegaciones a oasis://accion - self.webview.connect("decide-policy", self._on_policy) - - self.win.add(self.webview) - - html_file = SCRIPT_DIR / "ui" / "index.html" - self.webview.load_uri(f"file://{html_file}") - - self.win.show_all() - - # Poll inicial tras 1.5 s (tiempo para que cargue el HTML) - GLib.timeout_add(1500, self._initial_poll) - # Poll continuo cada 3 s - GLib.timeout_add_seconds(3, self._poll) - - def _on_destroy(self, *_): - self._alive = False - Gtk.main_quit() - - # ── Polling ─────────────────────────────────────────────────────────── - def _initial_poll(self): - self._poll() - return False - - def _poll(self): - threading.Thread(target=self._poll_thread, daemon=True).start() - return True - - def _poll_thread(self): - o_dir = find_oasis_dir() - o_inst = _oasis_valid(o_dir) - o_run = oasis_running() - o_ver = oasis_version() - o_node = node_version() - - e_dir = find_ecoin_dir() - e_inst = ecoin_installed() - e_wall = ecoin_wallet_exists() - e_qt = (e_dir / "ecoin" / "ecoin-qt").is_file() - e_dmn = (e_dir / "ecoin" / "src" / "ecoind").is_file() - e_run = ecoin_running() - - # Info RPC solo si el daemon está corriendo - e_info = {} - e_balance = None - if e_run: - info, _ = ecoin_rpc("getinfo") - bal, _ = ecoin_rpc("getbalance") - e_info = info or {} - e_balance = bal - - data = { - "oasis_installed": o_inst, - "oasis_running": o_run, - "oasis_version": o_ver, - "node_version": o_node, - "oasis_dir": str(o_dir) if o_inst else "", - "ecoin_installed": e_inst, - "ecoin_running": e_run, - "ecoin_wallet": e_wall, - "ecoin_qt": e_qt, - "ecoin_daemon": e_dmn, - "ecoin_dir": str(e_dir) if e_inst else "", - "ecoin_balance": e_balance, - "ecoin_blocks": e_info.get("blocks", None), - "ecoin_connections": e_info.get("connections", None), - } - GLib.idle_add(self._js, f"updateStatus({json.dumps(data)})") - - # ── JS helpers ──────────────────────────────────────────────────────── - def _js(self, code): - if self._alive: - self.webview.run_javascript(code, None, None, None) - return False - - def _log(self, text): - if self._alive: - self._js(f"appendLog({json.dumps(text)})") - - # ── Bridge JS→Python via navegación oasis://accion ─────────────────── - def _on_policy(self, webview, decision, decision_type): - if decision_type == WebKit2.PolicyDecisionType.NAVIGATION_ACTION: - uri = decision.get_navigation_action().get_request().get_uri() - if uri.startswith("oasis://"): - action = uri[len("oasis://"):] - print(f"[bridge] action={action}", flush=True) - GLib.idle_add(self._dispatch, action) - decision.ignore() - return True - decision.use() - return False - - def _dispatch(self, action): - installer_cmds = { - "oasis-install": ["--oasis-install"], - "ecoin-install": ["--ecoin-install"], - } - - if action == "oasis-stop": - threading.Thread(target=self._kill_oasis, daemon=True).start() - GLib.idle_add(self._js, "switchTab('sistema')") - - elif action == "oasis-start": - GLib.idle_add(self._js, "switchTab('sistema')") - threading.Thread(target=self._start_oasis, daemon=True).start() - - elif action == "oasis-browser": - subprocess.Popen(["xdg-open", "http://localhost:3000"]) - GLib.idle_add(self._log, "Abriendo http://localhost:3000 ...") - - elif action == "ecoin-start": - GLib.idle_add(self._js, "switchTab('sistema')") - threading.Thread(target=self._start_ecoin, daemon=True).start() - - elif action == "ecoin-stop": - GLib.idle_add(self._js, "switchTab('sistema')") - threading.Thread(target=self._stop_ecoin, daemon=True).start() - - elif action == "ecoin-gui": - GLib.idle_add(self._js, "switchTab('sistema')") - threading.Thread(target=self._open_ecoin_gui, daemon=True).start() - - elif action == "ecoin-info": - GLib.idle_add(self._js, "switchTab('sistema')") - threading.Thread(target=self._ecoin_info, daemon=True).start() - - elif action in installer_cmds: - GLib.idle_add(self._js, "switchTab('sistema')") - args = installer_cmds[action] - threading.Thread(target=self._run_cmd, args=(args,), daemon=True).start() - - def _run_cmd(self, args): - cmd = [str(INSTALLER_SH)] + args - GLib.idle_add(self._log, "$ " + " ".join(cmd)) - try: - proc = subprocess.Popen( - cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - text=True, bufsize=1 - ) - for line in iter(proc.stdout.readline, ""): - line = line.rstrip() - if line: - GLib.idle_add(self._log, line) - proc.wait() - GLib.idle_add(self._log, f"── fin (código {proc.returncode}) ──") - except Exception as e: - GLib.idle_add(self._log, f"[Error]: {e}") - GLib.idle_add(self._poll) - - def _start_oasis(self): - import time - oasis_dir = find_oasis_dir() - GLib.idle_add(self._log, f"$ cd {oasis_dir} && bash oasis.sh") - try: - proc = subprocess.Popen( - ["bash", "-lc", f"cd '{oasis_dir}' && bash oasis.sh"], - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - start_new_session=True, - ) - except Exception as e: - GLib.idle_add(self._log, f"[Error]: {e}") - return - - self._oasis_proc = proc - GLib.idle_add(self._log, f"PID {proc.pid} — esperando puerto 3000...") - - # Leer salida del proceso y mostrarla en el log - def _read_output(): - for line in iter(proc.stdout.readline, b""): - if self._alive: - GLib.idle_add(self._log, line.decode("utf-8", errors="replace").rstrip()) - threading.Thread(target=_read_output, daemon=True).start() - - # Esperar hasta 30s a que el puerto esté activo y abrir navegador - for i in range(30): - time.sleep(1) - if oasis_running(): - GLib.idle_add(self._log, "Puerto 3000 activo — abriendo navegador") - subprocess.Popen(["xdg-open", "http://localhost:3000"]) - GLib.idle_add(self._poll) - return - GLib.idle_add(self._log, "[Timeout] OASIS no levantó en 30s") - GLib.idle_add(self._poll) - - # ── ECOIN actions ───────────────────────────────────────────────────── - def _start_ecoin(self): - if ecoin_running(): - GLib.idle_add(self._log, "ecoind ya está corriendo.") - GLib.idle_add(self._poll) - return - e_dir = find_ecoin_dir() - ecoind = e_dir / "ecoin" / "src" / "ecoind" - if not ecoind.is_file(): - GLib.idle_add(self._log, f"[Error]: ecoind no encontrado en {ecoind}") - return - GLib.idle_add(self._log, f"$ {ecoind} -daemon") - try: - subprocess.Popen( - [str(ecoind), "-daemon"], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - start_new_session=True, - ) - import time - for i in range(15): - time.sleep(1) - if ecoin_running(): - GLib.idle_add(self._log, "ecoind iniciado correctamente.") - GLib.idle_add(self._poll) - return - GLib.idle_add(self._log, "[Timeout] ecoind no respondió en 15s.") - except Exception as e: - GLib.idle_add(self._log, f"[Error]: {e}") - GLib.idle_add(self._poll) - - def _stop_ecoin(self): - GLib.idle_add(self._log, "Deteniendo ecoind...") - result, err = ecoin_rpc("stop") - if err: - GLib.idle_add(self._log, f"[Error RPC]: {err}") - else: - GLib.idle_add(self._log, "ecoind detenido.") - GLib.idle_add(self._poll) - - def _open_ecoin_gui(self): - e_dir = find_ecoin_dir() - ecoin_qt = e_dir / "ecoin" / "ecoin-qt" - src_dir = e_dir / "ecoin" - - if ecoin_qt.is_file(): - # Si ecoind está corriendo, pararlo antes (no pueden coexistir) - if ecoin_running(): - GLib.idle_add(self._log, "Parando ecoind antes de abrir la GUI...") - ecoin_rpc("stop") - import time - for _ in range(10): - time.sleep(1) - if not ecoin_running(): - break - GLib.idle_add(self._log, f"$ {ecoin_qt}") - subprocess.Popen([str(ecoin_qt)], start_new_session=True) - GLib.idle_add(self._poll) - return - - # No compilado — compilar primero - GLib.idle_add(self._log, "ecoin-qt no encontrado. Compilando...") - - # Comprobar que qmake está disponible - qmake = None - for q in ("qmake", "qmake-qt5", "qmake6"): - r = subprocess.run(["which", q], capture_output=True) - if r.returncode == 0: - qmake = q - break - if not qmake: - GLib.idle_add(self._log, "[Error]: qmake no encontrado.") - GLib.idle_add(self._log, "Instala Qt5: sudo apt install qtbase5-dev qt5-qmake") - return - - GLib.idle_add(self._log, f"$ cd {src_dir} && {qmake} && make -j$(nproc)") - try: - proc = subprocess.Popen( - ["bash", "-c", f"cd '{src_dir}' && {qmake} && make -j$(nproc)"], - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - ) - for line in iter(proc.stdout.readline, b""): - if self._alive: - GLib.idle_add(self._log, line.decode("utf-8", errors="replace").rstrip()) - proc.wait() - except Exception as e: - GLib.idle_add(self._log, f"[Error compilando]: {e}") - return - - if ecoin_qt.is_file(): - GLib.idle_add(self._log, "Compilación completada. Lanzando ecoin-qt...") - subprocess.Popen([str(ecoin_qt)], start_new_session=True) - else: - GLib.idle_add(self._log, "[Error]: compilación falló. Revisa las dependencias.") - GLib.idle_add(self._log, "Dependencias necesarias:") - GLib.idle_add(self._log, " sudo apt install qtbase5-dev qt5-qmake libboost-all-dev libssl-dev libdb++-dev") - - def _ecoin_info(self): - if not ecoin_running(): - GLib.idle_add(self._log, "ecoind no está corriendo. Inicia el daemon primero.") - return - info, err = ecoin_rpc("getinfo") - if err: - GLib.idle_add(self._log, f"[RPC Error]: {err}") - return - bal, _ = ecoin_rpc("getbalance") - GLib.idle_add(self._log, "── ECOIN INFO ──────────────────") - GLib.idle_add(self._log, f" Balance: {bal} ECO") - GLib.idle_add(self._log, f" Bloques: {info.get('blocks')}") - GLib.idle_add(self._log, f" Conexiones: {info.get('connections')}") - GLib.idle_add(self._log, f" Versión: {info.get('version')}") - GLib.idle_add(self._log, f" Dificultad: {info.get('difficulty')}") - GLib.idle_add(self._log, "────────────────────────────────") - GLib.idle_add(self._poll) - - def _kill_oasis(self): - GLib.idle_add(self._log, "Deteniendo OASIS...") - try: - import os, signal as sig - if self._oasis_proc and self._oasis_proc.poll() is None: - # Matar el grupo de procesos completo (bash -lc + node hijo) - os.killpg(os.getpgid(self._oasis_proc.pid), sig.SIGTERM) - self._oasis_proc.wait(timeout=5) - self._oasis_proc = None - GLib.idle_add(self._log, "OASIS detenido.") - else: - # Fallback: matar cualquier node backend.js corriendo - subprocess.run(["pkill", "-f", "node.*backend.js"], timeout=5) - GLib.idle_add(self._log, "OASIS detenido.") - except Exception as e: - GLib.idle_add(self._log, f"[Error al detener]: {e}") - GLib.idle_add(self._poll) - - -# ── Main ─────────────────────────────────────────────────────────────────── -def main(): - signal.signal(signal.SIGINT, signal.SIG_DFL) - OasisPanel() - Gtk.main() - - -if __name__ == "__main__": - main() diff --git a/INSTALLER_V3/ui/app.js b/INSTALLER_V3/ui/app.js deleted file mode 100644 index 3fce5ef..0000000 --- a/INSTALLER_V3/ui/app.js +++ /dev/null @@ -1,119 +0,0 @@ -// ============================================================= -// Solar Net Hub — app.js -// Bridge JS ↔ Python y lógica de UI -// ============================================================= - -// ── Bridge JS → Python ──────────────────────────────────────── -function send(action) { - window.location.href = 'oasis://' + action; -} - -// ── Tabs ────────────────────────────────────────────────────── -function switchTab(name) { - document.querySelectorAll('.tab-panel').forEach(el => el.classList.remove('active')); - document.querySelectorAll('.tabBtn').forEach(el => el.classList.remove('active')); - const panel = document.getElementById('tab-' + name); - const btn = document.querySelector('[data-tab="' + name + '"]'); - if (panel) panel.classList.add('active'); - if (btn) btn.classList.add('active'); -} - -// ── Actualizar estado (llamado desde Python) ────────────────── -function updateStatus(data) { - // --- OASIS --- - const oCard = document.getElementById('oasisCard'); - const state = data.oasis_running ? 'running' - : data.oasis_installed ? 'stopped' - : 'unknown'; - - oCard.dataset.state = state; - - if (data.oasis_running) { - document.getElementById('oasisState').textContent = 'ACTIVO'; - document.getElementById('oasisSub').textContent = 'servidor corriendo en puerto 3000'; - _btn('btnStart', true); - _btn('btnStop', false); - _btn('btnInstall', true); - _btn('btnBrowser', false); - } else if (data.oasis_installed) { - document.getElementById('oasisState').textContent = 'INSTALADO'; - document.getElementById('oasisSub').textContent = 'servidor detenido'; - _btn('btnStart', false); - _btn('btnStop', true); - _btn('btnInstall', true); - _btn('btnBrowser', true); - } else { - document.getElementById('oasisState').textContent = 'NO INSTALADO'; - document.getElementById('oasisSub').textContent = 'instala OASIS para comenzar'; - _btn('btnStart', true); - _btn('btnStop', true); - _btn('btnInstall', false); - _btn('btnBrowser', true); - } - - document.getElementById('iVer').textContent = data.oasis_version ? 'v' + data.oasis_version : '—'; - document.getElementById('iNode').textContent = data.node_version || '—'; - document.getElementById('iDir').textContent = data.oasis_dir || '—'; - - // Global dot - document.getElementById('globalDot').dataset.state = state; - - // --- ECOIN --- - const eCard = document.getElementById('ecoinCard'); - eCard.dataset.state = data.ecoin_running ? 'running' : (data.ecoin_installed ? 'stopped' : 'unknown'); - - if (data.ecoin_running) { - document.getElementById('ecoinState').textContent = 'ACTIVO'; - document.getElementById('ecoinSub').textContent = 'daemon corriendo (headless)'; - _btn('btnEStart', true); - _btn('btnEStop', false); - _btn('btnEGui', false); - _btn('btnEInfo', false); - } else if (data.ecoin_installed) { - document.getElementById('ecoinState').textContent = 'INSTALADO'; - document.getElementById('ecoinSub').textContent = 'daemon detenido'; - _btn('btnEStart', false); - _btn('btnEStop', true); - _btn('btnEGui', false); - _btn('btnEInfo', true); - } else { - document.getElementById('ecoinState').textContent = 'NO INSTALADO'; - document.getElementById('ecoinSub').textContent = 'instala ECOIN para comenzar'; - _btn('btnEStart', true); - _btn('btnEStop', true); - _btn('btnEGui', true); - _btn('btnEInfo', true); - } - - // Info grid ecoin - document.getElementById('eBalance').textContent = data.ecoin_balance != null ? data.ecoin_balance + ' ECO' : '—'; - document.getElementById('eBlocks').textContent = data.ecoin_blocks != null ? data.ecoin_blocks : '—'; - document.getElementById('eConns').textContent = data.ecoin_connections != null ? data.ecoin_connections : '—'; - document.getElementById('eWallet').textContent = data.ecoin_wallet ? 'Si' : 'No'; - document.getElementById('eDaemon').textContent = data.ecoin_daemon ? 'Si' : 'No'; -} - -// ── Log (llamado desde Python) ──────────────────────────────── -function appendLog(text) { - const area = document.getElementById('logArea'); - const div = document.createElement('div'); - div.className = 'log-line'; - - if (text.startsWith('$')) div.classList.add('cmd'); - else if (text.startsWith('──')) div.classList.add('end'); - else if (text.startsWith('[Error')) div.classList.add('error'); - - div.textContent = text; - area.appendChild(div); - area.scrollTop = area.scrollHeight; -} - -function clearLog() { - document.getElementById('logArea').innerHTML = ''; -} - -// ── Helper: deshabilitar/habilitar botón ────────────────────── -function _btn(id, disabled) { - const el = document.getElementById(id); - if (el) el.disabled = disabled; -} diff --git a/INSTALLER_V3/ui/index.html b/INSTALLER_V3/ui/index.html deleted file mode 100644 index 0e33f73..0000000 --- a/INSTALLER_V3/ui/index.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - OASIS - - - - - -
-
- -
- OASIS - SOLAR NET HUB PANEL -
-
-
-
- - - - - -
- - -
- -
-
-
-
COMPROBANDO
-
iniciando...
-
-
- -
-
- - -
-
- - -
-
- -
-
VERSION
-
NODE.JS
-
RUTA
-
-
- - -
- -
-
-
-
COMPROBANDO
-
iniciando...
-
-
- -
-
- - -
-
- - -
-
- -
-
BALANCE
-
BLOQUES
-
CONEXIONES
-
WALLET
-
ECOIND
-
-
- - -
-
LOG DE ACTIVIDAD
-
- -
- -
- - - - diff --git a/INSTALLER_V3/ui/style.css b/INSTALLER_V3/ui/style.css deleted file mode 100644 index 14b7fc8..0000000 --- a/INSTALLER_V3/ui/style.css +++ /dev/null @@ -1,347 +0,0 @@ -/* ================================================================ - OASIS — Panel v3 - CSS puro, sin limitaciones GTK. Control total como Electron/web. - ================================================================ */ - -@font-face { - font-family: 'Dune Rise'; - src: url('../Dune_Rise.otf') format('opentype'); - font-weight: normal; - font-style: normal; -} - -:root { - --bg: #000000; - --bg2: #080808; - --bg3: #0D0D0D; - /* OASIS — naranja */ - --orange: #FF4E00; - --orange-dim: #882200; - --orange-mute: #441100; - /* ECOIN — amarillo */ - --yellow: #FFB300; - --yellow-dim: #886000; - --yellow-mute: #443000; - /* LOG — verde neon */ - --neon: #39FF14; - --neon-dim: #1a7a09; - /* General */ - --green: #27D980; - --border: #1A1A1A; - --font: 'Dune Rise', 'Cantarell', 'Ubuntu', sans-serif; - --mono: 'DejaVu Sans Mono', 'Courier New', monospace; -} - -/* ── Reset ── */ -*, *::before, *::after { - margin: 0; - padding: 0; - box-sizing: border-box; - -webkit-font-smoothing: antialiased; -} - -html, body { - width: 390px; - height: 620px; - overflow: hidden; - background: var(--bg); - color: var(--orange); - font-family: var(--font); - font-size: 10pt; - display: flex; - flex-direction: column; - user-select: none; -} - -/* ── Header ─────────────────────────────────────────────────── */ -header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 16px; - background: var(--bg2); - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} - -.header-left { - display: flex; - align-items: center; - gap: 10px; -} - -.logo { - width: 38px; - height: 38px; - object-fit: contain; - flex-shrink: 0; -} - -.header-titles { - display: flex; - flex-direction: column; - gap: 2px; -} - -.app-title { - font-size: 15pt; - color: var(--orange); - letter-spacing: 5px; - line-height: 1; -} - -.app-sub { - font-size: 6pt; - color: var(--orange-mute); - letter-spacing: 2px; -} - -.status-dot { - font-size: 11pt; - transition: color 0.4s; - line-height: 1; -} -.status-dot[data-state="running"] { color: var(--green); } -.status-dot[data-state="stopped"] { color: var(--orange); } -.status-dot[data-state="unknown"] { color: var(--orange-mute); } - -/* ── Tab bar ─────────────────────────────────────────────────── */ -nav.tabbar { - display: flex; - background: var(--bg); - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} - -.tabBtn { - flex: 1; - background: none; - border: none; - border-bottom: 3px solid transparent; - padding: 11px 0; - font-size: 7.5pt; - letter-spacing: 2px; - font-family: var(--font); - cursor: pointer; - transition: color 0.2s, border-color 0.2s; -} - -/* Tab OASIS — naranja */ -.tabBtn[data-tab="oasis"] { color: var(--orange-dim); } -.tabBtn[data-tab="oasis"]:hover { color: var(--orange); } -.tabBtn[data-tab="oasis"].active { color: var(--orange); border-bottom-color: var(--orange); } - -/* Tab ECOIN — amarillo */ -.tabBtn[data-tab="ecoin"] { color: var(--yellow-dim); } -.tabBtn[data-tab="ecoin"]:hover { color: var(--yellow); } -.tabBtn[data-tab="ecoin"].active { color: var(--yellow); border-bottom-color: var(--yellow); } - -/* Tab SISTEMA — verde neon */ -.tabBtn[data-tab="sistema"] { color: var(--neon-dim); } -.tabBtn[data-tab="sistema"]:hover { color: var(--neon); } -.tabBtn[data-tab="sistema"].active { color: var(--neon); border-bottom-color: var(--neon); } - -/* ── Main content ────────────────────────────────────────────── */ -main { - flex: 1; - overflow: hidden; - position: relative; - background: var(--bg); -} - -.tab-panel { - display: none; - flex-direction: column; - height: 100%; - overflow-y: auto; - background: var(--bg); -} -.tab-panel.active { display: flex; } - -/* ── Scrollbar ───────────────────────────────────────────────── */ -::-webkit-scrollbar { width: 4px; } -::-webkit-scrollbar-track { background: var(--bg); } -::-webkit-scrollbar-thumb { background: #222; border-radius: 2px; } -::-webkit-scrollbar-thumb:hover { background: #333; } - -/* ================================================================ - OASIS TAB — naranja - ================================================================ */ -#tab-oasis .card-state { color: var(--orange-dim); } -#tab-oasis .status-card[data-state="running"] .card-state { color: var(--green); } -#tab-oasis .status-card[data-state="stopped"] .card-state { color: var(--orange); } -#tab-oasis .status-card[data-state="running"] .card-stripe { background: var(--green); } -#tab-oasis .status-card[data-state="stopped"] .card-stripe { background: var(--orange); } -#tab-oasis .card-sub { color: var(--orange-mute); } -#tab-oasis .btn { color: var(--orange); border-color: var(--orange); } -#tab-oasis .btn.primary { background: var(--orange); color: #000; border-color: var(--orange); } -#tab-oasis .ik { color: var(--orange-dim); } -#tab-oasis .iv { color: var(--orange); } - -/* ================================================================ - ECOIN TAB — amarillo - ================================================================ */ -#tab-ecoin .card-state { color: var(--yellow-dim); } -#tab-ecoin .status-card[data-state="running"] .card-state { color: var(--yellow); } -#tab-ecoin .status-card[data-state="stopped"] .card-state { color: var(--yellow-dim); } -#tab-ecoin .status-card[data-state="running"] .card-stripe { background: var(--yellow); } -#tab-ecoin .status-card[data-state="stopped"] .card-stripe { background: var(--yellow-dim); } -#tab-ecoin .card-sub { color: var(--yellow-mute); } -#tab-ecoin .btn { color: var(--yellow); border-color: var(--yellow); } -#tab-ecoin .btn.primary { background: var(--yellow); color: #000; border-color: var(--yellow); } -#tab-ecoin .ik { color: var(--yellow-dim); } -#tab-ecoin .iv { color: var(--yellow); } - -/* ================================================================ - STATUS CARD — base - ================================================================ */ -.status-card { - display: flex; - margin: 14px 14px 6px; - background: var(--bg2); - border: 1px solid var(--border); - border-radius: 10px; - overflow: hidden; - flex-shrink: 0; - transition: border-color 0.3s; -} - -.card-stripe { - width: 5px; - flex-shrink: 0; - background: #1A1A1A; - transition: background 0.4s; -} -.status-card[data-state="unknown"] .card-stripe { background: #1A1A1A; } - -.card-body { - padding: 14px 16px; - display: flex; - flex-direction: column; - gap: 5px; -} - -.card-state { - font-size: 18pt; - font-weight: bold; - letter-spacing: 2px; - transition: color 0.4s; -} - -.card-sub { - font-size: 7.5pt; - letter-spacing: 1px; -} - -/* ── Botones base ────────────────────────────────────────────── */ -.actions { - padding: 6px 14px 2px; - flex-shrink: 0; -} - -.action-row { - display: flex; - gap: 8px; - margin-bottom: 8px; -} - -.btn { - flex: 1; - background: var(--bg); - border: 1px solid currentColor; - border-radius: 20px; - padding: 9px 10px; - font-size: 7.5pt; - font-weight: bold; - letter-spacing: 1px; - font-family: var(--font); - cursor: pointer; - transition: background 0.2s, color 0.2s, border-color 0.2s, opacity 0.2s; - white-space: nowrap; - text-align: center; -} -.btn:hover:not(:disabled) { - background: var(--green); - color: #000; - border-color: var(--green); -} -.btn:active:not(:disabled) { - background: #1fa865; - border-color: #1fa865; - color: #000; -} -.btn:disabled { - opacity: 0.2; - cursor: not-allowed; -} - -.btn.btn-clear { - flex: none; - width: calc(100% - 28px); - margin: 6px 14px 12px; - color: var(--neon); - border-color: var(--neon); -} -.btn.btn-clear:hover:not(:disabled) { - background: var(--neon); - color: #000; - border-color: var(--neon); -} - -/* ── Info box ────────────────────────────────────────────────── */ -.infobox { - margin: 8px 14px 14px; - border-top: 1px solid var(--border); - padding-top: 8px; - flex-shrink: 0; -} - -.info-row { - display: flex; - align-items: center; - padding: 5px 2px; - border-bottom: 1px solid #080808; -} -.info-row:last-child { border-bottom: none; } - -.ik { - font-size: 7.5pt; - letter-spacing: 1px; - min-width: 82px; - flex-shrink: 0; -} -.iv { - font-size: 7.5pt; - word-break: break-all; -} - -/* ================================================================ - SISTEMA / LOG — verde neon - ================================================================ */ -.log-header { - font-size: 6.5pt; - color: var(--neon-dim); - letter-spacing: 2px; - padding: 12px 16px 6px; - flex-shrink: 0; -} - -.log-area { - flex: 1; - background: #030303; - border: 1px solid #0a1a0a; - border-radius: 8px; - margin: 0 14px 8px; - padding: 10px 12px; - overflow-y: auto; - font-family: var(--mono); - font-size: 7.5pt; - color: var(--neon); - line-height: 1.55; - min-height: 0; -} - -.log-line { word-break: break-all; padding: 1px 0; color: var(--neon); } -.log-line.cmd { color: var(--orange); } -.log-line.end { color: var(--neon-dim); margin-top: 4px; } -.log-line.error { color: #ff4444; } diff --git a/start_panel.sh b/start_panel.sh new file mode 100755 index 0000000..7a39f23 --- /dev/null +++ b/start_panel.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# ============================================================= +# SOLAR NET HUB — Panel de control (lanzador) +# Lanza panel.py si python3-gi está disponible; +# si no, lo instala y reintenta. +# ============================================================= +set -euo pipefail + +REPO_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +PANEL_PY="$REPO_DIR/INSTALLER/panel.py" +INSTALLER_SH="$REPO_DIR/INSTALLER/installer.sh" + +C_RESET='\033[0m' +C_OK='\033[1;32m' +C_FAIL='\033[1;31m' +C_WARN='\033[1;33m' +C_INFO='\033[1;36m' + +have(){ command -v "$1" >/dev/null 2>&1; } + +# ── Instalar fuente Dune Rise (usuario, sin root) ───────────────────────── +install_font(){ + local src="$REPO_DIR/INSTALLER/Dune_Rise.otf" + local dst="$HOME/.local/share/fonts/Dune_Rise.otf" + if [ -f "$src" ] && [ ! -f "$dst" ]; then + mkdir -p "$HOME/.local/share/fonts" + cp -f "$src" "$dst" + fc-cache -f >/dev/null 2>&1 || true + fi +} + +# ── Verificar / instalar python3-gi ────────────────────────────────────── +check_gi(){ + python3 -c "import gi; gi.require_version('Gtk','3.0'); from gi.repository import Gtk" \ + >/dev/null 2>&1 +} + +install_gi(){ + echo -e "${C_WARN}⚠ python3-gi no encontrado. Instalando…${C_RESET}" + if have apt-get; then sudo apt-get install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 + elif have pacman; then sudo pacman -Sy --noconfirm python-gobject + elif have dnf; then sudo dnf install -y python3-gobject gtk3 + elif have zypper; then sudo zypper --non-interactive install python3-gobject gtk3 + else + echo -e "${C_FAIL}✘ Gestor de paquetes no soportado.${C_RESET}" + echo " Instala manualmente: python3-gi / python-gobject" + exit 1 + fi +} + +# ── Verificar DISPLAY / Wayland ─────────────────────────────────────────── +check_display(){ + if [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then + echo -e "${C_FAIL}✘ No hay sesión gráfica (DISPLAY/WAYLAND_DISPLAY vacíos).${C_RESET}" + echo " Ejecuta este script desde tu escritorio o una terminal gráfica." + exit 1 + fi +} + +# ── Main ───────────────────────────────────────────────────────────────── +echo -e "${C_INFO}== SOLAR NET HUB :: Panel de Control ==${C_RESET}" + +check_display + +install_font 2>/dev/null || true + +if ! check_gi; then + install_gi + if ! check_gi; then + echo -e "${C_FAIL}✘ No se pudo cargar python3-gi tras la instalación.${C_RESET}" + echo " Intenta cerrar sesión y volver a entrar, o instala python3-gi manualmente." + exit 1 + fi +fi + +echo -e "${C_OK}✔ python3-gi listo. Lanzando panel…${C_RESET}" +exec python3 "$PANEL_PY" "$@" diff --git a/start_panel_v2.sh b/start_panel_v2.sh new file mode 100755 index 0000000..ce12051 --- /dev/null +++ b/start_panel_v2.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +# ============================================================= +# SOLAR NET HUB — Panel v2 (lanzador + registro en menú) +# ============================================================= +set -euo pipefail + +REPO_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +PANEL_PY="$REPO_DIR/INSTALLER_V2/panel.py" +FONT_SRC="$REPO_DIR/INSTALLER_V2/Dune_Rise.otf" +ICON_SRC="$REPO_DIR/INSTALLER_V2/oasis-logo.png" + +APPS_DIR="$HOME/.local/share/applications" +ICONS_DIR="$HOME/.local/share/icons/hicolor/256x256/apps" +DESKTOP_FILE="$APPS_DIR/solarnethub-panel.desktop" + +C_RESET='\033[0m'; C_OK='\033[1;32m'; C_FAIL='\033[1;31m' +C_WARN='\033[1;33m'; C_INFO='\033[1;36m' + +have(){ command -v "$1" >/dev/null 2>&1; } + +check_display(){ + if [ -z "${DISPLAY:-}" ] && [ -z "${WAYLAND_DISPLAY:-}" ]; then + echo -e "${C_FAIL}✘ Sin sesión gráfica (DISPLAY/WAYLAND_DISPLAY vacíos).${C_RESET}" + exit 1 + fi +} + +install_font(){ + local dst="$HOME/.local/share/fonts/Dune_Rise.otf" + if [ -f "$FONT_SRC" ] && [ ! -f "$dst" ]; then + mkdir -p "$HOME/.local/share/fonts" + cp -f "$FONT_SRC" "$dst" + fc-cache -f >/dev/null 2>&1 || true + echo -e "${C_OK}✔ Fuente Dune Rise instalada.${C_RESET}" + fi +} + +register_desktop(){ + mkdir -p "$APPS_DIR" "$ICONS_DIR" + + # Copiar icono al directorio de iconos del usuario + if [ -f "$ICON_SRC" ]; then + cp -f "$ICON_SRC" "$ICONS_DIR/solarnethub.png" + fi + + # Crear .desktop apuntando a este lanzador + cat > "$DESKTOP_FILE" </dev/null || true + fi + + # Refrescar cache de aplicaciones e iconos + have update-desktop-database && \ + update-desktop-database "$APPS_DIR" >/dev/null 2>&1 || true + have gtk-update-icon-cache && \ + gtk-update-icon-cache -f "$HOME/.local/share/icons/hicolor" >/dev/null 2>&1 || true + + echo -e "${C_OK}✔ Registrado en el menú: 'Solar Net Hub'${C_RESET}" +} + +check_gi(){ + python3 -c "import gi; gi.require_version('Gtk','3.0'); from gi.repository import Gtk" \ + >/dev/null 2>&1 +} + +install_gi(){ + echo -e "${C_WARN}⚠ python3-gi no encontrado. Instalando...${C_RESET}" + if have apt-get; then sudo apt-get install -y python3-gi python3-gi-cairo gir1.2-gtk-3.0 + elif have pacman; then sudo pacman -Sy --noconfirm python-gobject + elif have dnf; then sudo dnf install -y python3-gobject gtk3 + elif have zypper; then sudo zypper --non-interactive install python3-gobject gtk3 + else + echo -e "${C_FAIL}✘ Gestor no soportado. Instala manualmente: python3-gi${C_RESET}" + exit 1 + fi +} + +# ── Main ────────────────────────────────────────────────────────────────── +echo -e "${C_INFO}== SOLAR NET HUB :: Panel v2 ==${C_RESET}" + +check_display +install_font 2>/dev/null || true +register_desktop 2>/dev/null || true + +if ! check_gi; then + install_gi + if ! check_gi; then + echo -e "${C_FAIL}✘ No se pudo cargar python3-gi.${C_RESET}" + exit 1 + fi +fi + +echo -e "${C_OK}✔ Lanzando panel...${C_RESET}" +exec python3 "$PANEL_PY" "$@" diff --git a/start_panel_v3.sh b/start_panel_v3.sh deleted file mode 100755 index 972e320..0000000 --- a/start_panel_v3.sh +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env bash -# ============================================================= -# SOLAR NET HUB — Panel v3 (WebKit2GTK) -# ============================================================= -set -euo pipefail - -REPO_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" -PANEL_PY="$REPO_DIR/INSTALLER_V3/panel.py" -FONT_SRC="$REPO_DIR/INSTALLER_V3/Dune_Rise.otf" -ICON_SRC="$REPO_DIR/INSTALLER_V3/oasis-logo.png" -APPS_DIR="$HOME/.local/share/applications" -ICONS_DIR="$HOME/.local/share/icons/hicolor/256x256/apps" - -C_OK='\033[1;32m'; C_FAIL='\033[1;31m'; C_WARN='\033[1;33m' -C_INFO='\033[1;36m'; C_RESET='\033[0m' - -have(){ command -v "$1" >/dev/null 2>&1; } - -check_display(){ - [ -n "${DISPLAY:-}" ] || [ -n "${WAYLAND_DISPLAY:-}" ] && return - echo -e "${C_FAIL}✘ Sin sesión gráfica.${C_RESET}"; exit 1 -} - -install_font(){ - local dst="$HOME/.local/share/fonts/Dune_Rise.otf" - [ -f "$FONT_SRC" ] && [ ! -f "$dst" ] && { - mkdir -p "$HOME/.local/share/fonts" - cp -f "$FONT_SRC" "$dst" - fc-cache -f >/dev/null 2>&1 || true - echo -e "${C_OK}✔ Fuente Dune Rise instalada.${C_RESET}" - } -} - -check_webkit(){ - python3 -c " -import gi -for v in ('4.1','4.0'): - try: - gi.require_version('WebKit2', v) - from gi.repository import WebKit2 - print(v) - break - except Exception: - pass -" 2>/dev/null -} - -install_webkit(){ - echo -e "${C_WARN}⚠ WebKit2GTK no encontrado. Instalando...${C_RESET}" - if have apt-get; then - sudo apt-get install -y python3-gi python3-gi-cairo \ - gir1.2-webkit2-4.0 libwebkit2gtk-4.0-dev 2>/dev/null || \ - sudo apt-get install -y python3-gi python3-gi-cairo \ - gir1.2-webkit2-4.1 libwebkit2gtk-4.1-dev 2>/dev/null - elif have pacman; then - sudo pacman -Sy --noconfirm webkit2gtk python-gobject - elif have dnf; then - sudo dnf install -y python3-gobject webkit2gtk4.0 - elif have zypper; then - sudo zypper --non-interactive install python3-gobject webkit2gtk3 - else - echo -e "${C_FAIL}✘ Instala manualmente: gir1.2-webkit2-4.0${C_RESET}" - exit 1 - fi -} - -register_desktop(){ - mkdir -p "$APPS_DIR" "$ICONS_DIR" - [ -f "$ICON_SRC" ] && cp -f "$ICON_SRC" "$ICONS_DIR/oasis.png" - - # Borrar entradas antiguas con nombres distintos - rm -f "$APPS_DIR/solarnethub.desktop" \ - "$APPS_DIR/solarnethub-panel.desktop" \ - "$APPS_DIR/INSTALLER.desktop" \ - "$APPS_DIR/oasis.desktop" - - cat > "$APPS_DIR/oasis-panel.desktop" </dev/null || true - - have update-desktop-database && \ - update-desktop-database "$APPS_DIR" >/dev/null 2>&1 || true - have gtk-update-icon-cache && \ - gtk-update-icon-cache -f "$HOME/.local/share/icons/hicolor" >/dev/null 2>&1 || true - - echo -e "${C_OK}✔ Registrado en el menú como 'Solar Net Hub'${C_RESET}" -} - -# ── Main ────────────────────────────────────────────────────── -echo -e "${C_INFO}== SOLAR NET HUB :: Panel v3 (WebKit2GTK) ==${C_RESET}" - -check_display -install_font 2>/dev/null || true -register_desktop 2>/dev/null || true - -WK_VER="$(check_webkit)" -if [ -z "$WK_VER" ]; then - install_webkit - WK_VER="$(check_webkit)" - [ -z "$WK_VER" ] && { - echo -e "${C_FAIL}✘ WebKit2GTK no disponible tras instalación.${C_RESET}" - exit 1 - } -fi - -echo -e "${C_OK}✔ WebKit2 $WK_VER listo. Lanzando panel...${C_RESET}" -exec python3 "$PANEL_PY" "$@"