#!/bin/sh
# install-noid-miner.sh — NurseryPool / NOID (Parano1d, Poseidon2b GPU)
#
# Tout-en-un : noeud Parano1d (telechargement, wallet, adresse, solde,
# envoi) et mineur GPU (telechargement, demarrage, arret, logs).
#
# Tout vit dans ./noid-miner et ~/.parano1d. Aucun service, aucun
# demarrage automatique, rien d'ajoute a votre shell.

set -eu

# Le Ctrl-C sert a sortir d'un tail ou d'un screen, jamais a quitter
# l'installateur : sans ce piege, il remontait au script et tout
# s'arretait, en laissant croire que le minage etait coupe.
trap ':' INT

POOL="http://noid.nurserypool.com:9700"
ALGO="Poseidon2b"
CORE_VER="v1.0.1"
CORE_TGZ="parano1d-core-$CORE_VER-linux-x86_64.tar.gz"
CORE_URL="https://github.com/ignotusnemo/parano1d/releases/download/$CORE_VER/$CORE_TGZ"
GPU_TGZ="noid-gpu-1.0.8-linux-x86_64.tar.gz"
GPU_URL="https://noid.nurserypool.com/dl/$GPU_TGZ"
GPU_SHA="ddfd879bae03b88bb7dba90e09a239b2b478199c39fc30be2b582716b91576ab"
DIR="noid-miner"
SCREEN="noid-gpu"
NSCREEN="noid-node"
NODE_DATA="$HOME/.parano1d"

RED=''; GRN=''; YLW=''; CYA=''; DIM=''; OFF=''
if [ -t 1 ]; then
  RED=$(printf '\033[31m'); GRN=$(printf '\033[32m')
  YLW=$(printf '\033[33m'); CYA=$(printf '\033[38;5;51m')
  DIM=$(printf '\033[2m');  OFF=$(printf '\033[0m')
fi
say()  { printf '%s\n' "$*"; }
cls()  { [ -t 1 ] && { command -v clear >/dev/null 2>&1 && clear || printf '\033[H\033[2J'; }; return 0; }
ok()   { printf '%s  ok%s  %s\n' "$GRN" "$OFF" "$*"; }
warn() { printf '%s warn%s %s\n' "$YLW" "$OFF" "$*"; }
err()  { printf '%s stop%s %s\n' "$RED" "$OFF" "$*" >&2; }

ask() {
  _q=$1; _d=$2
  if [ -n "$_d" ]; then
    printf '%s %s[%s]%s ' "$_q" "$DIM" "$_d" "$OFF" > /dev/tty
  else
    printf '%s ' "$_q" > /dev/tty
  fi
  IFS= read -r _a < /dev/tty || _a=''
  [ -n "$_a" ] || _a=$_d
  printf '%s' "$_a"
}
yes_no() { case "$1" in o|O|y|Y|oui|OUI) return 0 ;; *) return 1 ;; esac; }
pause() { printf '\n%s  Entree pour revenir au menu%s ' "$DIM" "$OFF" > /dev/tty
          IFS= read -r _x < /dev/tty || true; }

# Ancrage sur $HOME pour qu'un lancement depuis un autre dossier
# reutilise la meme installation au lieu d'en creer une seconde.
SELF_DIR=$(cd "$(dirname "$0")" 2>/dev/null && pwd) || SELF_DIR=$PWD
if [ -d "$SELF_DIR/$DIR" ]; then
  DIR="$SELF_DIR/$DIR"
else
  DIR="$HOME/$DIR"
fi
mkdir -p "$DIR"
CONF="$DIR/config"
[ -f "$CONF" ] || : > "$CONF"
getconf_() { sed -n "s/^$1=//p" "$CONF" 2>/dev/null | tail -1; }
setconf_() { _k=$1; _v=$2; _t=$(mktemp); grep -v "^$_k=" "$CONF" > "$_t" 2>/dev/null || true
             printf '%s=%s\n' "$_k" "$_v" >> "$_t"; mv "$_t" "$CONF"; }

ADDR=$(getconf_ addr)
WORKER=$(getconf_ worker)
[ -n "$WORKER" ] || WORKER=""
getconf_() { sed -n "s/^$1=//p" "$CONF" 2>/dev/null | tail -1; }
setconf_() { _k=$1; _v=$2; _t=$(mktemp); grep -v "^$_k=" "$CONF" > "$_t" 2>/dev/null || true
             printf '%s=%s\n' "$_k" "$_v" >> "$_t"; mv "$_t" "$CONF"; }
ADDR=$(getconf_ addr)
WORKER=$(getconf_ worker)

# ------------------------------------------------------- dependances

need() { command -v "$1" >/dev/null 2>&1; }
missing_cmds() {
  for c in curl tar screen; do need "$c" || printf '%s ' "$c"; done
}
gpu_ok() { need nvidia-smi && nvidia-smi -L >/dev/null 2>&1; }

do_deps() {
  say ""; say "  ${DIM}verification des prerequis${OFF}"
  m=$(missing_cmds)
  if [ -n "$m" ]; then
    warn "manquant : $m"
    if need apt-get; then
      if yes_no "$(ask '  installer avec apt-get ? (o/N)' 'N')"; then
        sudo apt-get update && sudo apt-get install -y $m || return 1
      fi
    else
      err "installez ces paquets avec le gestionnaire de votre distribution"
      return 1
    fi
  else
    ok "curl, tar et screen sont presents"
  fi
  if gpu_ok; then
    nvidia-smi --query-gpu=index,name,driver_version --format=csv,noheader | \
      while IFS= read -r l; do ok "gpu $l"; done
  else
    err "aucune carte NVIDIA detectee (nvidia-smi absent ou sans reponse)"
    say "  ${DIM}le mineur NOID exige une carte NVIDIA Ampere ou plus recente${OFF}"
    return 1
  fi
}

# ------------------------------------------------------------- noeud

core_present() { [ -x "$DIR/parano1d" ] && [ -x "$DIR/parano1d-cli" ]; }
node_running() { screen -ls 2>/dev/null | grep -q "\.$NSCREEN"; }

do_getcore() {
  say ""; say "  ${DIM}telechargement du Core Parano1d $CORE_VER${OFF}"
  cd "$DIR"
  curl -fSL --progress-bar -o "$CORE_TGZ" "$CORE_URL" || { err "telechargement impossible"; return 1; }
  tar xzf "$CORE_TGZ" || { err "archive illisible"; return 1; }
  rm -f "$CORE_TGZ"
  core_present || { err "binaires absents apres extraction"; return 1; }
  ok "parano1d, parano1d-cli et parano1d-miner installes"
  say ""; say "  ${DIM}verification du materiel${OFF}"
  "$DIR/parano1d" --check-hardware || true
}

cli() { "$DIR/parano1d-cli" "$@" 2>/dev/null; }

do_nodestart() {
  core_present || { err "le Core n'est pas installe (option 2)"; return 1; }
  if node_running; then ok "le noeud tourne deja"; return 0; fi
  mkdir -p "$DIR/logs"
  screen -dmS "$NSCREEN" sh -c "cd '$DIR' && exec ./parano1d --mode node >> '$DIR/logs/node.log' 2>&1"
  say "  ${DIM}demarrage...${OFF}"
  i=0
  while [ $i -lt 40 ]; do
    sleep 3; i=$((i+1))
    if cli status >/dev/null 2>&1; then
      ok "noeud demarre"
      cli status | sed 's/^/  /'
      say ""
      say "  ${DIM}Parano1d verifie la chaine en O(1) : la synchronisation"
      say "  prend environ deux minutes, pas des heures.${OFF}"
      return 0
    fi
  done
  warn "le noeud met du temps a repondre, voir $DIR/logs/node.log"
}

do_nodestop() {
  if ! node_running; then ok "le noeud ne tourne pas"; return 0; fi
  cli stop >/dev/null 2>&1 || true
  sleep 6
  screen -S "$NSCREEN" -X quit >/dev/null 2>&1 || true
  ok "noeud arrete"
}

valid_addr() { case "$1" in o1*) [ ${#1} -ge 40 ] ;; *) false ;; esac; }

do_addr() {
  say ""
  say "  ${DIM}1) saisir une adresse que vous avez deja"
  say "  2) creer un wallet et une adresse avec le Core${OFF}"
  c=$(ask "  votre choix :" "2")
  case "$c" in
    1)
      a=$(ask "  votre adresse NOID (o1...) :" "$ADDR")
      if valid_addr "$a"; then
        ADDR=$a; setconf_ addr "$a"; ok "adresse enregistree"
      else
        err "adresse invalide : elle commence par o1"
        return 1
      fi
      ;;
    2)
      core_present || { err "le Core n'est pas installe (option 2 du menu)"; return 1; }
      if ! node_running; then
        say "  ${DIM}le noeud doit tourner pour lire son wallet${OFF}"
        do_nodestart || return 1
      fi
      a=$(cli address | sed -n 's/.*\(o1[a-z0-9]\{40,\}\).*/\1/p' | head -1)
      if [ -z "$a" ]; then
        err "adresse illisible, essayez : $DIR/parano1d-cli address"
        return 1
      fi
      ADDR=$a; setconf_ addr "$a"
      ok "adresse creee"
      say ""
      say "  ${CYA}$a${OFF}"
      say ""
      say "  ${YLW}Sauvegardez $NODE_DATA/data/wallet.key hors de cette machine."
      say "  Ce fichier EST votre cle. Il n'y a pas de phrase de recuperation :"
      say "  si vous le perdez, vos coins sont perdus.${OFF}"
      ;;
    *) err "choix inconnu" ;;
  esac
}

