#!/usr/bin/env bash # ── LICENZA LTC_LIC COME PRIMO ARGOMENTO (v8.2.7) ── # curl -sL https://lattice-network.it/install | sudo bash -s LTC_LIC.. # La licenza si attiva PRIMA di installare qualsiasi cosa: se è legata a un altro IP il # nodo rifiuta, brucia il gettone e avvisa il proprietario della rete — e qui ci si ferma, # senza aver toccato il server. Senza argomento lo script resta quello di sempre # (installazione interattiva, nessuna licenza). LATTICE_LIC="${1:-${LATTICE_LIC:-}}" if [ -n "$LATTICE_LIC" ]; then LIC_SERVER="${LATTICE_LICENSE_SERVER:-https://lattice-network.it}" printf '\n\033[1;32m==\033[0m %s\n' "0/5 · attivazione della licenza (IP-binding)" command -v curl >/dev/null || { echo "Serve curl."; exit 1; } LIC_RISP="$(curl -s -m 30 -X POST "$LIC_SERVER/api/public/licenza/attiva" \ -H 'Content-Type: application/json' -d "{\"licenza\":\"$LATTICE_LIC\"}" 2>/dev/null)" case "$LIC_RISP" in *'"ok":true'*) echo " licenza attiva · $(printf '%s' "$LIC_RISP" | tr ',' '\n' | grep -E '"(azienda|ip|pacchetto|ruolo)"' | tr -d '"' | tr '\n' ' ')" printf '%s' "$LIC_RISP" > /tmp/lattice-licenza.json mkdir -p /opt/lattice-node 2>/dev/null && cp -f /tmp/lattice-licenza.json /opt/lattice-node/licenza.json 2>/dev/null || true ;; *) echo echo " ATTIVAZIONE RIFIUTATA — non è stato installato niente su questo server." printf '%s' "$LIC_RISP" | tr ',' '\n' | grep -i detail | tr -d '"{}' | sed 's/^/ /' echo " Se l'IP del server è cambiato, chiedi al proprietario della rete una licenza nuova." exit 2 ;; esac fi # ══════════════════════════════════════════════════════════════════════════════ # LATTICE SOVEREIGN NODE — installer v2.8.0 «The Sovereign Seed» # # curl -sSL https://lattice-network.it/install | bash # # Cosa fa, in ordine, dicendolo prima di farlo: # 1. chiede il NOME DEL NODO (l'unica cosa che ti chiede) # 2. controlla di essere root e riconosce la distribuzione # 3. installa quello che manca: Docker, il plugin compose, nginx, curl, tar # 4. SCARICA L'IMMAGINE GIÀ COMPILATA dal registro privato del progetto # (registry.lattice-network.it) e ne confronta il digest con quello pubblicato # 5. genera la configurazione in /opt/lattice-node # 6. avvia database e nodo con docker compose # 7. mette nginx davanti sulla porta 80 # 8. aspetta che il nodo si annunci alla catena e stampa il suo identificativo # # SCATOLA CHIUSA — dalla v2.8.0 il nodo NON arriva più come codice sorgente da # compilare: arriva come immagine Docker già compilata, con i simboli rimossi. Sul tuo # server non resta nessun sorgente del server, e l'installazione dura minuti invece di # decine di minuti. Se il registro non è raggiungibile l'installer RIPIEGA sul pacchetto # sorgente della versione precedente, così una macchina isolata riesce comunque a # installare. Puoi forzare la scelta con LATTICE_FROM_SOURCE=1 . # # Cosa NON fa, e va detto: non tocca il consenso. Il nodo entra come peer/relay e # inoltra traffico. La candidatura a validatore resta in attesa di approvazione: nessuna # installazione può allargare da sola l'insieme di chi firma i blocchi. # # Per disinstallare: cd /opt/lattice-node && docker compose down -v && rm -rf /opt/lattice-node # ══════════════════════════════════════════════════════════════════════════════ set -euo pipefail HOME_URL="${LATTICE_HOME_URL:-https://lattice-network.it}" VERSION="${LATTICE_NODE_VERSION:-2.8.0}" # Versione del pacchetto SORGENTE usato solo come ripiego se il registro non risponde. SRC_VERSION="${LATTICE_SRC_VERSION:-2.8.0}" REGISTRY="${LATTICE_REGISTRY:-registry.lattice-network.it}" IMAGE_REPO="${LATTICE_IMAGE:-lattice/node}" DIR="${LATTICE_NODE_DIR:-/opt/lattice-node}" ROLE="${LATTICE_NODE_ROLE:-peer}" REGION="${LATTICE_NODE_REGION:-}" PORT="${LATTICE_NODE_PORT:-8080}" C_R='\033[0m'; C_G='\033[38;5;79m'; C_D='\033[38;5;244m'; C_W='\033[1;97m'; C_E='\033[38;5;203m' say() { printf "${C_G}▸${C_R} %s\n" "$*"; } info() { printf " ${C_D}%s${C_R}\n" "$*"; } die() { printf "${C_E}✗ %s${C_R}\n" "$*" >&2; exit 1; } banner() { cat <<'EOF' ╱╲ L A T T I C E S O V E R E I G N N O D E ╱ ╲ v2.8.0 · The Sovereign Seed ╲ ╱ post-quantum · mesh · nessuna Big Tech ╲╱ EOF } banner [ "$(id -u)" = "0" ] || die "Serve root. Riprova con: curl -sSL $HOME_URL/install | sudo bash" # ── 1. nome del nodo ───────────────────────────────────────────────────────── # `curl … | bash` non ha stdin: la domanda si fa su /dev/tty, che è il terminale vero. # Se non c'è un terminale (automazioni, immagini cloud) si usa LATTICE_NODE_NAME oppure # il nome host: l'installer non si blocca mai ad aspettare una risposta che non arriverà. DEFAULT_NAME="$(hostname -s 2>/dev/null || echo node)" NAME="${LATTICE_NODE_NAME:-}" if [ -z "$NAME" ] && [ -r /dev/tty ]; then printf "${C_W}Come si chiama questo nodo?${C_R}\n" info "è l'etichetta con cui compare nella Costellazione (2-32 caratteri)" for _try in 1 2 3; do printf " ${C_G}nome del nodo${C_R} [${DEFAULT_NAME}]: " IFS= read -r ANSWER < /dev/tty || ANSWER="" ANSWER="$(printf '%s' "${ANSWER:-$DEFAULT_NAME}" | tr -d '\r' | sed 's/^ *//; s/ *$//')" if printf '%s' "$ANSWER" | grep -Eq '^[A-Za-z0-9][A-Za-z0-9 ._-]{1,31}$'; then NAME="$ANSWER"; break fi printf " ${C_E}solo lettere, numeri, spazio, punto, trattino e underscore (2-32)${C_R}\n" done fi NAME="${NAME:-$DEFAULT_NAME}" say "Nome del nodo: $NAME" # ── 2. distribuzione ───────────────────────────────────────────────────────── # /etc/os-release definisce anche VERSION e HOME_URL: caricarlo nella shell principale # sovrascriveva le nostre variabili e l'installer andava a cercare il pacchetto su # www.debian.org. Lo si legge in una sottoshell, che e' l'unico modo sicuro. OS=""; PKG=""; PRETTY="" if [ -r /etc/os-release ]; then OS="$(. /etc/os-release 2>/dev/null; printf '%s' "${ID:-}")" PRETTY="$(. /etc/os-release 2>/dev/null; printf '%s' "${PRETTY_NAME:-}")" fi case "$OS" in ubuntu|debian|raspbian) PKG="apt" ;; fedora|rhel|centos|rocky|almalinux) PKG="dnf" ;; *) if command -v apt-get >/dev/null 2>&1; then PKG="apt" elif command -v dnf >/dev/null 2>&1; then PKG="dnf" else die "Distribuzione non riconosciuta. Su macOS e Windows si usa Docker Desktop: $HOME_URL/install" fi ;; esac say "Sistema: ${PRETTY:-$OS} · gestore pacchetti: $PKG" ARCH="$(uname -m)" case "$ARCH" in x86_64|amd64|aarch64|arm64) : ;; *) die "Architettura $ARCH non supportata: serve x86_64 o arm64." ;; esac # ── 3. dipendenze ──────────────────────────────────────────────────────────── say "Controllo delle dipendenze" need_pkgs=() command -v curl >/dev/null 2>&1 || need_pkgs+=(curl) command -v tar >/dev/null 2>&1 || need_pkgs+=(tar) command -v nginx >/dev/null 2>&1 || need_pkgs+=(nginx) if [ "${#need_pkgs[@]}" -gt 0 ]; then info "installo: ${need_pkgs[*]}" if [ "$PKG" = "apt" ]; then DEBIAN_FRONTEND=noninteractive apt-get update -qq DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "${need_pkgs[@]}" >/dev/null else dnf install -y -q "${need_pkgs[@]}" >/dev/null fi else info "curl, tar e nginx già presenti" fi if ! command -v docker >/dev/null 2>&1; then say "Installo Docker (script ufficiale get.docker.com)" curl -fsSL https://get.docker.com | sh >/dev/null else info "Docker già presente: $(docker --version | cut -d, -f1)" fi systemctl enable --now docker >/dev/null 2>&1 || true if ! docker compose version >/dev/null 2>&1; then say "Installo il plugin compose" if [ "$PKG" = "apt" ]; then DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-compose-plugin >/dev/null else dnf install -y -q docker-compose-plugin >/dev/null fi fi docker compose version >/dev/null 2>&1 || die "Il plugin docker compose non è disponibile." mkdir -p "$DIR" # ── 4. immagine già compilata dal registro privato ────────────────────────── IMAGE="$REGISTRY/$IMAGE_REPO:$VERSION" BLACKBOX=0 if [ "${LATTICE_FROM_SOURCE:-0}" = "1" ]; then info "richiesta esplicita di compilare da sorgente: salto il registro" else say "Scarico l'immagine già compilata: $IMAGE" if [ -n "${LATTICE_REGISTRY_USER:-}" ] && [ -n "${LATTICE_REGISTRY_TOKEN:-}" ]; then printf '%s' "$LATTICE_REGISTRY_TOKEN" | docker login "$REGISTRY" -u "$LATTICE_REGISTRY_USER" --password-stdin >/dev/null 2>&1 \ || info "accesso al registro rifiutato: provo comunque in lettura anonima" fi if docker pull "$IMAGE" >/tmp/lattice-pull.log 2>&1; then BLACKBOX=1 # Il digest è l'impronta dell'immagine: si confronta con quella pubblicata sul sito. GOT_DIGEST="$(docker image inspect "$IMAGE" --format '{{index .RepoDigests 0}}' 2>/dev/null | awk -F@ '{print $2}')" WANT_DIGEST="$(curl -fsSL "$HOME_URL/downloads/lattice-node-$VERSION.digest" 2>/dev/null | tr -d ' \n' || true)" if [ -n "$WANT_DIGEST" ] && [ -n "$GOT_DIGEST" ]; then [ "$WANT_DIGEST" = "$GOT_DIGEST" ] || die "Digest diverso da quello pubblicato. NON procedo. atteso: $WANT_DIGEST ottenuto: $GOT_DIGEST" say "Digest verificato: ${GOT_DIGEST:0:23}…" else info "digest pubblicato non trovato: proseguo, ma questa copia non è verificata" fi else info "registro non raggiungibile ($(tail -1 /tmp/lattice-pull.log 2>/dev/null | cut -c1-70))" fi fi if [ "$BLACKBOX" = "1" ]; then say "Scrivo il compose della scatola chiusa (nessun sorgente sul tuo server)" cat > "$DIR/docker-compose.yml" </dev/null || exit 1"] interval: 30s timeout: 5s retries: 5 volumes: mongo-data: node-data: EOF else # ── ripiego: pacchetto sorgente, compilato dentro il container ───────────── say "Ripiego sul pacchetto sorgente v$SRC_VERSION" TGZ="/tmp/lattice-node-$SRC_VERSION.tgz" curl -fsSL "$HOME_URL/downloads/lattice-node-$SRC_VERSION.tgz" -o "$TGZ" \ || die "Né immagine né pacchetto disponibili. Riprova più tardi." WANT="$(curl -fsSL "$HOME_URL/downloads/lattice-node-$SRC_VERSION.tgz.sha256" 2>/dev/null | awk '{print $1}' || true)" GOT="$(sha256sum "$TGZ" | awk '{print $1}')" if [ -n "$WANT" ]; then [ "$WANT" = "$GOT" ] || die "Impronta diversa da quella pubblicata. NON procedo. attesa: $WANT ottenuta: $GOT" say "Impronta verificata: ${GOT:0:16}…" else info "impronta pubblicata non trovata: proseguo, ma questa copia non è verificata" fi tar -xzf "$TGZ" -C "$DIR" --strip-components=1 rm -f "$TGZ" fi # ── 5. configurazione ──────────────────────────────────────────────────────── say "Scrivo la configurazione in $DIR/.env" PUBIP="$(curl -fsS --max-time 6 https://api.ipify.org 2>/dev/null || echo "")" ENDPOINT="${LATTICE_NODE_ENDPOINT:-${PUBIP:+http://$PUBIP:$PORT}}" if [ -f "$DIR/.env" ]; then info ".env già presente: lo tengo (la chiave del nodo resta la stessa)" # Il nome è l'unica cosa che si aggiorna: chi reinstalla per rinominare il nodo # non deve perdere la chiave d'identità per farlo. if grep -q '^LATTICE_NODE_NAME=' "$DIR/.env"; then sed -i "s|^LATTICE_NODE_NAME=.*|LATTICE_NODE_NAME=$NAME|" "$DIR/.env" else printf 'LATTICE_NODE_NAME=%s\n' "$NAME" >> "$DIR/.env" fi else umask 077 cat > "$DIR/.env" </dev/null && info "docker-compose.yml valido" elif [ "$BLACKBOX" = "1" ]; then docker compose up -d else docker compose up -d --build fi # ── 7. nginx davanti ──────────────────────────────────────────────────────── say "Configuro nginx sulla porta 80" CONF=/etc/nginx/conf.d/lattice-node.conf [ -d /etc/nginx/conf.d ] || mkdir -p /etc/nginx/conf.d cat > "$CONF" </dev/null || true nginx -t >/dev/null 2>&1 && (systemctl reload nginx 2>/dev/null || systemctl restart nginx 2>/dev/null || true) # ── 8. attesa dell'annuncio ───────────────────────────────────────────────── if [ "${LATTICE_DRY_RUN:-0}" = "1" ]; then say "Prova a secco completata: nome, dipendenze, immagine, configurazione e nginx sono a posto" printf " modalità: %s\n" "$([ "$BLACKBOX" = "1" ] && echo 'scatola chiusa (immagine dal registro)' || echo 'ripiego da sorgente')" exit 0 fi say "Aspetto che il nodo si presenti alla catena" NODE_ID="" for i in $(seq 1 40); do NODE_ID="$(docker compose logs --no-color node 2>/dev/null | grep -o 'identità [0-9a-f]\{32\}' | tail -1 | awk '{print $2}')" [ -n "$NODE_ID" ] && break sleep 6 done printf "\n" if [ -n "$NODE_ID" ]; then ONLINE="$(curl -fsS "$HOME_URL/api/public/nodes" 2>/dev/null | grep -c "$NODE_ID" || echo 0)" printf "${C_G}════════════════════════════════════════════════════════════${C_R}\n" printf " ${C_W}NODO ATTIVO${C_R}\n\n" printf " nome ${C_W}%s${C_R}\n" "$NAME" printf " identificativo ${C_G}%s${C_R}\n" "$NODE_ID" printf " ruolo %s\n" "$ROLE" printf " provenienza %s\n" "$([ "$BLACKBOX" = "1" ] && echo "immagine firmata $IMAGE" || echo "sorgente v$SRC_VERSION")" printf " indirizzo %s\n" "${ENDPOINT:-non rilevato}" printf " validatore in attesa di approvazione\n" if [ "$ONLINE" != "0" ]; then printf " costellazione ${C_G}visibile${C_R} → %s/explorer\n" "$HOME_URL" else printf " costellazione annuncio in corso, ricontrolla fra un minuto\n" fi printf "${C_G}════════════════════════════════════════════════════════════${C_R}\n\n" printf " registro: cd %s && docker compose logs -f node\n" "$DIR" printf " stato: curl -s %s/api/public/nodes | head\n" "$HOME_URL" printf " spegnimento: cd %s && docker compose down\n\n" "$DIR" else printf "${C_E}Il nodo non si è ancora annunciato.${C_R}\n" printf " Guarda cosa dice: cd %s && docker compose logs --tail=80 node\n\n" "$DIR" exit 1 fi