Univ2025
Application de Sauvegarde
Système de sauvegarde automatique client-serveur avec gestion de versions, chiffrement AES-256-GCM et interface web Flask
- Durée
- 3 mois
- Équipe
- Projet universitaire, équipe de 4 étudiants
- Mon rôle
- Développeur principal : architecture système, gestion des versions, chiffrement, interface web

Contexte
Projet de BUT Informatique visant à créer un système de sauvegarde robuste pour protéger les données contre les ransomwares et les erreurs utilisateur, avec une rétention de 30 jours.
Objectifs
Développer un système de sauvegarde automatique, sécurisé via SSH, avec gestion intelligente des versions, compression gzip (70 % de réduction), déduplication par hash SHA-256 et chiffrement AES-256-GCM.
Approche technique
Architecture client-serveur avec un daemon de surveillance (watchdog) sur le client, transfert SSH/SCP sécurisé, traitement côté serveur (compression + chiffrement + déduplication), stockage des métadonnées dans SQLite et interface web Flask pour le monitoring et la restauration.
Architecture
Client : daemon watchdog, SSH/SCP via Paramiko. Serveur : process_file.py pour le traitement, version_manager.py pour la gestion des versions et la déduplication, encryption.py pour AES-256-GCM, app.py (Flask) pour l'interface web (dashboard, restauration, API REST), SQLite pour les métadonnées, service systemd pour l'automatisation.
Compétences développées
Python avancé & Architecture système
Architecture client-serveur avec daemon watchdog, transferts SSH/SCP via Paramiko, et automatisation systemd.
Cryptographie & Sécurité
Chiffrement AES-256-GCM avec dérivation PBKDF2, compression gzip (70% réduction), et déduplication par hash SHA256.
Flask & API REST
Interface web Flask avec dashboard temps réel, API REST pour consultation et restauration de versions, authentification intégrée.
Base de données SQLite
Stockage des métadonnées de versions avec timestamps, gestion de la rétention 30 jours, et requêtes optimisées.
Extraits de code
1# daemon-sauvegarde - Version Manager avec deduplication
2class VersionManager:
3 def save_version(self, file_path, relative_path, action='modified'):
4 # 1. Calcul hash SHA256 du fichier
5 file_hash = self._calculate_hash(file_path)
6 file_size = os.path.getsize(file_path)
7
8 # 2. Verification changement (compare avec version actuelle)
9 current_hash = self._get_current_hash(relative_path)
10 if current_hash == file_hash:
11 return # Fichier inchange, pas de nouvelle version
12
13 # 3. Deduplication - verifier si hash existe deja
14 dedup_info = self._check_deduplication(file_hash)
15
16 if dedup_info:
17 # Reutiliser fichier existant (incrementer ref_count)
18 self._increment_ref_count(file_hash)
19 compressed_size = dedup_info['compressed_size']
20 dedup_ref = dedup_info['dedup_path']
21 else:
22 # 4. Compression gzip (niveau 6, ~70% reduction)
23 temp_compressed = f"{file_path}.gz"
24 with open(file_path, 'rb') as f_in:
25 with gzip.open(temp_compressed, 'wb', compresslevel=6) as f_out:
26 shutil.copyfileobj(f_in, f_out)
27
28 # 5. Chiffrement AES-256-GCM
29 encrypted_path = f"{temp_compressed}.enc"
30 self.encryption_manager.encrypt_file(temp_compressed, encrypted_path)
31
32 # 6. Stockage deduplication (hash-based path)
33 dedup_ref = self._store_deduplicated(file_hash, encrypted_path)
34 compressed_size = os.path.getsize(encrypted_path)
35
36 # 7. Enregistrement version dans SQLite
37 timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S-%f')
38 self.cursor.execute("""
39 INSERT INTO file_versions
40 (file_path, version_timestamp, file_size, compressed_size,
41 file_hash, dedup_ref, is_compressed, is_encrypted, action)
42 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
43 """, (relative_path, timestamp, file_size, compressed_size,
44 file_hash, dedup_ref, True, True, action))
45
46 self.conn.commit()Ce code implémente la gestion de versions avec déduplication intelligente. Chaque fichier est haché (SHA-256), comparé à la version actuelle, puis compressé (gzip, 70 %), chiffré (AES-256-GCM) et stocké de manière dédupliquée. Si le même hash existe déjà, le fichier est réutilisé (économie d'espace). Les métadonnées sont enregistrées dans SQLite avec un horodatage pour la rétention de 30 jours.
1# daemon-sauvegarde - Flask Web Interface API
2@app.route('/api/files/<path:file_path>/versions', methods=['GET'])
3@auth.login_required
4def get_file_versions(file_path):
5 """Recupere toutes les versions d'un fichier"""
6 versions = vm.cursor.execute("""
7 SELECT version_timestamp, file_size, compressed_size,
8 file_hash, action, created_at
9 FROM file_versions
10 WHERE file_path = ?
11 ORDER BY version_timestamp DESC
12 """, (file_path,)).fetchall()
13
14 return jsonify({
15 'file_path': file_path,
16 'versions': [{
17 'timestamp': v[0],
18 'size': v[1],
19 'compressed_size': v[2],
20 'hash': v[3],
21 'action': v[4],
22 'created_at': v[5]
23 } for v in versions]
24 })
25
26@app.route('/api/restore', methods=['POST'])
27@auth.login_required
28def restore_file():
29 """Restaure une version specifique d'un fichier"""
30 data = request.json
31 file_path = data.get('file_path')
32 timestamp = data.get('timestamp')
33
34 # 1. Recuperer infos version depuis SQLite
35 version = vm.cursor.execute("""
36 SELECT dedup_ref, is_compressed, is_encrypted
37 FROM file_versions
38 WHERE file_path = ? AND version_timestamp = ?
39 """, (file_path, timestamp)).fetchone()
40
41 if not version:
42 return jsonify({'error': 'Version not found'}), 404
43
44 dedup_ref, is_compressed, is_encrypted = version
45
46 # 2. Charger fichier depuis dedup_store
47 stored_file = os.path.join(vm.backup_path, dedup_ref)
48
49 # 3. Dechiffrement AES-256-GCM
50 decrypted_file = f"{stored_file}.dec"
51 vm.encryption_manager.decrypt_file(stored_file, decrypted_file)
52
53 # 4. Decompression gzip
54 restored_file = os.path.join('./restored', file_path)
55 os.makedirs(os.path.dirname(restored_file), exist_ok=True)
56 with gzip.open(decrypted_file, 'rb') as f_in:
57 with open(restored_file, 'wb') as f_out:
58 shutil.copyfileobj(f_in, f_out)
59
60 # 5. Nettoyage temporaires
61 os.remove(decrypted_file)
62
63 return jsonify({
64 'success': True,
65 'restored_path': restored_file,
66 'download_url': f'/api/download/{file_path}'
67 })Cette API Flask expose des endpoints REST pour consulter l'historique des versions et restaurer des fichiers. GET /api/files/<path>/versions retourne toutes les versions avec leurs métadonnées (horodatage, tailles, hash). POST /api/restore gère la restauration complète : récupération depuis dedup_store, déchiffrement AES-256-GCM, décompression gzip et écriture du fichier restauré. L'interface web utilise ces endpoints pour permettre une restauration à un instant donné.
Résultats & bilan
Système prêt pour la production avec 80 à 90 % de réduction d'espace disque (compression gzip 70 % + déduplication), interface web fonctionnelle avec dashboard en temps réel, API REST complète, tests automatiques d'intégrité et de restauration, rétention de 30 jours.
Réflexion & apprentissages
Projet enrichissant qui m'a permis d'approfondir mes compétences en architecture système, sécurité (SSH, chiffrement), optimisation (compression + déduplication) et développement web. La gestion de versions avec rétention m'a particulièrement formé aux problématiques de stockage et de récupération de données.