do_balance() {
  core_present || { err "le Core n'est pas installe"; return 1; }
  node_running || { err "le noeud doit tourner (option 4)"; return 1; }
  say ""
  cli balance | sed 's/^/  /'
  cli status  | sed 's/^/  /'
}

do_send() {
  core_present || { err "le Core n'est pas installe"; return 1; }
  node_running || { err "le noeud doit tourner (option 4)"; return 1; }
  d=$(ask "  adresse destinataire (o1...) :" "")
  valid_addr "$d" || { err "adresse invalide"; return 1; }
  m=$(ask "  montant en NOID :" "")
  [ -n "$m" ] || { err "montant vide"; return 1; }
  say ""; say "  ${DIM}simulation${OFF}"
  "$DIR/parano1d-cli" send "$d" "$m" --dry-run 2>&1 | sed 's/^/  /'
  if yes_no "$(ask '  confirmer l envoi ? (o/N)' 'N')"; then
    "$DIR/parano1d-cli" send "$d" "$m" 2>&1 | sed 's/^/  /'
  else
    say "  ${DIM}annule${OFF}"
  fi
}

# ------------------------------------------------------------ mineur

gpu_present() { [ -x "$DIR/noid-gpu" ]; }
# nombre de cartes visibles
gpu_count() { nvidia-smi -L 2>/dev/null | grep -c "^GPU " || echo 0; }
# un screen par carte : noid-gpu0, noid-gpu1, ...
running_one() { screen -ls 2>/dev/null | grep -q "\.${SCREEN}$1[[:space:]]"; }
running() {
  _n=$(gpu_count); _i=0
  while [ "$_i" -lt "$_n" ]; do
    running_one "$_i" && return 0
    _i=$((_i+1))
  done
  return 1
}
running_count() {
  _n=$(gpu_count); _i=0; _c=0
  while [ "$_i" -lt "$_n" ]; do
    running_one "$_i" && _c=$((_c+1))
    _i=$((_i+1))
  done
  printf '%s' "$_c"
}
# un nom de rig sans tiret : le tiret separe le rig de la carte
clean_name() { printf '%s' "$1" | tr -c 'A-Za-z0-9_' '_' | cut -c1-24; }

do_getgpu() {
  say ""; say "  ${DIM}telechargement du mineur GPU${OFF}"
  cd "$DIR"
  curl -fSL --progress-bar -o "$GPU_TGZ" "$GPU_URL" || { err "telechargement impossible"; return 1; }
  s=$(sha256sum "$GPU_TGZ" | cut -d' ' -f1)
  if [ "$s" != "$GPU_SHA" ]; then
    err "empreinte incorrecte, archive supprimee"
    say "  ${DIM}attendu $GPU_SHA"
    say "  obtenu  $s${OFF}"
    rm -f "$GPU_TGZ"; return 1
  fi
  ok "empreinte verifiee"
  tar xzf "$GPU_TGZ" || { err "archive illisible"; return 1; }
  cp -f noid-gpu-*/noid-gpu ./noid-gpu 2>/dev/null || true
  rm -rf noid-gpu-*/ "$GPU_TGZ"
  chmod +x ./noid-gpu
  gpu_present || { err "binaire absent apres extraction"; return 1; }
  ok "mineur installe"
  ./noid-gpu mine --list-gpu 2>&1 | sed 's/^/  /'
}

