feat(setup): Ubuntu 26.04 LTS support + cert autofirmato + 3 file .example
Modifiche di compatibilita' e miglioramenti UX dopo analisi del 12/05/26
del codice argos main repo vs argos-setup.
1. Multi-version Ubuntu (24.04 e 26.04 LTS):
- first-setup.sh: SUPPORTED_VERSIONS array per check
- Aggiunto openssl esplicito alle dipendenze apt
- README aggiornato con compatibility matrix
- ARGOS code (Python 3.14-ready: no distutils/imp/pkg_resources)
gia' compatibile, requirements.txt pin gia' build 2026
2. Certificato SSL autofirmato come 3a opzione:
- UI: bottone '🔐 Autofirmato' nel Tab Rete & SSL
- Backend: openssl req -x509 -newkey rsa:4096 -days 3650
- SAN dinamica: tutti i DNS (hostname + aliases) + IP server
- Subject: C=IT, O=<cliente_full>, OU=ARGOS SOC, CN=<hostname>
- Warning UI prominente sulla non-attendibilita' browser
- Utile per installazioni LAN/dev senza DNS pubblico
3. Rimozione campi M365:
- sp_tenant (SharePoint tenant) rimosso da Tab Cliente
- sharepoint_tenant non piu' nel argos.json generato
- M365/Entra ID configurabile dall'UI Integrazioni post-install
4. Textarea ai_context opzionale nel Tab Cliente:
- Iniettato nei prompt AI per contestualizzare il cliente
- Hint con esempio ASREM
- Salvato in argos.json -> cliente.ai_context (letto da config.py)
5. Copia di 3 file .example che il setup non gestiva:
- automations.json (config feed TI sources + cron daemon)
- siem_integrations.json (catalogo SIEM Integration Builder)
- subnet_registry.json (mapping sede/reparto da subnet)
Necessari dopo i lavori marzo-maggio 2026.
6. Rimozione gen_config.py legacy:
- Schema obsoleto (manca ai_context, console_url, network,
vendor_heartbeat)
- Non piu' richiamato da nessuno (verificato con grep)
- Sostituito completamente da setup_server.py inline
7. README aggiornato:
- Sezione 'Opzioni certificato SSL' con 3 modalita'
- Requisiti: Ubuntu 24.04 LTS o 26.04 LTS
- Tab 2 menziona contesto AI opzionale
This commit is contained in:
parent
6424c784f9
commit
a68324725f
20
README.md
20
README.md
|
|
@ -6,7 +6,7 @@ Repository pubblico contenente l'installer di prima fase e il Web Setup Wizard d
|
|||
|
||||
## Installazione rapida (one-liner)
|
||||
|
||||
Su una macchina Ubuntu 24.04 LTS vergine, esegui:
|
||||
Su una macchina Ubuntu 24.04 o 26.04 LTS vergine, esegui:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://argos-update.tecnotelsrl.com/tecnotel/argos-setup/raw/branch/main/bootstrap.sh | sudo bash
|
||||
|
|
@ -33,25 +33,32 @@ sudo bash first-setup.sh
|
|||
| `first-setup.sh` | Installer ambiente base: sistema, utenti, firewall, nginx temp, avvia wizard web |
|
||||
| `setup_server.py` | Backend Python del Web Installer (porta 8888, self-contained) |
|
||||
| `setup.html` | Frontend del Web Installer — wizard in 6 step |
|
||||
| `gen_config.py` | Helper per generazione iniziale di `argos.json` |
|
||||
|
||||
## Flusso d'installazione
|
||||
|
||||
1. Il cliente esegue il bootstrap (o il clone manuale + first-setup.sh).
|
||||
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`.
|
||||
2. `first-setup.sh` predispone l'ambiente base Ubuntu (pacchetti, utente argos, cartelle, firewall) e avvia il Web Installer su `http://IP:8888`.
|
||||
3. Il cliente apre il browser e segue il wizard in 6 step:
|
||||
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)
|
||||
2. **Informazioni cliente** (nome, dominio, logo, contesto AI opzionale)
|
||||
3. **Rete & SSL** (hostname, certificato: Let's Encrypt / caricamento / autofirmato)
|
||||
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.
|
||||
|
||||
## Opzioni certificato SSL
|
||||
|
||||
Il wizard offre tre modalità per il certificato:
|
||||
|
||||
- **🔒 Let's Encrypt** — automatico e gratuito. Richiede DNS pubblico già puntato al server.
|
||||
- **📄 Certificato esistente** — caricamento di file `.crt` e `.key` esistenti (es. cert wildcard aziendale, cert di CA interna).
|
||||
- **🔐 Autofirmato** — generato localmente con RSA 4096 e validità 10 anni. Utile per installazioni in LAN / dev / ambienti isolati senza DNS pubblico. ⚠️ I browser segnaleranno il certificato come non attendibile.
|
||||
|
||||
## Requisiti
|
||||
|
||||
- Ubuntu 24.04 LTS (verificato dallo script)
|
||||
- Ubuntu **24.04 LTS** o **26.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
|
||||
|
|
@ -63,3 +70,4 @@ Al termine il Web Installer viene disattivato automaticamente e la porta 8888 ch
|
|||
## Versioning
|
||||
|
||||
Questo repository segue lo stesso schema di versioning di `argos` (SemVer tag `vX.Y.Z`).
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,14 @@ section() { echo -e "\n${BLUE}════════════════
|
|||
|
||||
[[ $EUID -ne 0 ]] && error "Eseguire come root: sudo bash first-setup.sh"
|
||||
. /etc/os-release
|
||||
[[ "$ID" != "ubuntu" || "$VERSION_ID" != "24.04" ]] && error "Richiesto Ubuntu 24.04 LTS"
|
||||
SUPPORTED_VERSIONS=("24.04" "26.04")
|
||||
VERSION_OK=0
|
||||
for v in "${SUPPORTED_VERSIONS[@]}"; do
|
||||
[[ "$VERSION_ID" == "$v" ]] && VERSION_OK=1
|
||||
done
|
||||
[[ "$ID" != "ubuntu" || $VERSION_OK -eq 0 ]] && \
|
||||
error "Richiesto Ubuntu 24.04 o 26.04 LTS (rilevato: $ID $VERSION_ID)"
|
||||
info "Sistema rilevato: Ubuntu $VERSION_ID LTS"
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}╔══════════════════════════════════════════╗${NC}"
|
||||
|
|
@ -39,7 +46,7 @@ apt-get install -y -qq \
|
|||
ufw fail2ban \
|
||||
build-essential libssl-dev libffi-dev python3-dev \
|
||||
sqlite3 net-tools dnsutils lsof tcpdump nmap \
|
||||
ca-certificates gnupg apt-transport-https
|
||||
ca-certificates gnupg apt-transport-https openssl
|
||||
success "Pacchetti sistema installati"
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
27
setup.html
27
setup.html
|
|
@ -172,7 +172,7 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
|||
<div><label class="form-label">Tipo organizzazione</label><select class="form-select" id="cliente_type"><option value="enterprise">Azienda / Enterprise</option><option value="healthcare">Sanità / Healthcare</option><option value="pa">Pubblica Amministrazione</option><option value="education">Istruzione</option></select></div>
|
||||
<div class="form-full"><label class="form-label">Nome completo</label><input class="form-input" id="cliente_full" placeholder="es. Nome Azienda Srl"></div>
|
||||
<div><label class="form-label">Dominio email <span>*</span></label><input class="form-input" id="cliente_domain" placeholder="es. azienda.it"><div class="form-hint">Dominio utenti (es. nome@azienda.it)</div></div>
|
||||
<div><label class="form-label">SharePoint tenant</label><input class="form-input" id="sp_tenant" placeholder="es. nomeazienda"><div class="form-hint">Solo se Microsoft 365. Vuoto se non usato.</div></div>
|
||||
<div class="form-full"><label class="form-label">Contesto AI <span style="color:var(--text4);font-weight:400">(opzionale)</span></label><textarea class="form-input" id="ai_context" rows="3" placeholder="es. ASREM = Azienda Sanitaria Regionale Molise, 17 sedi, 2000+ endpoint, infrastruttura mista Cisco/Fortinet, normative NIS2/GDPR/AgID."></textarea><div class="form-hint">3-4 frasi sul cliente per contestualizzare i prompt AI. Iniettato in ogni richiesta AI per sintesi più accurate.</div></div>
|
||||
<div class="sec-div">Logo cliente (per i PDF report)</div>
|
||||
<div class="upload-area form-full" id="logo-drop" ondragover="event.preventDefault();this.classList.add('drag')" ondragleave="this.classList.remove('drag')" ondrop="handleLogoDrop(event)" onclick="document.getElementById('logo-input').click()">
|
||||
<input type="file" id="logo-input" accept="image/*" style="display:none" onchange="handleLogoFile(this)">
|
||||
|
|
@ -196,6 +196,7 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
|||
<div class="ssl-toggle">
|
||||
<div class="ssl-btn active" id="ssl-le" onclick="setSsl('letsencrypt')">🔒 Let's Encrypt<small>Automatico e gratuito</small></div>
|
||||
<div class="ssl-btn" id="ssl-manual" onclick="setSsl('manual')">📄 Certificato esistente<small>Carica .crt e .key</small></div>
|
||||
<div class="ssl-btn" id="ssl-selfsigned" onclick="setSsl('selfsigned')">🔐 Autofirmato<small>LAN / dev — 10 anni</small></div>
|
||||
</div>
|
||||
<div id="le-block" class="form-full"><label class="form-label">Email amministratore <span>*</span></label><input class="form-input" id="admin_email" placeholder="admin@tecnotelsrl.com"><div class="form-hint">Per notifiche di scadenza certificato</div></div>
|
||||
<div id="manual-block" class="form-full" style="display:none">
|
||||
|
|
@ -204,6 +205,17 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
|||
<div><label class="form-label">File .key <span>*</span></label><input type="file" class="form-input" accept=".key,.pem" onchange="uploadSsl(this,'key')"><div class="form-hint" id="key-status">Nessun file selezionato</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="selfsigned-block" class="form-full" style="display:none">
|
||||
<div style="padding:12px;background:rgba(217,119,6,0.08);border:1px solid rgba(217,119,6,0.3);border-radius:6px;font-size:13px;color:var(--text2);line-height:1.5">
|
||||
<strong style="color:var(--warn)">⚠️ Attenzione — certificato autofirmato</strong><br>
|
||||
Il certificato verrà generato localmente con RSA 4096 e validità 10 anni.
|
||||
I browser segnaleranno il sito come <em>non attendibile</em> al primo accesso —
|
||||
sarà necessario aggiungere un'eccezione manualmente.<br>
|
||||
<strong>Usare solo per installazioni LAN, test o ambienti isolati.</strong>
|
||||
Per produzione su Internet pubblico preferire <em>Let's Encrypt</em> o un
|
||||
certificato firmato da CA aziendale.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-row"><button class="btn btn-ghost" onclick="goTab(1)">← Indietro</button><button class="btn btn-brand" onclick="goTab(3)">Avanti →</button></div>
|
||||
</div>
|
||||
|
|
@ -400,8 +412,10 @@ function setSsl(mode) {
|
|||
sslMode = mode;
|
||||
document.getElementById('ssl-le').classList.toggle('active', mode === 'letsencrypt');
|
||||
document.getElementById('ssl-manual').classList.toggle('active', mode === 'manual');
|
||||
document.getElementById('ssl-selfsigned').classList.toggle('active', mode === 'selfsigned');
|
||||
document.getElementById('le-block').style.display = mode === 'letsencrypt' ? 'block' : 'none';
|
||||
document.getElementById('manual-block').style.display = mode === 'manual' ? 'block' : 'none';
|
||||
document.getElementById('selfsigned-block').style.display = mode === 'selfsigned' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
async function uploadSsl(input, type) {
|
||||
|
|
@ -438,7 +452,8 @@ function collectData() {
|
|||
return {
|
||||
cliente_name: g('cliente_name'), cliente_full: g('cliente_full'),
|
||||
cliente_domain: g('cliente_domain'), cliente_type: g('cliente_type'),
|
||||
sp_tenant: g('sp_tenant'), domain: g('domain'), aliases: g('aliases'),
|
||||
ai_context: g('ai_context'),
|
||||
domain: g('domain'), aliases: g('aliases'),
|
||||
ssl_mode: sslMode, admin_email: g('admin_email'),
|
||||
os_url: g('os_url'), os_user: g('os_user') || 'admin', os_pass: g('os_pass'),
|
||||
admin_username: g('admin_username') || 'admin',
|
||||
|
|
@ -460,13 +475,19 @@ function buildSummary() {
|
|||
|
||||
const row = (l, v) => '<div class="summary-row"><div class="summary-label">' + l + '</div><div class="summary-value ' + (v ? '' : 'empty') + '">' + (v || 'non configurato') + '</div></div>';
|
||||
|
||||
const sslLabel = sslMode === 'letsencrypt' ? "Let's Encrypt (automatico)"
|
||||
: sslMode === 'manual' ? "Certificato esistente caricato"
|
||||
: sslMode === 'selfsigned' ? "Autofirmato (RSA 4096, 10 anni)"
|
||||
: "Non configurato";
|
||||
|
||||
document.getElementById('summary-box').innerHTML = '<div class="summary-table">' +
|
||||
'<div class="summary-section">Cliente</div>' +
|
||||
row('Nome', d.cliente_name) + row('Nome completo', d.cliente_full) +
|
||||
row('Dominio email', d.cliente_domain) + row('Tipo', d.cliente_type) +
|
||||
(d.ai_context ? row('Contesto AI', d.ai_context.substring(0, 80) + (d.ai_context.length > 80 ? '…' : '')) : '') +
|
||||
'<div class="summary-section">Rete & SSL</div>' +
|
||||
row('Hostname', d.domain) + row('Alias', d.aliases) +
|
||||
row('SSL', sslMode === 'letsencrypt' ? "Let's Encrypt" : 'Certificato manuale') +
|
||||
row('SSL', sslLabel) +
|
||||
'<div class="summary-section">SIEM</div>' +
|
||||
row('OpenSearch URL', d.os_url) + row('Username', d.os_user) +
|
||||
'<div class="summary-section">Utente admin</div>' +
|
||||
|
|
|
|||
|
|
@ -177,8 +177,7 @@ def generate_argos_json(data):
|
|||
"full_name": data.get("cliente_full", ""),
|
||||
"domain": data.get("cliente_domain", ""),
|
||||
"type": data.get("cliente_type", "enterprise"),
|
||||
"ai_context": "",
|
||||
"sharepoint_tenant": "",
|
||||
"ai_context": data.get("ai_context", ""),
|
||||
"console_url": ""
|
||||
},
|
||||
"network": {
|
||||
|
|
@ -364,6 +363,19 @@ def install(data):
|
|||
chown(CONFIG_DIR / "modules.json")
|
||||
log("modules.json copiato")
|
||||
|
||||
# 5b. File .example aggiuntivi (config sezioni recenti)
|
||||
# - automations.json: config feed TI sources + cron daemon TI
|
||||
# - siem_integrations.json: catalogo SIEM Integration Builder
|
||||
# - subnet_registry.json: mapping sede/reparto da subnet
|
||||
for stem in ("automations", "siem_integrations", "subnet_registry"):
|
||||
src = APP_DIR / f"config/{stem}.json.example"
|
||||
dst = CONFIG_DIR / f"{stem}.json"
|
||||
if src.exists() and not dst.exists():
|
||||
shutil.copy(src, dst)
|
||||
os.chmod(dst, 0o640)
|
||||
chown(dst)
|
||||
log(f"{stem}.json copiato da template")
|
||||
|
||||
# 6. Logo cliente
|
||||
logo_src = SETUP_DIR / "logo_cliente.png"
|
||||
if logo_src.exists():
|
||||
|
|
@ -413,6 +425,74 @@ def install(data):
|
|||
ssl_crt = str(CERTS_DIR / "fullchain.pem")
|
||||
ssl_key = str(CERTS_DIR / "privkey.pem")
|
||||
log("Certificato SSL manuale copiato")
|
||||
elif ssl_mode == "selfsigned":
|
||||
# Cert autofirmato — utile per installazioni LAN/dev senza DNS
|
||||
# pubblico. Validita' 10 anni, RSA 4096, SAN da hostname+alias.
|
||||
log("Generazione certificato autofirmato (RSA 4096, validita' 10 anni)")
|
||||
CERTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
crt_path = CERTS_DIR / "fullchain.pem"
|
||||
key_path = CERTS_DIR / "privkey.pem"
|
||||
cnf_path = CERTS_DIR / "openssl-selfsigned.cnf"
|
||||
|
||||
# Build subjectAltName list: tutti i nomi DNS + IP server
|
||||
san_dns = [n for n in all_names.split() if n]
|
||||
try:
|
||||
server_ip = subprocess.check_output(
|
||||
["hostname", "-I"], text=True
|
||||
).strip().split()[0]
|
||||
except Exception:
|
||||
server_ip = ""
|
||||
|
||||
san_lines = "\n".join(f"DNS.{i+1} = {n}" for i, n in enumerate(san_dns))
|
||||
if server_ip:
|
||||
san_lines += f"\nIP.1 = {server_ip}"
|
||||
|
||||
# Subject info: cliente_full come O, domain come CN
|
||||
client_full = data.get("cliente_full") or data.get("cliente_name") or "ARGOS"
|
||||
cn = domain or "argos.local"
|
||||
|
||||
cnf = f"""[req]
|
||||
default_bits = 4096
|
||||
prompt = no
|
||||
default_md = sha256
|
||||
distinguished_name = dn
|
||||
req_extensions = req_ext
|
||||
x509_extensions = v3_ext
|
||||
|
||||
[dn]
|
||||
C = IT
|
||||
O = {client_full}
|
||||
OU = ARGOS SOC
|
||||
CN = {cn}
|
||||
|
||||
[req_ext]
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[v3_ext]
|
||||
subjectAltName = @alt_names
|
||||
basicConstraints = critical, CA:FALSE
|
||||
keyUsage = critical, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
|
||||
[alt_names]
|
||||
{san_lines}
|
||||
"""
|
||||
cnf_path.write_text(cnf)
|
||||
# Genero la chiave e il cert in un solo passo
|
||||
run(
|
||||
f"openssl req -x509 -nodes -days 3650 -newkey rsa:4096 "
|
||||
f"-keyout {key_path} -out {crt_path} -config {cnf_path}"
|
||||
)
|
||||
os.chmod(key_path, 0o600)
|
||||
os.chmod(crt_path, 0o644)
|
||||
chown(key_path)
|
||||
chown(crt_path)
|
||||
ssl_crt = str(crt_path)
|
||||
ssl_key = str(key_path)
|
||||
log(f"Cert autofirmato generato (CN={cn}, SAN: {len(san_dns)} DNS"
|
||||
f"{' + 1 IP' if server_ip else ''})")
|
||||
log("⚠️ ATTENZIONE: i browser segnaleranno il cert come non attendibile.")
|
||||
log(" Usare solo per LAN/test. Per produzione: Let's Encrypt o cert CA.")
|
||||
else:
|
||||
_write_nginx_http(all_names)
|
||||
run("nginx -t && systemctl restart nginx")
|
||||
|
|
|
|||
Loading…
Reference in New Issue