#77 — config-backup-git
PLANIFIÉ
Priorité: 🟠 HAUTE · Type: C (Celery) · Conteneur: rgz-beat · Code: app/tasks/config_backup.pyDépendances: #7 rgz-gateway
Description
Sauvegarde quotidienne à 04:00 UTC des configurations réseau vers dépôt git local (git.rgz.local). Permet :
- Versioning: Historique complet modifications config (qui a changé quoi, quand)
- Audit trail: Logs commits automatiques avec timestamp
- Rollback rapide: Revenir à version précédente en < 5 min si config cassée
- Multi-repo: Configs séparées par domaine (gateway, dns, radius, kea)
Configurations sauvegardées :
- nftables rules (
scripts/gateway/*.nft) - Unbound DNS config + sinkhole rules (
config/unbound/) - FreeRADIUS config (
config/radius/) - Kea DHCP config (
config/kea/) - BGP/OSPF (quand implémentés)
Chaque commit = snapshot config jour précédent. En cas incident config, audit trail + rollback possible.
Architecture Interne
Flux de Backup
Quotidiennement 04:00 UTC:
↓
Celery Beat déclenche rgz.backup.configs
↓
Pour chaque domaine (gateway, dns, radius, kea):
1. Récupérer fichier config actuel (de volumes Docker)
2. Comparer avec dernière version sauvegardée
3. SI changé:
- Copier fichier vers git working dir
- Ajouter à git: git add {fichier}
- Commit avec message: "backup {timestamp} {fichier} {hash_ancien}->{hash_nouveau}"
4. SI aucun changé pour ce domaine:
- Commit vide (marquer date dernière sauvegarde)
5. Git push git.rgz.local (origin)
↓
Notification:
Email NOC: "Config backup — 4 files changed, 2 commits"
↓
Logging:
Enregistrer commit hashes dans DB pour audit trailSchéma de Données
sql
-- Table tracking git backups
TABLE config_git_backups:
id UUID PK
backup_date TIMESTAMP
domain TEXT CHECK(gateway|dns|radius|kea|ospf)
config_file TEXT (ex: /config/radius/radiusd.conf)
commit_hash TEXT (git SHA1)
commit_message TEXT
changes_made BOOLEAN (true si fichier modifié)
previous_hash TEXT (commit parent)
backed_up_at TIMESTAMP
backed_up_by TEXT ("system", "manual", etc.)
-- Table rollback capability
TABLE config_rollback_history:
id UUID PK
requested_at TIMESTAMP
requested_by UUID FK (user qui demande rollback)
domain TEXT
target_hash TEXT (commit hash à restaurer)
rollback_status CHECK(pending|in_progress|success|failed)
completed_at TIMESTAMP
reason TEXT (why rollback was needed)
duration_seconds INTExemple Git Backup
GIT CONFIG BACKUP — February 6, 2026
Start: 2026-02-06 04:00:00 UTC
Repo: git.rgz.local:/configs.git
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DOMAIN: gateway
Changes:
scripts/gateway/rgz-main.nft
Previous: a1b2c3d4e5f6 (2026-02-05)
Current: f7e42c9f5a3e (2026-02-06)
Modified: +2 rules (DNS sinkhole list update)
scripts/gateway/watch_config.sh
Unchanged (hash: 7c3d4e5f6a7b)
Commit: 2026-02-06T04:15:23Z
Author: celery-beat@rgz.local
Message: "config backup 2026-02-06 scripts/gateway/rgz-main.nft a1b2c3d→f7e42c9f"
Hash: abc123def456
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DOMAIN: dns
Changes:
config/unbound/unbound.conf
Previous: 3d4f5e6a7b8c (2026-02-05)
Current: 8c9d0e1f2a3b (2026-02-06)
Modified: +12 new blocklist entries
config/unbound/unbound.conf.d/sinkhole.conf
Unchanged (hash: 2b4d7c8f6a9e)
Commit: 2026-02-06T04:22:15Z
Hash: ghj456khi789
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
DOMAIN: radius
Changes:
config/radius/radiusd.conf
Unchanged (hash: 9e2b4d7c8f6a)
config/radius/clients.conf
Unchanged (hash: 6a9e2b4d7c8f)
Commit: 2026-02-06T04:23:00Z
Author: celery-beat@rgz.local
Message: "config backup 2026-02-06 — no changes (radius domain stable)"
Hash: lmn789opq012
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GIT PUSH: git.rgz.local (origin)
Commits pushed: 3
Status: SUCCESS ✓
────────────────────────────────────────────────────────
AUDIT TRAIL (via git log):
$ git log --oneline | head
abc123 config backup 2026-02-06 scripts/gateway/...
ghj456 config backup 2026-02-06 config/unbound/...
lmn789 config backup 2026-02-06 (no changes radius)
pqr012 config backup 2026-02-05 config/radius/...
stu345 config backup 2026-02-04 scripts/gateway/...
ROLLBACK CAPABILITY:
Si incident le 2026-02-06, facilement revenir:
$ git checkout abc123^ -- config/ # Revenir à 2026-02-05
$ docker-compose exec rgz-gateway /entrypoint.sh # Reload
→ Downtime: < 5 minutes
────────────────────────────────────────────────────────
End: 2026-02-06 04:28:47 UTC
Next: 2026-02-07 04:00:00 UTCConfiguration
env
# Config Git Backup
CONFIG_BACKUP_ENABLED=true
CONFIG_BACKUP_HOUR_UTC=4
CONFIG_BACKUP_MINUTE_UTC=0
# Git Repository
GIT_BACKUP_REPO=git@git.rgz.local:configs.git
GIT_BACKUP_BRANCH=main
GIT_SSH_KEY_PATH=/home/rgz-app/.ssh/git_rsa
GIT_SSH_KNOWN_HOSTS=/home/rgz-app/.ssh/known_hosts
GIT_WORKING_DIR=/tmp/config-backup-git
GIT_USER_NAME=celery-beat@rgz.local
GIT_USER_EMAIL=celery@rgz.local
# Domains to backup
CONFIG_BACKUP_DOMAINS=gateway,dns,radius,kea
# Paths to backup (per domain)
CONFIG_BACKUP_PATHS__gateway=scripts/gateway/*.nft,scripts/gateway/watch_config.sh
CONFIG_BACKUP_PATHS__dns=config/unbound/unbound.conf,config/unbound/unbound.conf.d/*
CONFIG_BACKUP_PATHS__radius=config/radius/radiusd.conf,config/radius/clients.conf,config/radius/dictionary
CONFIG_BACKUP_PATHS__kea=config/kea/kea-dhcp4.conf
# Retention
CONFIG_BACKUP_GIT_RETENTION_MONTHS=24 # Keep 2 years git history
# Notifications
CONFIG_BACKUP_NOTIFY_ON_CHANGES=true
CONFIG_BACKUP_NOTIFY_RECIPIENTS=noc@rgz.localEndpoints API
| Méthode | Route | Description | Réponse |
|---|---|---|---|
| GET | /api/v1/backups/config/status | Statut dernier backup | |
| GET | /api/v1/backups/config/history?domain={dns} | Historique changements config | List[{date, file, commit_hash, change_type}] |
| GET | /api/v1/backups/config/diff?commit1={hash1}&commit2={hash2} | Diff entre 2 commits | |
| POST | /api/v1/backups/config/rollback?commit={hash} | Rollback à config antérieure (admin) | 202 Accepted + |
| GET | /api/v1/backups/config/rollback/status?task_id={id} | Suivi rollback |
Authentification: Admin + NOC only
Celery Task
| Champ | Valeur |
|---|---|
| Task name | rgz.backup.configs |
| Schedule | Daily 04:00 UTC (0 4 * * *) |
| Queue | rgz.maintenance |
| Timeout | 300s |
| Retry | 2x |
Logique esquisse:
python
@app.task(name='rgz.backup.configs', bind=True)
def backup_configs_to_git(self):
"""
Backup configurations réseau vers git.rgz.local
"""
backup_date = datetime.utcnow()
results = {}
try:
# 1. Clone/fetch repo
git_repo = _ensure_git_repo_cloned()
# 2. Pour chaque domaine
for domain in settings.CONFIG_BACKUP_DOMAINS:
domain_results = {
'domain': domain,
'files_changed': 0,
'files_unchanged': 0,
'commit_hash': None
}
# Récupérer fichiers à backup
file_patterns = settings.CONFIG_BACKUP_PATHS.get(domain, [])
files_to_backup = _expand_file_patterns(file_patterns)
any_change = False
for src_file in files_to_backup:
# Copier fichier vers git working dir
dst_file = os.path.join(git_repo, src_file)
os.makedirs(os.path.dirname(dst_file), exist_ok=True)
# Comparer avec version précédente
old_hash = _get_file_hash(dst_file) if os.path.exists(dst_file) else None
new_hash = _get_file_hash(src_file)
if old_hash != new_hash:
shutil.copy2(src_file, dst_file)
git_repo.index.add(dst_file)
domain_results['files_changed'] += 1
any_change = True
logger.info(f"Config changed: {src_file} ({old_hash} → {new_hash})")
else:
domain_results['files_unchanged'] += 1
# 3. Commit si changement
if any_change:
commit_message = f"config backup {backup_date.isoformat()} {domain}"
commit = git_repo.index.commit(
commit_message,
author=Actor(settings.GIT_USER_NAME, settings.GIT_USER_EMAIL)
)
domain_results['commit_hash'] = commit.hexsha
logger.info(f"Committed {domain}: {commit.hexsha}")
else:
# Commit vide (just mark date)
commit_message = f"config backup {backup_date.isoformat()} — no changes ({domain})"
# Git allows empty commit with --allow-empty
try:
commit = git_repo.index.commit(
commit_message,
author=Actor(settings.GIT_USER_NAME, settings.GIT_USER_EMAIL)
)
except:
logger.info(f"No changes for {domain}, skipping empty commit")
results[domain] = domain_results
# 4. Git push
git_repo.remotes.origin.push(settings.GIT_BACKUP_BRANCH)
logger.info(f"Pushed {len(results)} domains to {settings.GIT_BACKUP_REPO}")
# 5. Enregistrer metadata
for domain, domain_result in results.items():
backup_exec = ConfigGitBackup(
backup_date=backup_date,
domain=domain,
commit_hash=domain_result['commit_hash'],
changes_made=domain_result['files_changed'] > 0,
backed_up_at=datetime.utcnow()
)
db.add(backup_exec)
db.commit()
# 6. Notification
total_changed = sum(r['files_changed'] for r in results.values())
total_commits = sum(1 for r in results.values() if r['commit_hash'])
send_email.delay(
to=settings.CONFIG_BACKUP_NOTIFY_RECIPIENTS,
subject=f"Config Backup — {total_changed} files changed, {total_commits} commits",
template='config_backup_success',
context={'results': results}
)
return {
'status': 'success',
'results': results
}
except Exception as e:
logger.error(f"Config backup failed: {e}")
send_email.delay(
to=settings.CONFIG_BACKUP_NOTIFY_RECIPIENTS,
subject='ALERT: Config Backup FAILED',
template='config_backup_failure',
context={'error': str(e)}
)
self.retry(exc=e, countdown=300)Commandes Utiles
bash
# Déclencher config backup manuellement
docker-compose exec rgz-api celery -A app.celery_app call rgz.backup.configs
# Voir historique commits config
cd /tmp/config-backup-git && git log --oneline --graph | head -20
# Diff commits (voir changements config)
cd /tmp/config-backup-git && git diff abc123 ghj456 -- config/
# Vérifier statut git repo
curl -H "Authorization: Bearer {admin_token}" \
"http://api-rgz.duckdns.org/api/v1/backups/config/status" | jq
# Historique changements domain spécifique
curl -H "Authorization: Bearer {admin_token}" \
"http://api-rgz.duckdns.org/api/v1/backups/config/history?domain=dns" | jq
# Rollback à version antérieure
curl -X POST -H "Authorization: Bearer {admin_token}" \
"http://api-rgz.duckdns.org/api/v1/backups/config/rollback?commit=abc123def456"
# Logs backup
docker-compose logs rgz-beat | grep "rgz.backup.configs"
# Vérifier git repo connectivity
ssh -i /home/rgz-app/.ssh/git_rsa git@git.rgz.local "git -C /data/configs.git log --oneline | head"Implémentation TODO
- [ ] Schéma DB
config_git_backups+config_rollback_history - [ ] Tâche Celery
rgz.backup.configsdansapp/tasks/config_backup.py - [ ] Fonction
_ensure_git_repo_cloned()(setup git working dir) - [ ] Fonction
_expand_file_patterns()(glob support) - [ ] Fonction
_get_file_hash()(SHA256) - [ ] Endpoints API GET/POST /api/v1/backups/config/*
- [ ] Rollback task:
rgz.config.rollback(trigger + validation) - [ ] Git SSH key management (security, rotation)
- [ ] Email notification templates (changes, failures)
- [ ] Tests: simulate config changes, diff, rollback
- [ ] Documentation: SOP config management, git workflow, emergency rollback
Dernière mise à jour: 2026-02-21