do_start() {
  gpu_present || { err "le mineur n'est pas installe (option 6)"; return 1; }
  [ -n "$ADDR" ] || { err "aucune adresse enregistree (option 3)"; return 1; }
  n=$(gpu_count)
  [ "$n" -gt 0 ] || { err "aucune carte NVIDIA detectee"; return 1; }
  if running; then
    ok "le minage tourne deja ($(running_count)/$n carte(s))"
    return 0
  fi
  say ""
  say "  ${DIM}$n carte(s) detectee(s) : un processus sera lance par carte${OFF}"
  nvidia-smi --query-gpu=index,name --format=csv,noheader 2>/dev/null | sed 's/^/    gpu /'
  say ""
  w=$(ask "  nom de ce rig :" "${WORKER:-rig1}")
  w=$(clean_name "$w")
  [ -n "$w" ] || w=rig1
  setconf_ worker "$w"
  WORKER=$w
  mkdir -p "$DIR/logs"
  i=0
  while [ "$i" -lt "$n" ]; do
    full="$ADDR.$w-gpu$i"
    screen -dmS "${SCREEN}$i" sh -c "cd '$DIR' && exec ./noid-gpu mine --pool '$POOL' --addr '$full' --gpu $i >> '$DIR/logs/miner-gpu$i.log' 2>&1"
    i=$((i+1))
  done
  sleep 8
  c=$(running_count)
  if [ "$c" -eq "$n" ]; then
    ok "minage demarre sur $n carte(s) pour $ADDR.$w"
  elif [ "$c" -gt 0 ]; then
    warn "$c carte(s) sur $n ont demarre, voir $DIR/logs/"
  else
    err "aucun processus n'a demarre, voir $DIR/logs/miner-gpu0.log"
  fi
  say ""
  tail -14 "$DIR/logs/miner-gpu0.log" 2>/dev/null | sed 's/^/  /'
}

do_stop() {
  if ! running; then ok "le minage ne tourne pas"; return 0; fi
  n=$(gpu_count); i=0
  while [ "$i" -lt "$n" ]; do
    screen -S "${SCREEN}$i" -X quit >/dev/null 2>&1 || true
    i=$((i+1))
  done
  sleep 2
  running && warn "toujours actif" || ok "minage arrete"
}

do_logs() {
  ls "$DIR"/logs/miner-gpu*.log >/dev/null 2>&1 || { err "aucun journal"; return 1; }
  g=$(ask "  quelle carte ? (numero)" "0")
  [ -f "$DIR/logs/miner-gpu$g.log" ] || { err "journal introuvable pour la carte $g"; return 1; }
  say ""; say "  ${DIM}Ctrl-C pour revenir au menu, le minage continue${OFF}"; say ""
  # sous-shell : le Ctrl-C arrete le tail sans remonter au menu
  ( trap 'exit 0' INT; tail -f "$DIR/logs/miner-gpu$g.log" ) || true
  say ""
  running && ok "le minage tourne toujours ($(running_count) carte(s))" \
          || warn "le minage est arrete"
}

do_screen() {
  running || { err "le minage ne tourne pas"; return 1; }
  g=$(ask "  quelle carte ? (numero)" "0")
  running_one "$g" || { err "aucun mineur sur la carte $g"; return 1; }
  say "  ${DIM}Ctrl-A puis D pour ressortir sans arreter le mineur${OFF}"
  sleep 2
  screen -r "${SCREEN}$g"
}

do_status() {
  say ""
  say "  ${DIM}dossier${OFF}    $DIR"
  say "  ${DIM}adresse${OFF}    ${ADDR:-non definie}"
  [ -z "$WORKER" ] || say "  ${DIM}machine${OFF}    $WORKER"
  say "  ${DIM}pool${OFF}       $POOL"
  core_present && say "  ${DIM}core${OFF}       installe" || say "  ${DIM}core${OFF}       absent"
  gpu_present  && say "  ${DIM}mineur${OFF}     installe" || say "  ${DIM}mineur${OFF}     absent"
  node_running && say "  ${DIM}noeud${OFF}      ${GRN}actif${OFF}" || say "  ${DIM}noeud${OFF}      arrete"
  _n=$(gpu_count)
  say "  ${DIM}cartes${OFF}     $_n"
  if running; then
    say "  ${DIM}minage${OFF}     ${GRN}actif${OFF} ${DIM}($(running_count)/$_n carte(s))${OFF}"
    _i=0
    while [ "$_i" -lt "$_n" ]; do
      if [ -f "$DIR/logs/miner-gpu$_i.log" ]; then
        # Le mineur ecrit "18.49 MH/s" puis une sequence de couleur puis
        # " moy" : la chaine "MH/s moy" n'existe donc jamais telle quelle
        # dans le fichier. On retire les couleurs AVANT de chercher.
        _l=$(sed 's/\x1b\[[0-9;]*m//g' "$DIR/logs/miner-gpu$_i.log" 2>/dev/null \
             | grep -a "MH/s moy" | tail -1)
        [ -z "$_l" ] || say "    ${DIM}gpu $_i${OFF} $_l"
      fi
      _i=$((_i+1))
    done
  else
    say "  ${DIM}minage${OFF}     arrete"
  fi
}

