feat(phase6): wizard licenza + bootstrap one-liner
- first-setup.sh: rimosso token hardcoded + clone/venv spostati nel wizard - setup_server.py: step licenza (verify Ed25519, machine_id match) + clone argos con URL autenticato da license.json - setup.html: nuovo step 1 'Licenza ARGOS' con machine_id display + upload - bootstrap.sh: one-liner installer per setup rapido su VM vergine - README: documentazione flusso nuovo
This commit is contained in:
parent
7d2c1d8809
commit
5e9a916515
|
|
@ -0,0 +1,4 @@
|
||||||
|
.backup/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.DS_Store
|
||||||
54
README.md
54
README.md
|
|
@ -4,25 +4,61 @@ Tecnotel Servizi SRL — [www.tecnotelsrl.com](https://www.tecnotelsrl.com)
|
||||||
|
|
||||||
Repository pubblico contenente l'installer di prima fase e il Web Setup Wizard di ARGOS SOC.
|
Repository pubblico contenente l'installer di prima fase e il Web Setup Wizard di ARGOS SOC.
|
||||||
|
|
||||||
|
## Installazione rapida (one-liner)
|
||||||
|
|
||||||
|
Su una macchina Ubuntu 24.04 LTS vergine, esegui:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://argos-update.tecnotelsrl.com/tecnotel/argos-setup/raw/branch/main/bootstrap.sh | sudo bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Il bootstrap scaricherà il repository e avvierà `first-setup.sh`.
|
||||||
|
|
||||||
|
## Installazione manuale
|
||||||
|
|
||||||
|
Se preferisci eseguire gli step separatamente:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt update && sudo apt install -y git
|
||||||
|
git clone https://argos-update.tecnotelsrl.com/tecnotel/argos-setup.git /opt/argos-setup-pkg
|
||||||
|
cd /opt/argos-setup-pkg
|
||||||
|
sudo bash first-setup.sh
|
||||||
|
```
|
||||||
|
|
||||||
## Contenuto
|
## Contenuto
|
||||||
|
|
||||||
| File | Scopo |
|
| File | Scopo |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `first-setup.sh` | Installer ambiente di prima fase: sistema base, utenti, firewall, virtualenv, nginx. |
|
| `bootstrap.sh` | One-liner installer: scarica il repo e avvia first-setup.sh |
|
||||||
| `setup_server.py` | Backend Flask del Web Installer (porta 8888). |
|
| `first-setup.sh` | Installer ambiente base: sistema, utenti, firewall, nginx temp, avvia wizard web |
|
||||||
| `setup.html` | Frontend del Web Installer, wizard in 5 step. |
|
| `setup_server.py` | Backend Python del Web Installer (porta 8888, self-contained) |
|
||||||
| `gen_config.py` | Helper per generazione iniziale di `argos.json`. |
|
| `setup.html` | Frontend del Web Installer — wizard in 6 step |
|
||||||
|
| `gen_config.py` | Helper per generazione iniziale di `argos.json` |
|
||||||
|
|
||||||
## Flusso d'installazione
|
## Flusso d'installazione
|
||||||
|
|
||||||
1. Il cliente esegue `first-setup.sh` per predisporre l'ambiente base (Ubuntu 24.04).
|
1. Il cliente esegue il bootstrap (o il clone manuale + first-setup.sh).
|
||||||
2. Al termine dello script viene avviato il Web Installer su `http://IP:8888`.
|
2. `first-setup.sh` predispone l'ambiente base Ubuntu 24.04 (pacchetti, utente argos, cartelle, firewall) e avvia il Web Installer su `http://IP:8888`.
|
||||||
3. Il wizard completa la configurazione: cliente, rete/SSL, SIEM, utente admin.
|
3. Il cliente apre il browser e segue il wizard in 6 step:
|
||||||
4. Al termine del wizard, `argos-setup` richiede la licenza ARGOS per sbloccare il clone del repository privato `tecnotel/argos` e completare l'installazione dei componenti runtime.
|
1. **Licenza ARGOS** — il wizard mostra il `machine_id` di questo server. Il cliente lo invia a Tecnotel, riceve `license.json`, lo carica nel wizard.
|
||||||
|
2. **Informazioni cliente** (nome, dominio, logo)
|
||||||
|
3. **Rete & SSL** (hostname, certificato)
|
||||||
|
4. **SIEM** (OpenSearch)
|
||||||
|
5. **Utente admin**
|
||||||
|
6. **Riepilogo & Installazione** — il wizard clona il repository privato `tecnotel/argos` usando il token della licenza, crea virtualenv, configura servizi e avvia ARGOS SOC.
|
||||||
|
|
||||||
|
Al termine il Web Installer viene disattivato automaticamente e la porta 8888 chiusa.
|
||||||
|
|
||||||
|
## Requisiti
|
||||||
|
|
||||||
|
- Ubuntu 24.04 LTS (verificato dallo script)
|
||||||
|
- Accesso root (sudo)
|
||||||
|
- Connessione internet verso `argos-update.tecnotelsrl.com`
|
||||||
|
- Una licenza ARGOS valida emessa da Tecnotel per il `machine_id` del server
|
||||||
|
|
||||||
## Repository correlati
|
## Repository correlati
|
||||||
|
|
||||||
- [`tecnotel/argos`](https://argos-update.tecnotelsrl.com/tecnotel/argos) — codice runtime di ARGOS SOC (privato).
|
- [`tecnotel/argos`](https://argos-update.tecnotelsrl.com/tecnotel/argos) — codice runtime di ARGOS SOC (privato, accessibile solo con licenza).
|
||||||
|
|
||||||
## Versioning
|
## Versioning
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# ARGOS SOC — Bootstrap Installer (one-liner)
|
||||||
|
# Tecnotel Servizi SRL — Ubuntu 24.04 LTS
|
||||||
|
#
|
||||||
|
# Uso tramite one-liner:
|
||||||
|
# curl -fsSL https://argos-update.tecnotelsrl.com/tecnotel/argos-setup/raw/branch/main/bootstrap.sh | sudo bash
|
||||||
|
#
|
||||||
|
# Oppure manuale (raccomandato per verifica):
|
||||||
|
# curl -fsSLo /tmp/argos-bootstrap.sh https://argos-update.tecnotelsrl.com/tecnotel/argos-setup/raw/branch/main/bootstrap.sh
|
||||||
|
# less /tmp/argos-bootstrap.sh # verifica cosa fa
|
||||||
|
# sudo bash /tmp/argos-bootstrap.sh
|
||||||
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'; CYAN='\033[0;36m'; NC='\033[0m'
|
||||||
|
info() { echo -e "${CYAN}[INFO]${NC} $1"; }
|
||||||
|
success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
||||||
|
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
|
error() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; }
|
||||||
|
|
||||||
|
# ── Check privilegi e OS ──────────────────────────────────────────────────────
|
||||||
|
[[ $EUID -ne 0 ]] && error "Eseguire come root (sudo)."
|
||||||
|
|
||||||
|
if [[ ! -f /etc/os-release ]]; then
|
||||||
|
error "OS non riconosciuto: /etc/os-release mancante."
|
||||||
|
fi
|
||||||
|
. /etc/os-release
|
||||||
|
if [[ "$ID" != "ubuntu" || "$VERSION_ID" != "24.04" ]]; then
|
||||||
|
error "Richiesto Ubuntu 24.04 LTS (trovato: $ID $VERSION_ID)."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}╔══════════════════════════════════════════════════╗${NC}"
|
||||||
|
echo -e "${BLUE}║ ARGOS SOC — Bootstrap Installer ║${NC}"
|
||||||
|
echo -e "${BLUE}║ Tecnotel Servizi SRL ║${NC}"
|
||||||
|
echo -e "${BLUE}╚══════════════════════════════════════════════════╝${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── Variabili ────────────────────────────────────────────────────────────────
|
||||||
|
SETUP_REPO_URL="https://argos-update.tecnotelsrl.com/tecnotel/argos-setup.git"
|
||||||
|
SETUP_DIR="/opt/argos-setup-pkg"
|
||||||
|
|
||||||
|
# ── Install git se mancante ──────────────────────────────────────────────────
|
||||||
|
if ! command -v git >/dev/null 2>&1; then
|
||||||
|
info "Git non installato — installo..."
|
||||||
|
apt-get update -qq
|
||||||
|
apt-get install -y -qq git
|
||||||
|
success "Git installato"
|
||||||
|
else
|
||||||
|
info "Git gia' presente ($(git --version | awk '{print $3}'))"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Scarica o aggiorna argos-setup ───────────────────────────────────────────
|
||||||
|
if [[ -d "$SETUP_DIR/.git" ]]; then
|
||||||
|
info "argos-setup gia' presente in $SETUP_DIR — aggiorno..."
|
||||||
|
git -C "$SETUP_DIR" pull --ff-only origin main
|
||||||
|
success "argos-setup aggiornato"
|
||||||
|
else
|
||||||
|
info "Scarico argos-setup da $SETUP_REPO_URL..."
|
||||||
|
rm -rf "$SETUP_DIR"
|
||||||
|
git clone --depth=1 "$SETUP_REPO_URL" "$SETUP_DIR"
|
||||||
|
success "argos-setup scaricato in $SETUP_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Verifica presenza file attesi ────────────────────────────────────────────
|
||||||
|
for f in first-setup.sh setup_server.py setup.html; do
|
||||||
|
if [[ ! -f "$SETUP_DIR/$f" ]]; then
|
||||||
|
error "File $f mancante in $SETUP_DIR — repo argos-setup incompleto?"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Lancia first-setup.sh ────────────────────────────────────────────────────
|
||||||
|
info "Avvio installer principale..."
|
||||||
|
echo ""
|
||||||
|
cd "$SETUP_DIR"
|
||||||
|
exec bash ./first-setup.sh
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
# ARGOS SOC — Installer ambiente
|
# ARGOS SOC — Installer ambiente
|
||||||
# Tecnotel Servizi SRL — Ubuntu 24.04 LTS
|
# Tecnotel Servizi SRL — Ubuntu 24.04 LTS
|
||||||
# Uso: sudo bash install.sh
|
# Uso: sudo bash first-setup.sh
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -14,20 +14,18 @@ warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||||
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||||
section() { echo -e "\n${BLUE}══════════════════════════════════════${NC}"; echo -e "${BLUE} $1${NC}"; echo -e "${BLUE}══════════════════════════════════════${NC}"; }
|
section() { echo -e "\n${BLUE}══════════════════════════════════════${NC}"; echo -e "${BLUE} $1${NC}"; echo -e "${BLUE}══════════════════════════════════════${NC}"; }
|
||||||
|
|
||||||
[[ $EUID -ne 0 ]] && error "Eseguire come root: sudo bash install.sh"
|
[[ $EUID -ne 0 ]] && error "Eseguire come root: sudo bash first-setup.sh"
|
||||||
. /etc/os-release
|
. /etc/os-release
|
||||||
[[ "$ID" != "ubuntu" || "$VERSION_ID" != "24.04" ]] && error "Richiesto Ubuntu 24.04 LTS"
|
[[ "$ID" != "ubuntu" || "$VERSION_ID" != "24.04" ]] && error "Richiesto Ubuntu 24.04 LTS"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${BLUE}╔══════════════════════════════════════════╗${NC}"
|
echo -e "${BLUE}╔══════════════════════════════════════════╗${NC}"
|
||||||
echo -e "${BLUE}║ ARGOS SOC — Installer v1.0.0 ║${NC}"
|
echo -e "${BLUE}║ ARGOS SOC — Setup ambiente base ║${NC}"
|
||||||
echo -e "${BLUE}║ Tecnotel Servizi SRL ║${NC}"
|
echo -e "${BLUE}║ Tecnotel Servizi SRL ║${NC}"
|
||||||
echo -e "${BLUE}╚══════════════════════════════════════════╝${NC}"
|
echo -e "${BLUE}╚══════════════════════════════════════════╝${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
|
||||||
GITEA_REPO="https://3eefb5a2802e8c5a9395396b1bb98e2d5fe46101@argos-update.tecnotelsrl.com:3443/tecnotel/argos.git"
|
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "1. Sistema base"
|
section "1. Sistema base"
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
@ -36,7 +34,7 @@ apt-get update -qq
|
||||||
apt-get upgrade -y -qq
|
apt-get upgrade -y -qq
|
||||||
apt-get install -y -qq \
|
apt-get install -y -qq \
|
||||||
curl wget git vim htop unzip jq \
|
curl wget git vim htop unzip jq \
|
||||||
python3 python3-pip python3-venv \
|
python3 python3-pip python3-venv python3-cryptography \
|
||||||
nginx certbot python3-certbot-nginx \
|
nginx certbot python3-certbot-nginx \
|
||||||
ufw fail2ban \
|
ufw fail2ban \
|
||||||
build-essential libssl-dev libffi-dev python3-dev \
|
build-essential libssl-dev libffi-dev python3-dev \
|
||||||
|
|
@ -92,33 +90,7 @@ chmod 755 /opt/argos/feeds
|
||||||
success "Struttura /opt/argos/ creata"
|
success "Struttura /opt/argos/ creata"
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "5. Clone repository"
|
section "5. Firewall UFW"
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
|
||||||
git config --global --add safe.directory /opt/argos/app 2>/dev/null || true
|
|
||||||
if [[ -d /opt/argos/app/.git ]]; then
|
|
||||||
warn "Repository già presente — aggiorno"
|
|
||||||
git -C /opt/argos/app pull origin main
|
|
||||||
else
|
|
||||||
git clone "$GITEA_REPO" /opt/argos/app
|
|
||||||
fi
|
|
||||||
chown -R argos:argos /opt/argos/app
|
|
||||||
success "Repository clonato in /opt/argos/app/"
|
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
|
||||||
section "6. Python virtualenv"
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
|
||||||
python3 -m venv /opt/argos/app/backend/venv
|
|
||||||
/opt/argos/app/backend/venv/bin/pip install --upgrade pip -q
|
|
||||||
if [[ -f /opt/argos/app/backend/requirements.txt ]]; then
|
|
||||||
/opt/argos/app/backend/venv/bin/pip install -r /opt/argos/app/backend/requirements.txt -q
|
|
||||||
success "Dipendenze Python installate"
|
|
||||||
else
|
|
||||||
warn "requirements.txt non trovato — installare manualmente dopo"
|
|
||||||
fi
|
|
||||||
chown -R argos:argos /opt/argos/app/backend/venv
|
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
|
||||||
section "7. Firewall UFW"
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
ufw --force reset >/dev/null
|
ufw --force reset >/dev/null
|
||||||
ufw default deny incoming >/dev/null
|
ufw default deny incoming >/dev/null
|
||||||
|
|
@ -131,13 +103,13 @@ ufw --force enable >/dev/null
|
||||||
success "Firewall UFW configurato"
|
success "Firewall UFW configurato"
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "8. Fail2ban"
|
section "6. Fail2ban"
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
systemctl enable --now fail2ban >/dev/null 2>&1
|
systemctl enable --now fail2ban >/dev/null 2>&1
|
||||||
success "Fail2ban attivo"
|
success "Fail2ban attivo"
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "9. Nginx temporaneo"
|
section "7. Nginx temporaneo"
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
rm -f /etc/nginx/sites-enabled/default
|
rm -f /etc/nginx/sites-enabled/default
|
||||||
cat > /etc/nginx/sites-available/argos-setup << 'NGINX'
|
cat > /etc/nginx/sites-available/argos-setup << 'NGINX'
|
||||||
|
|
@ -153,11 +125,18 @@ nginx -t && systemctl restart nginx
|
||||||
success "Nginx temporaneo configurato"
|
success "Nginx temporaneo configurato"
|
||||||
|
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
section "10. Web Installer"
|
section "8. Web Installer"
|
||||||
# ══════════════════════════════════════════════════════════════════════════════
|
# ══════════════════════════════════════════════════════════════════════════════
|
||||||
# Copia file setup
|
# Copia file setup dalla directory dello script (argos-setup tarball)
|
||||||
cp /opt/argos/app/scripts/setup_server.py /opt/argos/setup/
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
cp /opt/argos/app/scripts/setup.html /opt/argos/setup/
|
for f in setup_server.py setup.html gen_config.py; do
|
||||||
|
if [[ -f "$SCRIPT_DIR/$f" ]]; then
|
||||||
|
cp "$SCRIPT_DIR/$f" /opt/argos/setup/
|
||||||
|
else
|
||||||
|
error "File $f non trovato in $SCRIPT_DIR — installare argos-setup completo"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
chown -R root:root /opt/argos/setup
|
||||||
|
|
||||||
# Systemd service web installer
|
# Systemd service web installer
|
||||||
cat > /etc/systemd/system/argos-setup.service << 'EOF'
|
cat > /etc/systemd/system/argos-setup.service << 'EOF'
|
||||||
|
|
@ -169,7 +148,7 @@ After=network.target
|
||||||
Type=simple
|
Type=simple
|
||||||
User=root
|
User=root
|
||||||
WorkingDirectory=/opt/argos/setup
|
WorkingDirectory=/opt/argos/setup
|
||||||
ExecStart=/opt/argos/app/backend/venv/bin/python3 /opt/argos/setup/setup_server.py
|
ExecStart=/usr/bin/python3 /opt/argos/setup/setup_server.py
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=3
|
RestartSec=3
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
|
|
|
||||||
170
setup.html
170
setup.html
|
|
@ -95,11 +95,12 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<div class="sidebar-title">Configurazione</div>
|
<div class="sidebar-title">Configurazione</div>
|
||||||
<div class="tab-item active" onclick="goTab(0)" id="tab-0"><div class="tab-num">1</div> Cliente</div>
|
<div class="tab-item active" onclick="goTab(0)" id="tab-0"><div class="tab-num">1</div> Licenza</div>
|
||||||
<div class="tab-item" onclick="goTab(1)" id="tab-1"><div class="tab-num">2</div> Rete & SSL</div>
|
<div class="tab-item" onclick="goTab(1)" id="tab-1"><div class="tab-num">2</div> Cliente</div>
|
||||||
<div class="tab-item" onclick="goTab(2)" id="tab-2"><div class="tab-num">3</div> SIEM</div>
|
<div class="tab-item" onclick="goTab(2)" id="tab-2"><div class="tab-num">3</div> Rete & SSL</div>
|
||||||
<div class="tab-item" onclick="goTab(3)" id="tab-3"><div class="tab-num">4</div> Utente admin</div>
|
<div class="tab-item" onclick="goTab(3)" id="tab-3"><div class="tab-num">4</div> SIEM</div>
|
||||||
<div class="tab-item" onclick="goTab(4)" id="tab-4"><div class="tab-num">5</div> Installa</div>
|
<div class="tab-item" onclick="goTab(4)" id="tab-4"><div class="tab-num">5</div> Utente admin</div>
|
||||||
|
<div class="tab-item" onclick="goTab(5)" id="tab-5"><div class="tab-num">6</div> Installa</div>
|
||||||
</aside>
|
</aside>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
|
|
@ -108,10 +109,62 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
||||||
<div class="prog-step" id="ps-2"></div>
|
<div class="prog-step" id="ps-2"></div>
|
||||||
<div class="prog-step" id="ps-3"></div>
|
<div class="prog-step" id="ps-3"></div>
|
||||||
<div class="prog-step" id="ps-4"></div>
|
<div class="prog-step" id="ps-4"></div>
|
||||||
|
<div class="prog-step" id="ps-5"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 1: Cliente -->
|
<!-- Tab 1: Licenza ARGOS -->
|
||||||
<div class="tab-panel active" id="panel-0">
|
<div class="tab-panel active" id="panel-0">
|
||||||
|
<div class="panel-title">🔑 Licenza ARGOS</div>
|
||||||
|
<div class="panel-sub">ARGOS SOC richiede una licenza valida emessa da Tecnotel Servizi SRL,
|
||||||
|
vincolata all'identificativo hardware di questo server.</div>
|
||||||
|
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="sec-div" style="grid-column:1/-1">1. Identificativo hardware del server</div>
|
||||||
|
<div class="form-full">
|
||||||
|
<label class="form-label">Machine ID (SHA256 hex)</label>
|
||||||
|
<div style="display:flex;gap:8px;align-items:stretch">
|
||||||
|
<input class="form-input mono-input" id="machine-id-display" value="Calcolo in corso..." readonly style="flex:1">
|
||||||
|
<button class="btn btn-ghost" onclick="copyMachineId()" id="btn-copy-mid" style="padding:10px 16px">📋 Copia</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-hint">Invia questo ID a <strong>Tecnotel Servizi SRL</strong> per ricevere la licenza.
|
||||||
|
Email: <strong>info@tecnotelsrl.com</strong></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sec-div" style="grid-column:1/-1">2. Carica license.json ricevuta</div>
|
||||||
|
<div class="upload-area form-full" id="lic-drop"
|
||||||
|
ondragover="event.preventDefault();this.classList.add('drag')"
|
||||||
|
ondragleave="this.classList.remove('drag')"
|
||||||
|
ondrop="handleLicenseDrop(event)"
|
||||||
|
onclick="document.getElementById('lic-input').click()">
|
||||||
|
<input type="file" id="lic-input" accept=".json,application/json" style="display:none" onchange="handleLicenseFile(this)">
|
||||||
|
<div style="font-size:24px;margin-bottom:6px">📜</div>
|
||||||
|
<div style="font-size:13px;color:var(--text2)">Trascina license.json o clicca per selezionare</div>
|
||||||
|
<div style="font-size:11px;color:var(--text3);margin-top:3px">File JSON firmato Ed25519 — max 10KB</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="lic-error" class="form-full" style="display:none;background:var(--err-dim);border:1px solid var(--err);border-radius:var(--r);padding:12px 16px;color:var(--err);font-size:13px"></div>
|
||||||
|
|
||||||
|
<div id="lic-summary" class="form-full" style="display:none;background:var(--ok-dim);border:1px solid var(--ok);border-radius:var(--r);padding:16px 20px">
|
||||||
|
<div style="font-size:14px;font-weight:700;color:var(--ok);margin-bottom:10px">✅ Licenza valida</div>
|
||||||
|
<div style="display:grid;grid-template-columns:auto 1fr;gap:6px 14px;font-size:12px;font-family:var(--mono)">
|
||||||
|
<div style="color:var(--text3)">Cliente:</div> <div id="lic-customer"></div>
|
||||||
|
<div style="color:var(--text3)">Tier:</div> <div id="lic-tier"></div>
|
||||||
|
<div style="color:var(--text3)">Hostname:</div> <div id="lic-hostname"></div>
|
||||||
|
<div style="color:var(--text3)">Emessa:</div> <div id="lic-issued"></div>
|
||||||
|
<div style="color:var(--text3)">Scadenza:</div> <div id="lic-expires"></div>
|
||||||
|
<div style="color:var(--text3)">Token Gitea:</div><div id="lic-gitea"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav-row">
|
||||||
|
<div></div>
|
||||||
|
<button class="btn btn-brand" id="btn-after-license" onclick="goTab(1)" disabled style="opacity:.4;cursor:not-allowed">Avanti →</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab 2: Cliente -->
|
||||||
|
<div class="tab-panel" id="panel-1">
|
||||||
<div class="panel-title">👤 Informazioni cliente</div>
|
<div class="panel-title">👤 Informazioni cliente</div>
|
||||||
<div class="panel-sub">Dati dell'organizzazione che utilizzerà ARGOS SOC.</div>
|
<div class="panel-sub">Dati dell'organizzazione che utilizzerà ARGOS SOC.</div>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
|
|
@ -129,11 +182,11 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
||||||
<div class="upload-preview" id="logo-preview"><img id="logo-img" src=""><div style="font-size:11px;color:var(--ok);font-family:var(--mono)" id="logo-fname"></div></div>
|
<div class="upload-preview" id="logo-preview"><img id="logo-img" src=""><div style="font-size:11px;color:var(--ok);font-family:var(--mono)" id="logo-fname"></div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-row"><div></div><button class="btn btn-brand" onclick="goTab(1)">Avanti →</button></div>
|
<div class="nav-row"><div></div><button class="btn btn-brand" onclick="goTab(2)">Avanti →</button></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 2: Rete & SSL -->
|
<!-- Tab 3: Rete & SSL -->
|
||||||
<div class="tab-panel" id="panel-1">
|
<div class="tab-panel" id="panel-2">
|
||||||
<div class="panel-title">🌐 Rete & SSL</div>
|
<div class="panel-title">🌐 Rete & SSL</div>
|
||||||
<div class="panel-sub">Configurazione dominio e certificato SSL. Il DNS deve già puntare a questo server.</div>
|
<div class="panel-sub">Configurazione dominio e certificato SSL. Il DNS deve già puntare a questo server.</div>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
|
|
@ -152,11 +205,11 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-row"><button class="btn btn-ghost" onclick="goTab(0)">← Indietro</button><button class="btn btn-brand" onclick="goTab(2)">Avanti →</button></div>
|
<div class="nav-row"><button class="btn btn-ghost" onclick="goTab(4)">← Indietro</button><button class="btn btn-brand" onclick="goTab(4)">Avanti →</button></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 3: SIEM -->
|
<!-- Tab 4: SIEM -->
|
||||||
<div class="tab-panel" id="panel-2">
|
<div class="tab-panel" id="panel-3">
|
||||||
<div class="panel-title">🔍 SIEM — OpenSearch</div>
|
<div class="panel-title">🔍 SIEM — OpenSearch</div>
|
||||||
<div class="panel-sub">Connessione al backend SIEM. Le altre integrazioni si configurano dall'interfaccia ARGOS dopo l'installazione.</div>
|
<div class="panel-sub">Connessione al backend SIEM. Le altre integrazioni si configurano dall'interfaccia ARGOS dopo l'installazione.</div>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
|
|
@ -169,7 +222,7 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 4: Admin -->
|
<!-- Tab 4: Admin -->
|
||||||
<div class="tab-panel" id="panel-3">
|
<div class="tab-panel" id="panel-4">
|
||||||
<div class="panel-title">🔑 Utente amministratore</div>
|
<div class="panel-title">🔑 Utente amministratore</div>
|
||||||
<div class="panel-sub">Crea il primo account admin per accedere a ARGOS SOC.</div>
|
<div class="panel-sub">Crea il primo account admin per accedere a ARGOS SOC.</div>
|
||||||
<div class="form-grid">
|
<div class="form-grid">
|
||||||
|
|
@ -183,8 +236,8 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
||||||
<div class="nav-row"><button class="btn btn-ghost" onclick="goTab(2)">← Indietro</button><button class="btn btn-brand" onclick="goToInstall()">Avanti →</button></div>
|
<div class="nav-row"><button class="btn btn-ghost" onclick="goTab(2)">← Indietro</button><button class="btn btn-brand" onclick="goToInstall()">Avanti →</button></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 5: Installa -->
|
<!-- Tab 6: Installa -->
|
||||||
<div class="tab-panel" id="panel-4">
|
<div class="tab-panel" id="panel-5">
|
||||||
<div class="panel-title">🚀 Riepilogo & Installazione</div>
|
<div class="panel-title">🚀 Riepilogo & Installazione</div>
|
||||||
<div class="panel-sub">Verifica i dati e avvia l'installazione.</div>
|
<div class="panel-sub">Verifica i dati e avvia l'installazione.</div>
|
||||||
|
|
||||||
|
|
@ -235,6 +288,91 @@ let keyUploaded = false;
|
||||||
let installing = false;
|
let installing = false;
|
||||||
let installDomain = '';
|
let installDomain = '';
|
||||||
|
|
||||||
|
// ── Licenza ARGOS: fetch machine_id e gestione upload ────────────────────────
|
||||||
|
let licenseValid = false;
|
||||||
|
|
||||||
|
async function loadMachineId() {
|
||||||
|
try {
|
||||||
|
const r = await fetch('/api/machine-id');
|
||||||
|
const d = await r.json();
|
||||||
|
const el = document.getElementById('machine-id-display');
|
||||||
|
if (d && d.machine_id) {
|
||||||
|
el.value = d.machine_id;
|
||||||
|
} else {
|
||||||
|
el.value = 'Errore caricamento';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
document.getElementById('machine-id-display').value = 'Errore: ' + e.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyMachineId() {
|
||||||
|
const el = document.getElementById('machine-id-display');
|
||||||
|
el.select();
|
||||||
|
navigator.clipboard.writeText(el.value).then(() => {
|
||||||
|
const btn = document.getElementById('btn-copy-mid');
|
||||||
|
const orig = btn.textContent;
|
||||||
|
btn.textContent = '✅ Copiato';
|
||||||
|
setTimeout(() => btn.textContent = orig, 1500);
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLicenseDrop(ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.currentTarget.classList.remove('drag');
|
||||||
|
if (ev.dataTransfer.files.length) uploadLicense(ev.dataTransfer.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLicenseFile(inp) {
|
||||||
|
if (inp.files.length) uploadLicense(inp.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadLicense(file) {
|
||||||
|
const errEl = document.getElementById('lic-error');
|
||||||
|
const sumEl = document.getElementById('lic-summary');
|
||||||
|
const btnNext = document.getElementById('btn-after-license');
|
||||||
|
errEl.style.display = 'none';
|
||||||
|
sumEl.style.display = 'none';
|
||||||
|
try {
|
||||||
|
const text = await file.text();
|
||||||
|
const r = await fetch('/api/license/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: text,
|
||||||
|
});
|
||||||
|
const d = await r.json();
|
||||||
|
if (!d.ok) {
|
||||||
|
errEl.textContent = 'Licenza non valida: ' + (d.error || 'errore sconosciuto');
|
||||||
|
errEl.style.display = 'block';
|
||||||
|
licenseValid = false;
|
||||||
|
btnNext.disabled = true;
|
||||||
|
btnNext.style.opacity = '.4';
|
||||||
|
btnNext.style.cursor = 'not-allowed';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const s = d.summary || {};
|
||||||
|
document.getElementById('lic-customer').textContent = s.customer || '—';
|
||||||
|
document.getElementById('lic-tier').textContent = (s.tier || '—').toUpperCase();
|
||||||
|
document.getElementById('lic-hostname').textContent = s.issued_to || '—';
|
||||||
|
document.getElementById('lic-issued').textContent = s.issued_at || '—';
|
||||||
|
document.getElementById('lic-expires').textContent = s.expires_at || '—';
|
||||||
|
document.getElementById('lic-gitea').textContent = s.has_gitea ? '✓ Incluso' : '— Non incluso';
|
||||||
|
sumEl.style.display = 'block';
|
||||||
|
licenseValid = true;
|
||||||
|
btnNext.disabled = false;
|
||||||
|
btnNext.style.opacity = '1';
|
||||||
|
btnNext.style.cursor = 'pointer';
|
||||||
|
} catch (e) {
|
||||||
|
errEl.textContent = 'Errore upload: ' + e.message;
|
||||||
|
errEl.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Al caricamento pagina: fetch machine_id
|
||||||
|
window.addEventListener('DOMContentLoaded', loadMachineId);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function goTab(n) {
|
function goTab(n) {
|
||||||
if (n > currentTab) document.getElementById('tab-' + currentTab).classList.add('done');
|
if (n > currentTab) document.getElementById('tab-' + currentTab).classList.add('done');
|
||||||
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
||||||
|
|
@ -254,7 +392,7 @@ function goToInstall() {
|
||||||
const err = document.getElementById('pw-error');
|
const err = document.getElementById('pw-error');
|
||||||
if (pw1 !== pw2) { err.style.display = 'block'; return; }
|
if (pw1 !== pw2) { err.style.display = 'block'; return; }
|
||||||
err.style.display = 'none';
|
err.style.display = 'none';
|
||||||
goTab(4);
|
goTab(5);
|
||||||
buildSummary();
|
buildSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
208
setup_server.py
208
setup_server.py
|
|
@ -26,11 +26,121 @@ SETUP_DIR = Path("/opt/argos/setup")
|
||||||
APP_USER = "argos"
|
APP_USER = "argos"
|
||||||
PORT = 8888
|
PORT = 8888
|
||||||
|
|
||||||
|
# ── Licenza ARGOS — pubkey per verifica firma Ed25519 ─────────────────────────
|
||||||
|
# Stessa pubkey hardcoded in backend/core.py. Raw Ed25519 (32 byte) base64.
|
||||||
|
# Il setup_server la usa per verificare licenze ricevute dal cliente PRIMA
|
||||||
|
# di procedere con l'installazione.
|
||||||
|
_LICENSE_PUBLIC_KEY_B64 = "GMRsZMoxOlCBiJU66EsQcj0ZO0gVd0GHB5LelEo/hns="
|
||||||
|
|
||||||
|
# ── Clone argos: username Basic Auth bot Gitea (costante del sistema) ─────────
|
||||||
|
GITEA_BOT_USER = "argos-portal-bot"
|
||||||
|
GITEA_REPO_PATH = "/tecnotel/argos.git"
|
||||||
|
|
||||||
install_log = []
|
install_log = []
|
||||||
install_done = False
|
install_done = False
|
||||||
install_error = False
|
install_error = False
|
||||||
|
|
||||||
|
|
||||||
|
def get_machine_id() -> str:
|
||||||
|
"""Fingerprint univoco del server (stesso algoritmo di core.py).
|
||||||
|
SHA256 hex di: /etc/machine-id | hostname | MAC prima interfaccia fisica.
|
||||||
|
"""
|
||||||
|
import socket as _sock
|
||||||
|
import subprocess as _sp
|
||||||
|
parts = []
|
||||||
|
try:
|
||||||
|
with open("/etc/machine-id") as f:
|
||||||
|
parts.append(f.read().strip())
|
||||||
|
except Exception:
|
||||||
|
parts.append("")
|
||||||
|
try:
|
||||||
|
parts.append(_sock.gethostname())
|
||||||
|
except Exception:
|
||||||
|
parts.append("")
|
||||||
|
try:
|
||||||
|
r = _sp.run(["cat", "/sys/class/net/eth0/address"],
|
||||||
|
capture_output=True, text=True, timeout=2)
|
||||||
|
mac = r.stdout.strip()
|
||||||
|
if not mac or mac == "00:00:00:00:00:00":
|
||||||
|
r = _sp.run(["ip", "-o", "link", "show"],
|
||||||
|
capture_output=True, text=True, timeout=2)
|
||||||
|
for line in r.stdout.splitlines():
|
||||||
|
if "link/ether" in line and "00:00:00:00:00:00" not in line:
|
||||||
|
if "docker" in line or "br-" in line or "veth" in line:
|
||||||
|
continue
|
||||||
|
mac = line.split("link/ether")[1].split()[0].strip()
|
||||||
|
break
|
||||||
|
parts.append(mac or "")
|
||||||
|
except Exception:
|
||||||
|
parts.append("")
|
||||||
|
combined = "|".join(parts)
|
||||||
|
return hashlib.sha256(combined.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def verify_license(raw_bytes):
|
||||||
|
"""Verifica firma Ed25519 + machine_id match + scadenza.
|
||||||
|
|
||||||
|
Ritorna tupla (ok, license_dict, error_message).
|
||||||
|
Se ok=True, license_dict contiene il payload completo (inclusi gitea_url,
|
||||||
|
gitea_token se presenti). Se ok=False, error_message e' umano-friendly.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
raw = json.loads(raw_bytes)
|
||||||
|
except Exception as e:
|
||||||
|
return (False, None, f"File non e' JSON valido: {e}")
|
||||||
|
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
return (False, None, "Formato licenza non riconosciuto")
|
||||||
|
|
||||||
|
# Verifica firma
|
||||||
|
try:
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
||||||
|
from cryptography.exceptions import InvalidSignature
|
||||||
|
import base64
|
||||||
|
sig = raw.pop("signature", "")
|
||||||
|
if not sig:
|
||||||
|
return (False, None, "Licenza senza firma (campo 'signature' mancante)")
|
||||||
|
payload = json.dumps(raw, sort_keys=True, separators=(",", ":"))
|
||||||
|
raw["signature"] = sig # ripristina
|
||||||
|
pub_bytes = base64.b64decode(_LICENSE_PUBLIC_KEY_B64)
|
||||||
|
pub = Ed25519PublicKey.from_public_bytes(pub_bytes)
|
||||||
|
try:
|
||||||
|
pub.verify(base64.b64decode(sig), payload.encode())
|
||||||
|
except InvalidSignature:
|
||||||
|
return (False, None, "Firma non valida. La licenza potrebbe essere manipolata o emessa da altro vendor.")
|
||||||
|
except ImportError:
|
||||||
|
return (False, None, "Libreria 'cryptography' non disponibile. Installa: apt install python3-cryptography")
|
||||||
|
except Exception as e:
|
||||||
|
return (False, None, f"Errore verifica firma: {e}")
|
||||||
|
|
||||||
|
# Verifica machine_id
|
||||||
|
lic_machine = raw.get("machine_id", "")
|
||||||
|
if not lic_machine:
|
||||||
|
return (False, None, "Licenza senza machine_id — formato non supportato")
|
||||||
|
cur_machine = get_machine_id()
|
||||||
|
if lic_machine != cur_machine:
|
||||||
|
return (False, None,
|
||||||
|
f"Machine ID non corrisponde: licenza per {lic_machine[:12]}..., "
|
||||||
|
f"questo server e' {cur_machine[:12]}... "
|
||||||
|
"La licenza non e' valida per questa macchina.")
|
||||||
|
|
||||||
|
# Verifica scadenza
|
||||||
|
expires = raw.get("expires_at", "")
|
||||||
|
if expires:
|
||||||
|
today = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
if expires < today:
|
||||||
|
return (False, None, f"Licenza scaduta il {expires}")
|
||||||
|
|
||||||
|
# Verifica presenza credenziali Gitea (la licenza deve averle per permettere clone)
|
||||||
|
if not raw.get("gitea_url") or not raw.get("gitea_token"):
|
||||||
|
return (False, None,
|
||||||
|
"Licenza priva di credenziali Gitea. "
|
||||||
|
"Contatta Tecnotel per riemetterla con il nuovo formato.")
|
||||||
|
|
||||||
|
return (True, raw, "")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def log(msg):
|
def log(msg):
|
||||||
ts = datetime.now().strftime("%H:%M:%S")
|
ts = datetime.now().strftime("%H:%M:%S")
|
||||||
line = f"[{ts}] {msg}"
|
line = f"[{ts}] {msg}"
|
||||||
|
|
@ -166,7 +276,51 @@ def install(data):
|
||||||
try:
|
try:
|
||||||
log("=== AVVIO INSTALLAZIONE ARGOS SOC ===")
|
log("=== AVVIO INSTALLAZIONE ARGOS SOC ===")
|
||||||
|
|
||||||
# 1. argos.json
|
# 0. Carica licenza (gia' validata da /api/license/upload)
|
||||||
|
log("── Verifica licenza ARGOS ──")
|
||||||
|
lic_path = SETUP_DIR / "license.json"
|
||||||
|
if not lic_path.exists():
|
||||||
|
raise RuntimeError(
|
||||||
|
"License.json non trovata in /opt/argos/setup/. "
|
||||||
|
"Caricare una licenza valida prima di avviare l'installazione."
|
||||||
|
)
|
||||||
|
ok, lic, err = verify_license(lic_path.read_bytes())
|
||||||
|
if not ok:
|
||||||
|
raise RuntimeError(f"Licenza non valida: {err}")
|
||||||
|
gitea_url = lic.get("gitea_url", "").rstrip("/")
|
||||||
|
gitea_token = lic.get("gitea_token", "")
|
||||||
|
# Deriva host dal gitea_url (es: https://host/api/v1 -> https://host)
|
||||||
|
gitea_host = gitea_url[:-len("/api/v1")] if gitea_url.endswith("/api/v1") else gitea_url
|
||||||
|
log(f"Licenza OK: {lic.get('customer')} / {lic.get('tier')} / exp {lic.get('expires_at')}")
|
||||||
|
|
||||||
|
# 1. Clone repository argos (URL autenticato temporaneo: token NON in .git/config)
|
||||||
|
log("── Clone repository ARGOS ──")
|
||||||
|
if APP_DIR.exists() and (APP_DIR / ".git").exists():
|
||||||
|
log("Repository gia' presente — skip clone")
|
||||||
|
else:
|
||||||
|
auth_url = f"https://{GITEA_BOT_USER}:{gitea_token}@{gitea_host[len('https://'):]}{GITEA_REPO_PATH}"
|
||||||
|
APP_DIR.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
# Clona con URL autenticato
|
||||||
|
run(f"git clone {auth_url} {APP_DIR}")
|
||||||
|
# Dopo clone, ripulisci .git/config rimuovendo il token
|
||||||
|
clean_url = f"{gitea_host}{GITEA_REPO_PATH}"
|
||||||
|
run(f"git -C {APP_DIR} remote set-url origin {clean_url}")
|
||||||
|
run(f"chown -R {APP_USER}:{APP_USER} {APP_DIR}")
|
||||||
|
log("Repository ARGOS clonato")
|
||||||
|
|
||||||
|
# 2. Python virtualenv
|
||||||
|
log("── Creazione virtualenv Python ──")
|
||||||
|
venv_dir = APP_DIR / "backend/venv"
|
||||||
|
if not venv_dir.exists():
|
||||||
|
run(f"python3 -m venv {venv_dir}")
|
||||||
|
run(f"{venv_dir}/bin/pip install --upgrade pip -q")
|
||||||
|
req_file = APP_DIR / "backend/requirements.txt"
|
||||||
|
if req_file.exists():
|
||||||
|
run(f"{venv_dir}/bin/pip install -r {req_file} -q")
|
||||||
|
run(f"chown -R {APP_USER}:{APP_USER} {venv_dir}")
|
||||||
|
log("Virtualenv Python pronto")
|
||||||
|
|
||||||
|
# 3. argos.json
|
||||||
log("── Generazione argos.json ──")
|
log("── Generazione argos.json ──")
|
||||||
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
config = generate_argos_json(data)
|
config = generate_argos_json(data)
|
||||||
|
|
@ -177,7 +331,7 @@ def install(data):
|
||||||
chown(CONFIG_DIR)
|
chown(CONFIG_DIR)
|
||||||
log("argos.json creato")
|
log("argos.json creato")
|
||||||
|
|
||||||
# 2. integrations.json
|
# 4. integrations.json
|
||||||
log("── Generazione integrations.json ──")
|
log("── Generazione integrations.json ──")
|
||||||
integ = generate_integrations_json()
|
integ = generate_integrations_json()
|
||||||
# Aggiorna pdf con nome organizzazione
|
# Aggiorna pdf con nome organizzazione
|
||||||
|
|
@ -191,14 +345,14 @@ def install(data):
|
||||||
chown(integ_path)
|
chown(integ_path)
|
||||||
log("integrations.json creato")
|
log("integrations.json creato")
|
||||||
|
|
||||||
# 3. modules.json
|
# 5. modules.json
|
||||||
mods = APP_DIR / "config/modules.json.example"
|
mods = APP_DIR / "config/modules.json.example"
|
||||||
if mods.exists():
|
if mods.exists():
|
||||||
shutil.copy(mods, CONFIG_DIR / "modules.json")
|
shutil.copy(mods, CONFIG_DIR / "modules.json")
|
||||||
chown(CONFIG_DIR / "modules.json")
|
chown(CONFIG_DIR / "modules.json")
|
||||||
log("modules.json copiato")
|
log("modules.json copiato")
|
||||||
|
|
||||||
# 4. Logo cliente
|
# 6. Logo cliente
|
||||||
logo_src = SETUP_DIR / "logo_cliente.png"
|
logo_src = SETUP_DIR / "logo_cliente.png"
|
||||||
if logo_src.exists():
|
if logo_src.exists():
|
||||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
@ -206,7 +360,7 @@ def install(data):
|
||||||
chown(CONFIG_DIR / "assets" / "logo_cliente.png")
|
chown(CONFIG_DIR / "assets" / "logo_cliente.png")
|
||||||
log("Logo cliente copiato")
|
log("Logo cliente copiato")
|
||||||
|
|
||||||
# 5. Build frontend
|
# 7. Build frontend
|
||||||
log("── Build frontend React ──")
|
log("── Build frontend React ──")
|
||||||
run(f"cd {APP_DIR}/frontend && npm install --silent")
|
run(f"cd {APP_DIR}/frontend && npm install --silent")
|
||||||
run(f"cd {APP_DIR}/frontend && npm run build")
|
run(f"cd {APP_DIR}/frontend && npm run build")
|
||||||
|
|
@ -216,7 +370,7 @@ def install(data):
|
||||||
run(f"chmod -R 755 {APP_DIR}/frontend/dist/")
|
run(f"chmod -R 755 {APP_DIR}/frontend/dist/")
|
||||||
log("Frontend buildato")
|
log("Frontend buildato")
|
||||||
|
|
||||||
# 6. Dipendenze Python
|
# 8. Dipendenze Python (re-run in caso di aggiornamenti)
|
||||||
log("── Dipendenze Python ──")
|
log("── Dipendenze Python ──")
|
||||||
venv_pip = APP_DIR / "backend/venv/bin/pip"
|
venv_pip = APP_DIR / "backend/venv/bin/pip"
|
||||||
req = APP_DIR / "backend/requirements.txt"
|
req = APP_DIR / "backend/requirements.txt"
|
||||||
|
|
@ -224,11 +378,11 @@ def install(data):
|
||||||
run(f"{venv_pip} install -r {req} -q")
|
run(f"{venv_pip} install -r {req} -q")
|
||||||
log("Dipendenze Python installate")
|
log("Dipendenze Python installate")
|
||||||
|
|
||||||
# 7. Utente admin
|
# 9. Utente admin
|
||||||
log("── Creazione utente admin ──")
|
log("── Creazione utente admin ──")
|
||||||
create_admin_user(data)
|
create_admin_user(data)
|
||||||
|
|
||||||
# 8. SSL
|
# 10. SSL
|
||||||
log("── Configurazione SSL ──")
|
log("── Configurazione SSL ──")
|
||||||
domain = data.get("domain", "").strip()
|
domain = data.get("domain", "").strip()
|
||||||
aliases = data.get("aliases", "").strip()
|
aliases = data.get("aliases", "").strip()
|
||||||
|
|
@ -257,13 +411,13 @@ def install(data):
|
||||||
ssl_key = f"/etc/letsencrypt/live/{domain}/privkey.pem"
|
ssl_key = f"/etc/letsencrypt/live/{domain}/privkey.pem"
|
||||||
log("Certificato Let's Encrypt ottenuto")
|
log("Certificato Let's Encrypt ottenuto")
|
||||||
|
|
||||||
# 9. Nginx finale
|
# 11. Nginx finale
|
||||||
log("── Nginx configurazione finale ──")
|
log("── Nginx configurazione finale ──")
|
||||||
_write_nginx_final(all_names, ssl_crt, ssl_key)
|
_write_nginx_final(all_names, ssl_crt, ssl_key)
|
||||||
run("nginx -t && systemctl restart nginx")
|
run("nginx -t && systemctl restart nginx")
|
||||||
log("Nginx configurato")
|
log("Nginx configurato")
|
||||||
|
|
||||||
# 10. Systemd services
|
# 12. Systemd services
|
||||||
log("── Creazione e avvio servizi ──")
|
log("── Creazione e avvio servizi ──")
|
||||||
_write_services()
|
_write_services()
|
||||||
run("systemctl daemon-reload")
|
run("systemctl daemon-reload")
|
||||||
|
|
@ -271,7 +425,15 @@ def install(data):
|
||||||
run(f"systemctl enable --now {svc}")
|
run(f"systemctl enable --now {svc}")
|
||||||
log(f"{svc} avviato")
|
log(f"{svc} avviato")
|
||||||
|
|
||||||
# 11. Chiudi web installer
|
# 13. Copia licenza + chiudi web installer
|
||||||
|
log("── Copia licenza in posizione finale ──")
|
||||||
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
final_lic = DATA_DIR / "license.json"
|
||||||
|
shutil.copy(lic_path, final_lic)
|
||||||
|
os.chmod(final_lic, 0o600)
|
||||||
|
chown(final_lic)
|
||||||
|
log(f"Licenza copiata in {final_lic}")
|
||||||
|
|
||||||
log("── Chiusura web installer ──")
|
log("── Chiusura web installer ──")
|
||||||
run("systemctl disable --now argos-setup", check=False)
|
run("systemctl disable --now argos-setup", check=False)
|
||||||
run("ufw delete allow 8888/tcp", check=False)
|
run("ufw delete allow 8888/tcp", check=False)
|
||||||
|
|
@ -435,6 +597,8 @@ class SetupHandler(BaseHTTPRequestHandler):
|
||||||
self.wfile.write(html)
|
self.wfile.write(html)
|
||||||
elif path == "/api/status":
|
elif path == "/api/status":
|
||||||
self._json({"done": install_done, "error": install_error, "log": install_log[-60:]})
|
self._json({"done": install_done, "error": install_error, "log": install_log[-60:]})
|
||||||
|
elif path == "/api/machine-id":
|
||||||
|
self._json({"machine_id": get_machine_id()})
|
||||||
else:
|
else:
|
||||||
self.send_response(404); self.end_headers()
|
self.send_response(404); self.end_headers()
|
||||||
|
|
||||||
|
|
@ -462,6 +626,28 @@ class SetupHandler(BaseHTTPRequestHandler):
|
||||||
SETUP_DIR.mkdir(parents=True, exist_ok=True)
|
SETUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
(SETUP_DIR / "logo_cliente.png").write_bytes(body)
|
(SETUP_DIR / "logo_cliente.png").write_bytes(body)
|
||||||
self._json({"ok": True})
|
self._json({"ok": True})
|
||||||
|
elif path == "/api/license/upload":
|
||||||
|
ok, lic, err = verify_license(body)
|
||||||
|
if not ok:
|
||||||
|
self._json({"ok": False, "error": err}, 400)
|
||||||
|
return
|
||||||
|
# Salva licenza valida nel SETUP_DIR per essere usata durante install()
|
||||||
|
SETUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
lic_path = SETUP_DIR / "license.json"
|
||||||
|
lic_path.write_bytes(body)
|
||||||
|
os.chmod(lic_path, 0o600)
|
||||||
|
# Risposta: solo dati sommari (non rinvia token in plain)
|
||||||
|
self._json({
|
||||||
|
"ok": True,
|
||||||
|
"summary": {
|
||||||
|
"customer": lic.get("customer", ""),
|
||||||
|
"tier": lic.get("tier", ""),
|
||||||
|
"issued_to": lic.get("issued_to", ""),
|
||||||
|
"issued_at": lic.get("issued_at", ""),
|
||||||
|
"expires_at": lic.get("expires_at", ""),
|
||||||
|
"has_gitea": bool(lic.get("gitea_token")),
|
||||||
|
}
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
self.send_response(404); self.end_headers()
|
self.send_response(404); self.end_headers()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue