diff --git a/INSTALLER/panel.py b/INSTALLER/panel.py deleted file mode 100755 index a3cc7ce..0000000 --- a/INSTALLER/panel.py +++ /dev/null @@ -1,695 +0,0 @@ -#!/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 deleted file mode 100644 index df025f4..0000000 Binary files a/INSTALLER_V2/Dune_Rise.otf and /dev/null differ diff --git a/INSTALLER_V2/ecoin.png b/INSTALLER_V2/ecoin.png deleted file mode 100644 index 4308a29..0000000 Binary files a/INSTALLER_V2/ecoin.png and /dev/null differ diff --git a/INSTALLER_V2/installer.sh b/INSTALLER_V2/installer.sh deleted file mode 100755 index d9a46e6..0000000 --- a/INSTALLER_V2/installer.sh +++ /dev/null @@ -1,400 +0,0 @@ -#!/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 deleted file mode 100644 index 4d870e8..0000000 Binary files a/INSTALLER_V2/oasis-ecoin.png and /dev/null differ diff --git a/INSTALLER_V2/oasis-logito.png b/INSTALLER_V2/oasis-logito.png deleted file mode 100644 index f28986c..0000000 Binary files a/INSTALLER_V2/oasis-logito.png and /dev/null differ diff --git a/INSTALLER_V2/oasis-logo.png b/INSTALLER_V2/oasis-logo.png deleted file mode 100644 index ccf33ce..0000000 Binary files a/INSTALLER_V2/oasis-logo.png and /dev/null differ diff --git a/INSTALLER_V2/panel.py b/INSTALLER_V2/panel.py deleted file mode 100644 index a58c74f..0000000 --- a/INSTALLER_V2/panel.py +++ /dev/null @@ -1,746 +0,0 @@ -#!/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/start_panel.sh b/start_panel.sh deleted file mode 100755 index 7a39f23..0000000 --- a/start_panel.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/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 deleted file mode 100755 index ce12051..0000000 --- a/start_panel_v2.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/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" "$@"