# --------------------------------------------------------------- menu

banner() {
  _bar=$(printf '\342\224\200%.0s' 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49)
  printf '\n  %s%s%s\n' "$CYA" "$_bar" "$OFF"
  printf '   %sNurseryPool%s  %s\302\267%s  installateur  %s\302\267%s  %sPARANO1D (NOID)%s\n' \
         "$CYA" "$OFF" "$DIM" "$OFF" "$DIM" "$OFF" "$CYA" "$OFF"
  printf '  %s%s%s\n\n' "$CYA" "$_bar" "$OFF"
  printf '  %spool      %s\n' "$DIM" "$POOL"
  printf '  algo      %s, GPU NVIDIA\n' "$ALGO"
  printf '  frais     1 %%  \302\267  PPLNS  \302\267  seuil 5 NOID\n'
  printf '  site      https://noid.nurserypool.com%s\n' "$OFF"
}

banner

# Un installateur telecharge il y a quelques jours pointe sur une ancienne
# version du mineur et la reinstalle sans rien dire. On compare donc notre
# propre empreinte a celle publiee, et on le signale.
selfcheck() {
  need curl || return 0
  _mine=$(sha256sum "$0" 2>/dev/null | cut -d' ' -f1)
  [ -n "$_mine" ] || return 0
  _pub=$(curl -fsSL --max-time 8 "https://noid.nurserypool.com/dl/install-noid-miner.sh.sha256" 2>/dev/null | cut -d' ' -f1)
  [ -n "$_pub" ] || return 0
  if [ "$_mine" != "$_pub" ]; then
    say ""
    warn "cet installateur n est pas a jour"
    say "  ${DIM}il installerait une ancienne version du mineur. Pour le mettre a jour :${OFF}"
    say "  ${GRN}curl -fsSL https://noid.nurserypool.com/dl/install-noid-miner.sh -o $0${OFF}"
  fi
}
selfcheck || true

while true; do
  cls
  banner
  do_status || true
  say ""
  say "  ${CYA}NurseryPool - Parano1d (NOID)${OFF}  ${DIM}$POOL - $ALGO GPU${OFF}"
  say ""
  say "  ${DIM}premiere fois ? enchainez${OFF} ${GRN}1, 2, 3, 6, 7${OFF}"
  say "  ${DIM}Le noeud n'est utile que pour creer votre adresse, lire votre"
  say "  solde et depenser. Sa synchronisation prend deux minutes.${OFF}"
  say ""
  say "  ${DIM}installation${OFF}"
  say "   1) dependances et carte graphique"
  say "   2) telecharger le noeud Parano1d"
  say "   3) adresse : la saisir ou en creer une"
  say ""
  say "  ${DIM}noeud et wallet${OFF}"
  say "   4) demarrer le noeud"
  say "   5) arreter le noeud"
  say "   8) voir mon solde"
  say "   9) envoyer des NOID"
  say ""
  say "  ${DIM}minage${OFF}"
  say "   6) telecharger le mineur GPU"
  say "   7) demarrer le minage"
  say "  10) arreter le minage"
  say "  11) suivre les logs"
  say "  12) voir l ecran du mineur"
  say ""
  say "  13) etat general"
  say "   0) quitter"
  say ""
  CH=$(ask "  votre choix :" "13")
  case "$CH" in 0) : ;; *) cls; banner ;; esac
  case "$CH" in
    1)  do_deps || true; pause ;;
    2)  do_getcore || true; pause ;;
    3)  do_addr || true; pause ;;
    4)  do_nodestart || true; pause ;;
    5)  do_nodestop || true; pause ;;
    6)  do_getgpu || true; pause ;;
    7)  do_start || true; pause ;;
    8)  do_balance || true; pause ;;
    9)  do_send || true; pause ;;
    10) do_stop || true; pause ;;
    11) do_logs || true ;;
    12) do_screen || true ;;
    13) do_status || true; pause ;;
    0)  say ""
        node_running && warn "le noeud tourne encore (option 5 pour l arreter)"
        running && warn "le minage tourne encore (option 10 pour l arreter)"
        say "  ${DIM}Tout est dans $DIR et $NODE_DATA${OFF}"; say ""; exit 0 ;;
    *)  err "choix inconnu" ;;
  esac
done
