backupmanager_ynhVersion 4.0 — Finale | 2026-05-08
Application YunoHost centralisant toutes les sauvegardes d'un serveur, s'appuyant au maximum sur les outils YunoHost natifs, extensible en écosystème fédéré multi-instances.
Périmètre : Apps YNH · Système YNH · Répertoires custom · MySQL · PostgreSQL
┌──────────────┐ API ┌──────────────┐ API ┌──────────────┐
│ Instance A │◄─────►│ Instance B │◄─────►│ Instance C │
│ (maître) │ │ (nœud) │ │ (nœud) │
└──────────────┘ └──────────────┘ └──────────────┘
N'importe quelle instance peut être "maître"
| Sujet | Décision |
|---|---|
| Langage | Python 3 + Flask |
| Scheduler | APScheduler + SQLite |
| DB | SQLite + SQLAlchemy |
| Frontend | Jinja2 + TailwindCSS CDN |
| Proxy/Service | Nginx + systemd YunoHost natifs |
| Archives | /home/yunohost.backup/archives/ |
| Nommage | jerry_nocodb_20260508.tar |
| Auth dashboard | SSO YunoHost — admins uniquement |
| Auth API | /api ouvert SSOwat + token X-BackupManager-Key |
| Transfert | HTTP chunked (défaut) + SSH/rsync (optionnel) |
| Restauration | Complète : fichiers + user système + systemd + DB |
backupmanager_ynh/
├── manifest.toml
├── scripts/
│ ├── _common.sh
│ ├── install / remove / backup / restore / upgrade
├── conf/
│ ├── nginx.conf
│ ├── systemd.service
│ └── app.conf.j2
├── sources/
│ ├── app.py # Flask routes + API REST
│ ├── scheduler.py # APScheduler
│ ├── db.py # SQLAlchemy
│ ├── retention.py # Moteur count/daily/gfs
│ ├── jobs/
│ │ ├── ynh_backup.py # yunohost backup create
│ │ ├── custom_dir.py # tar + rsync chemins libres
│ │ ├── db_dump.py # mysqldump / pg_dump
│ │ └── transfer.py # rsync SSH / SFTP
│ ├── federation/
│ │ ├── api.py # Endpoints REST
│ │ ├── client.py # Appels instances distantes
│ │ └── sync.py # Sync état réseau
│ └── templates/
│ ├── base.html
│ ├── dashboard_local.html
│ ├── dashboard_network.html
│ ├── job_form.html
│ └── job_history.html
└── doc/
-- Jobs de sauvegarde
CREATE TABLE jobs (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
type TEXT NOT NULL, -- ynh_app|ynh_system|custom_dir|mysql|postgresql
config_json TEXT,
cron_expr TEXT NOT NULL, -- ex: "0 3 * * 1"
retention_mode TEXT NOT NULL, -- count|daily|gfs
retention_value INTEGER NOT NULL,
enabled BOOLEAN DEFAULT 1,
core_only BOOLEAN DEFAULT 0,
created_at DATETIME,
updated_at DATETIME
);
-- Historique des exécutions
CREATE TABLE runs (
id INTEGER PRIMARY KEY,
job_id INTEGER REFERENCES jobs(id),
started_at DATETIME,
finished_at DATETIME,
status TEXT, -- running|success|error
log_text TEXT,
archive_name TEXT,
size_bytes INTEGER
);
-- Instances distantes enregistrées
CREATE TABLE remote_instances (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL, -- ex: "tom"
url TEXT NOT NULL, -- https://tom.domaine.fr
api_key TEXT NOT NULL,
last_seen DATETIME,
status TEXT, -- online|offline|error
created_at DATETIME
);
-- Cache états distants
CREATE TABLE remote_runs (
id INTEGER PRIMARY KEY,
instance_id INTEGER REFERENCES remote_instances(id),
job_name TEXT,
last_run_at DATETIME,
last_status TEXT,
archive_count INTEGER,
last_size_bytes INTEGER
);
-- Transferts chunked en cours
CREATE TABLE uploads (
upload_id TEXT PRIMARY KEY, -- uuid4
filename TEXT,
total_size INTEGER,
chunk_size INTEGER,
chunks_received INTEGER DEFAULT 0,
checksum TEXT, -- SHA256
started_at DATETIME,
status TEXT -- pending|in_progress|complete|error
);
ynh_appyunohost backup create --apps <app_id> --name <archive_name>
BACKUP_CORE_ONLY=1 yunohost backup create --apps nextcloud --name <archive_name>
{ "app_id": "nocodb", "core_only": false }
ynh_systemyunohost backup create --system --name <archive_name>
{ "parts": [] }
custom_dir{
"source_path": "/opt/hermes-agent",
"excludes": ["cache/", "logs/", "*.tmp"],
"restore": {
"system_user": { "name": "hermes-agent", "home": "/opt/hermes-agent", "shell": "/bin/false" },
"systemd_service": { "name": "hermes-agent", "service_file": "/opt/hermes-agent/hermes-agent.service" },
"permissions": { "owner": "hermes-agent:hermes-agent", "mode": "750" },
"post_restore_commands": ["systemctl restart hermes-agent"]
}
}
Format archive compatible YunoHost :
jerry_hermes-agent_20260508.tar
├── backup.csv # requis YNH
├── backup_info.json # métadonnées BackupManager
└── data/custom/opt/hermes-agent/...
jerry_hermes-agent_20260508.info.json # SÉPARÉ hors tar, requis YNH
mysql{ "database": "mabase", "user": "mabase_user" }
postgresql{ "database": "mabase", "user": "postgres" }
count→ Garde les N dernières archives
→ Supprime les plus anciennes
daily — fenêtre glissanteretention_value: 7
→ 1 archive par jour sur les 7 derniers jours
→ Fenêtre glissante J-1 à J-7
→ Toute archive > J-7 supprimée
gfs (Phase 4)daily: 7 / weekly: 4 / monthly: 3
Format : {instance}_{app}_{YYYYMMDD}.tar
jerry_nocodb_20260508.tar
jerry_nextcloud_20260508.tar
jerry_system_20260508.tar
jerry_hermes-agent_20260508.tar
jerry_mysql_mabase_20260508.tar
backup_info.json embarqué dans le tar :
{
"instance_name": "jerry",
"instance_url": "https://jerry.mondomaine.fr",
"app": "nocodb",
"type": "ynh_app",
"created_at": "2026-05-08T03:00:00",
"backupmanager_version": "1.0.0",
"yunohost_version": "12.1.17"
}
| Outil YNH | Usage |
|---|---|
yunohost backup create |
Jobs ynh_app et ynh_system |
yunohost backup restore |
Restauration webadmin |
yunohost app list --output-as json |
Formulaire job |
/home/yunohost.backup/archives/ |
Toutes les archives |
| Nginx + systemd + SSOwat + Let's Encrypt | Infra app |
SSOwat (manifest.toml) :
[resources.permissions.main]
url = "/"
allowed = "admins"
[resources.permissions.api]
url = "/api"
allowed = "visitors" # sécurisé par token Flask
auth_header = false
protected = true
Tous les endpoints protégés par X-BackupManager-Key.
GET /api/v1/health
GET /api/v1/jobs
GET /api/v1/jobs/<id>/runs
POST /api/v1/jobs/<id>/run
GET /api/v1/archives
GET /api/v1/archives/<name>/info
DELETE /api/v1/archives/<name>
POST /api/v1/archives/<name>/transfer
POST /api/v1/archives/upload/start
POST /api/v1/archives/upload/<id>/chunk/<n>
POST /api/v1/archives/upload/<id>/finish
DELETE /api/v1/archives/upload/<id>
POST /api/v1/archives/<name>/restore # Phase 3
GET /api/v1/archives/<name>/restore/status # Phase 3
Chunks : 50 MB | Reprise : upload_id SQLite | Vérif : SHA256 | Transport : HTTPS
ssh-keygen -t ed25519 -f $data_dir/keys/backupmanager_rsa -N "" -C "backupmanager@jerry"
rsync -az -e "ssh -i $key -p $port" archive.tar archive.info.json user@host:/home/yunohost.backup/archives/
| Situation | Mode |
|---|---|
| SSH non configuré | HTTP chunked |
| SSH + archive < 2 GB | HTTP chunked |
| SSH + archive > 2 GB | rsync auto |
| Override manuel | Au choix |
| Action | Dashboard BM | Webadmin YNH |
|---|---|---|
| App YNH | ✅ (via YNH) | ✅ natif |
| Système YNH | ✅ (via YNH) | ✅ natif |
| Fichiers custom_dir | ✅ complet | ✅ partiel |
| User système | ✅ useradd |
❌ |
| Service systemd | ✅ systemctl |
❌ |
| Permissions | ✅ chown/chmod |
❌ |
| Post-restore commands | ✅ | ❌ |
| MySQL / PostgreSQL | ✅ | ❌ |
| Port firewall | ✅ yunohost firewall |
❌ |
| Instance distante | ✅ Phase 3 | ❌ |
| Élément | Mesure |
|---|---|
| Dashboard | SSO YunoHost admins |
| Token API | secrets.token_hex(32), hashé bcrypt |
| Clé SSH | ed25519, $data_dir/keys/, permissions 600 |
| Credentials DB | Jamais en clair, config protégée |
| Logs | Sans credentials |
| Inter-instances | HTTPS obligatoire |
backupmanager_ynh — CDC v4.0 — 2026-05-08 | Avancement mis à jour le 2026-05-08