Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
b3c8832
Implementazione sistema backup CRI Catania
claude Jun 9, 2026
362f3e3
feat: autenticazione, verifica integrità, retention per copie, notifi…
claude Jun 9, 2026
ead12c3
feat: pagina Report & Statistiche con grafici e export CSV
claude Jun 9, 2026
717bc92
fix: aggiungi cartella static mancante
claude Jun 9, 2026
429fd96
feat: installer interattivo per Ubuntu Server
claude Jun 9, 2026
970bfeb
Refactor: remove brand references, add edit/test UI, fix Fernet key
claude Jun 10, 2026
860213e
Remove all remaining CRI Catania brand references
claude Jun 10, 2026
db104bb
Aggiungi backup bare-metal Windows via wbadmin su share SMB QNAP
claude Jun 10, 2026
64036bd
Fix: ripristino endpoints e template server sovrascritti
claude Jun 10, 2026
9ec8215
Organizzazione configurabile da impostazioni, rimossi tutti i riferim…
claude Jun 10, 2026
d66f86a
Sorgenti separate per tipo: Linux, Windows, VMware
claude Jun 10, 2026
53dffb9
Aggiungi sistema replica VM con failover automatico e manuale
claude Jun 10, 2026
5896b86
Documentazione utente e test connessione inline per sorgenti/destinaz…
claude Jun 10, 2026
50b102e
Guida utente: checklist live con stato reale del sistema
claude Jun 10, 2026
58ab1e4
Fix rsync --mkpath su QNAP e snapshot ESXi licenza Free
claude Jun 10, 2026
25bae62
Fix export VM su ESXi Free License: fallback automatico su ovftool
claude Jun 10, 2026
1de6c88
Fix backup VMware su ESXi Free: download diretto dal datastore HTTPS
claude Jun 10, 2026
682c43b
Backup VMware: streaming diretto ESXi → QNAP, zero disco locale
claude Jun 10, 2026
a1b17a2
Fix streaming VMware: sopprime warning SSH e mostra errore reale
claude Jun 10, 2026
110339c
Fix SSH auth: rimosso BatchMode=yes che bloccava sshpass
claude Jun 10, 2026
6cfd34d
Messaggio errore mkdir QNAP più chiaro con istruzioni
claude Jun 10, 2026
a772673
Add XCP-ng hypervisor support via XAPI
claude Jun 10, 2026
c85d342
Aggiungi cancellazione host ESXi e XCP-ng
claude Jun 10, 2026
aceee8d
Pulsante Riprova per backup falliti + aggiornamento manuale
claude Jun 10, 2026
3912859
Fix cancellazione host e pagina Guida
claude Jun 10, 2026
0444066
Fix colore pulsante Riprova: icona bianca su sfondo scuro
claude Jun 10, 2026
f6e5a12
Intestazioni tabella storico in bianco
claude Jun 10, 2026
1b753e1
Testo tabella storico esecuzioni in bianco
claude Jun 10, 2026
145accc
Forza testo bianco tabella storico con !important (override Bootstrap)
claude Jun 10, 2026
43b5382
Testo bianco su tutte le tabelle: fix globale in base.html
claude Jun 10, 2026
8c47725
Fix backup VM Snapshot Windows/Linux su ESXi: streaming diretto + gua…
claude Jun 10, 2026
d78132c
Messaggio errore cartella vuota più specifico con istruzioni
claude Jun 10, 2026
dad05a1
Aggiungi modifica job: endpoint PATCH /api/jobs/{id} e modal UI
claude Jun 10, 2026
17b2c5b
Fix cascade delete job: aggiunge delete-orphan su runs e logs
claude Jun 10, 2026
c5e0684
Form Windows: aggiungi campi Host ESXi e VM name per VM Snapshot
claude Jun 10, 2026
10feb36
Fix cascade delete sorgente e destinazione
claude Jun 10, 2026
f49eb65
Avviso esplicito nel log quando job è APP_DATA ma sorgente ha vmware_…
claude Jun 10, 2026
3734f91
Errore chiaro quando manca SMB share per backup Windows FULL
claude Jun 10, 2026
98ea325
Fix autenticazione SMB wbadmin: usa PSCredential invece di net use
claude Jun 10, 2026
4f6a38f
Aggiungi box informativi nei wizard con requisiti lato NAS/server
claude Jun 10, 2026
4cea287
Sorgenti: tab distinti con descrizione, rimozione campi ESXi da form …
claude Jun 10, 2026
ca41f3b
Fix HTML: rimossi </div> spuri nei panel VMware e XCP-ng
claude Jun 10, 2026
ca5296f
Cambio modalità
ico88 Jun 10, 2026
c207ea3
Fix Cambio modalità
ico88 Jun 10, 2026
18223e8
Fix Cambio modalità
ico88 Jun 10, 2026
924c517
Fix Cambio modalità
ico88 Jun 10, 2026
521b9eb
Fix Cambio modalità
ico88 Jun 10, 2026
73b945e
Fix Cambio modalità
ico88 Jun 10, 2026
189114a
Fix Cambio modalità
ico88 Jun 10, 2026
97766aa
Fix Cambio modalità
ico88 Jun 10, 2026
7e81d61
Fix Cambio modalità
ico88 Jun 10, 2026
95a0a1f
Fix Cambio modalità
ico88 Jun 10, 2026
af5f081
Fix Cambio modalità
ico88 Jun 10, 2026
249ea49
Fix Cambio modalità
ico88 Jun 11, 2026
5a541a4
Replication verboose
ico88 Jun 14, 2026
09bc734
Replication fix
ico88 Jun 15, 2026
5d9a34d
Replication fix delete
ico88 Jun 15, 2026
c24667c
Replication fix net
ico88 Jun 15, 2026
8f35436
Replication fix net
ico88 Jun 15, 2026
663f0d7
Replication fix hardware
ico88 Jun 15, 2026
4f81bdf
Replication fix VM versione
ico88 Jun 15, 2026
39839ab
Replication fix VM versione
ico88 Jun 15, 2026
1324148
Replication fix VM versione
ico88 Jun 15, 2026
73c0074
Replication fix VM versione
ico88 Jun 15, 2026
220e926
Replication fix VM versione
ico88 Jun 16, 2026
b381ce0
Add reset_admin.py script to reset admin password from CLI
ico88 Aug 11, 2026
3aeefde
Aggiunge supporto CBT (Changed Block Tracking) per backup incremental…
claude Aug 11, 2026
4b29815
Fix replica VM: rimuove mapping rete hardcoded, aggiunge campo target…
claude Aug 11, 2026
9c1d6d7
Rilevamento automatico mapping reti per replica VM ESXi
claude Aug 11, 2026
e92bea6
Replica VM a caldo: rimuove --powerOffSource da ovftool
claude Aug 11, 2026
b762e04
Aggiunge barra di progresso trasferimento disco nella schermata replica
claude Aug 11, 2026
9948a0b
Dashboard: aggiunge sezione stato repliche VM
claude Aug 11, 2026
a63c2fe
Rimuove --maxVirtualHardwareVersion=10 da ovftool
claude Aug 11, 2026
40f65f9
Replica VM incrementale CBT: trasferisce solo i blocchi modificati
claude Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Copia questo file in .env e adattalo
PORT=8000
DEV=false

# Chiave di cifratura credenziali (generata automaticamente al primo avvio)
# Per produzione: genera con: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# BACKUP_SECRET_KEY=...

# Percorso DB (default: SQLite locale)
# DATABASE_URL=sqlite:///./backup_all.db
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
__pycache__/
*.pyc
*.pyo
.env
.secret_key
backup_all.db
*.db-journal
*.db-wal
*.db-shm
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,68 @@
# Backup-all
# Backup-All — CRI Catania

Sistema automatizzato di backup per VM VMware ESXi con UI web di configurazione.

## Architettura

- **Backend**: Python 3.11 + FastAPI
- **Database**: SQLite (via SQLAlchemy) — sostituibile con PostgreSQL/MySQL
- **Scheduler**: APScheduler (cron)
- **Backup VM**: pyvmomi → ESXi snapshot + export OVF
- **Backup dati Windows** (Gamma/TeamSystem): WinRM + PowerShell
- **Backup dati Linux** (Abulafia): SSH + rsync + pg_dump
- **Destinazione**: QNAP via rsync-over-SSH o API QTS

## Installazione

```bash
pip install -r requirements.txt
python run.py
```

Apri il browser su `http://localhost:8000`

## Primo avvio — wizard configurazione

1. **Aggiungi Server** → `/wizard/server`
- Configura Windows Server (Gamma) e Debian (Abulafia)
- Inserisci credenziali WinRM / SSH
- Collega all'host ESXi per backup OVF

2. **Aggiungi QNAP** → `/wizard/destination`
- IP del NAS, credenziali SSH, percorso base

3. **Crea Job** → `/wizard/job`
- Scegli server, destinazione, tipo backup
- Configura schedule cron (o usa i preset)

## Prerequisiti

### Server Windows (Gamma)
```powershell
winrm quickconfig -y
winrm set winrm/config/service/auth '@{Basic="true"}'
```

### Server Debian (Abulafia)
- SSH abilitato con utente con accesso ai dati

### QNAP
- Abilitare SSH: `Pannello di Controllo > Terminale & SNMP > Abilita SSH`
- Creare utente dedicato backup con accesso alla share

### ESXi
- Utente con ruolo Administrator o almeno permessi snapshot + datastore

## Struttura backup sul QNAP

```
/share/Backup/cri-catania/
windows-gamma/
2024-01-15_02-00-00/
vm_export/ <- OVF + VMDK
app_data/ <- file Gamma + .bak SQL Server
debian-abulafia/
2024-01-15_02-00-00/
vm_export/
app_data/ <- dati + dump PostgreSQL
```
Empty file added app/__init__.py
Empty file.
Empty file added app/api/__init__.py
Empty file.
119 changes: 119 additions & 0 deletions app/api/auth_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import RedirectResponse
from pydantic import BaseModel
from sqlalchemy.orm import Session
from datetime import datetime, timezone

from app.database import get_db
from app.auth import hash_password, verify_password, login_user, logout_user
from app.models import User

router = APIRouter(prefix="/api/auth", tags=["auth"])


class LoginRequest(BaseModel):
username: str
password: str


class ChangePasswordRequest(BaseModel):
current_password: str
new_password: str


class CreateUserRequest(BaseModel):
username: str
email: str = ""
password: str
is_admin: bool = False


@router.post("/login")
def login(data: LoginRequest, request: Request, db: Session = Depends(get_db)):
user = db.query(User).filter_by(username=data.username, is_active=True).first()
if not user or not verify_password(data.password, user.hashed_password):
raise HTTPException(401, "Credenziali non valide")
login_user(request, user.id)
user.last_login_at = datetime.now(timezone.utc)
db.commit()
return {"ok": True, "username": user.username, "is_admin": user.is_admin}


@router.post("/logout")
def logout(request: Request):
logout_user(request)
return {"ok": True}


@router.get("/me")
def me(request: Request, db: Session = Depends(get_db)):
uid = request.session.get("user_id")
if not uid:
raise HTTPException(401, "Non autenticato")
user = db.get(User, uid)
if not user:
raise HTTPException(401, "Utente non trovato")
return {"id": user.id, "username": user.username, "email": user.email,
"is_admin": user.is_admin, "last_login_at": user.last_login_at}


@router.post("/change-password")
def change_password(data: ChangePasswordRequest, request: Request, db: Session = Depends(get_db)):
uid = request.session.get("user_id")
if not uid:
raise HTTPException(401, "Non autenticato")
user = db.get(User, uid)
if not verify_password(data.current_password, user.hashed_password):
raise HTTPException(400, "Password attuale non corretta")
user.hashed_password = hash_password(data.new_password)
db.commit()
return {"ok": True}


@router.get("/users")
def list_users(request: Request, db: Session = Depends(get_db)):
uid = request.session.get("user_id")
if not uid:
raise HTTPException(401, "Non autenticato")
caller = db.get(User, uid)
if not caller or not caller.is_admin:
raise HTTPException(403, "Solo gli amministratori possono gestire gli utenti")
users = db.query(User).all()
return [{"id": u.id, "username": u.username, "email": u.email,
"is_admin": u.is_admin, "is_active": u.is_active,
"last_login_at": u.last_login_at} for u in users]


@router.post("/users", status_code=201)
def create_user(data: CreateUserRequest, request: Request, db: Session = Depends(get_db)):
uid = request.session.get("user_id")
if not uid:
raise HTTPException(401, "Non autenticato")
caller = db.get(User, uid)
if not caller or not caller.is_admin:
raise HTTPException(403, "Solo gli amministratori possono creare utenti")
if db.query(User).filter_by(username=data.username).first():
raise HTTPException(400, "Username già esistente")
user = User(username=data.username, email=data.email,
hashed_password=hash_password(data.password), is_admin=data.is_admin)
db.add(user)
db.commit()
return {"id": user.id, "username": user.username}


@router.delete("/users/{user_id}")
def delete_user(user_id: int, request: Request, db: Session = Depends(get_db)):
uid = request.session.get("user_id")
if not uid:
raise HTTPException(401, "Non autenticato")
caller = db.get(User, uid)
if not caller or not caller.is_admin:
raise HTTPException(403, "Solo gli amministratori possono eliminare utenti")
if user_id == uid:
raise HTTPException(400, "Non puoi eliminare te stesso")
user = db.get(User, user_id)
if not user:
raise HTTPException(404, "Utente non trovato")
db.delete(user)
db.commit()
return {"ok": True}
Loading