|
@@ -1,5 +1,5 @@
|
|
|
# Cahier des charges — `backupmanager_ynh`
|
|
# Cahier des charges — `backupmanager_ynh`
|
|
|
-**Version 4.1 — 2026-05-09**
|
|
|
|
|
|
|
+**Version 5.0 — 2026-07-17**
|
|
|
|
|
|
|
|
## 1. Vision générale
|
|
## 1. Vision générale
|
|
|
|
|
|
|
@@ -30,8 +30,9 @@ Application YunoHost centralisant toutes les sauvegardes d'un serveur, s'appuyan
|
|
|
| Nommage | `jerry_nocodb_20260508.tar` |
|
|
| Nommage | `jerry_nocodb_20260508.tar` |
|
|
|
| Auth dashboard | SSO YunoHost — admins uniquement |
|
|
| Auth dashboard | SSO YunoHost — admins uniquement |
|
|
|
| Auth API | `/api` ouvert SSOwat + token `X-BackupManager-Key` |
|
|
| Auth API | `/api` ouvert SSOwat + token `X-BackupManager-Key` |
|
|
|
-| Transfert | HTTP chunked (défaut) + SSH/rsync (optionnel) |
|
|
|
|
|
|
|
+| Transfert | HTTP chunked (fédération) + SSH/rsync (destinations SSH) |
|
|
|
| Restauration | Complète : fichiers + user système + systemd + DB |
|
|
| Restauration | Complète : fichiers + user système + systemd + DB |
|
|
|
|
|
+| Destinations | Many-to-many : un job peut cibler plusieurs destinations |
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
@@ -46,28 +47,46 @@ backupmanager_ynh/
|
|
|
├── conf/
|
|
├── conf/
|
|
|
│ ├── nginx.conf
|
|
│ ├── nginx.conf
|
|
|
│ ├── systemd.service
|
|
│ ├── systemd.service
|
|
|
-│ └── app.conf.j2
|
|
|
|
|
|
|
+│ ├── app.conf.j2
|
|
|
|
|
+│ └── sudoers
|
|
|
├── sources/
|
|
├── sources/
|
|
|
-│ ├── app.py # Flask routes + API REST
|
|
|
|
|
|
|
+│ ├── app.py # Bootstrap Flask + enregistrement blueprints
|
|
|
│ ├── scheduler.py # APScheduler
|
|
│ ├── scheduler.py # APScheduler
|
|
|
-│ ├── db.py # SQLAlchemy (Job, Run, Destination, Setting)
|
|
|
|
|
-│ ├── retention.py # Moteur count/daily/gfs
|
|
|
|
|
|
|
+│ ├── db.py # SQLAlchemy (Job, Run, Destination, Setting, …)
|
|
|
|
|
+│ ├── retention.py # Moteur count/daily/gfs + apply_ssh_retention
|
|
|
│ ├── notifications.py # Email SMTP (succès/erreur)
|
|
│ ├── notifications.py # Email SMTP (succès/erreur)
|
|
|
|
|
+│ ├── helpers.py # read_archive_info, get_ynh_apps
|
|
|
|
|
+│ ├── blueprints/
|
|
|
|
|
+│ │ ├── jobs.py # Dashboard local, CRUD jobs, archives, restauration
|
|
|
|
|
+│ │ ├── destinations.py # CRUD destinations SSH
|
|
|
|
|
+│ │ ├── network.py # Fédération, push/pull inter-instances
|
|
|
|
|
+│ │ ├── settings.py # Paramètres SMTP, export/import config JSON
|
|
|
|
|
+│ │ ├── api.py # API REST v1 (auth par token)
|
|
|
|
|
+│ │ └── overview.py # Vue globale des archives (local + SSH + instances)
|
|
|
│ ├── jobs/
|
|
│ ├── jobs/
|
|
|
-│ │ ├── ynh_backup.py # yunohost backup create/restore
|
|
|
|
|
|
|
+│ │ ├── ynh_backup.py # Point d'entrée execute_job, ynh_app, ynh_system
|
|
|
│ │ ├── custom_dir.py # tar + rsync chemins libres
|
|
│ │ ├── custom_dir.py # tar + rsync chemins libres
|
|
|
│ │ ├── db_dump.py # mysqldump / pg_dump
|
|
│ │ ├── db_dump.py # mysqldump / pg_dump
|
|
|
-│ │ ├── transfer.py # rsync SSH
|
|
|
|
|
-│ │ └── utils.py # sudo_exists/getsize/listdir/read_backup_info
|
|
|
|
|
-│ ├── federation/ # Phase 3 (non commencé)
|
|
|
|
|
|
|
+│ │ ├── transfer.py # rsync SSH + push HTTP chunked
|
|
|
|
|
+│ │ └── utils.py # sudo_exists/getsize/listdir/rm/rm_archive
|
|
|
|
|
+│ ├── federation/
|
|
|
|
|
+│ │ └── client.py # FederationClient + sync_instance
|
|
|
|
|
+│ ├── tests/
|
|
|
|
|
+│ │ └── test_retention.py # Tests pytest (count / daily / GFS)
|
|
|
│ └── templates/
|
|
│ └── templates/
|
|
|
│ ├── base.html
|
|
│ ├── base.html
|
|
|
│ ├── dashboard_local.html
|
|
│ ├── dashboard_local.html
|
|
|
|
|
+│ ├── dashboard_network.html
|
|
|
│ ├── job_form.html
|
|
│ ├── job_form.html
|
|
|
│ ├── job_history.html
|
|
│ ├── job_history.html
|
|
|
|
|
+│ ├── archives.html
|
|
|
|
|
+│ ├── archives_overview.html # Vue globale des archives
|
|
|
│ ├── restore_confirm.html
|
|
│ ├── restore_confirm.html
|
|
|
│ ├── settings.html
|
|
│ ├── settings.html
|
|
|
-│ └── destinations.html
|
|
|
|
|
|
|
+│ ├── destinations.html
|
|
|
|
|
+│ ├── remote_instances.html
|
|
|
|
|
+│ ├── remote_instance_form.html
|
|
|
|
|
+│ └── federation.html
|
|
|
└── doc/
|
|
└── doc/
|
|
|
```
|
|
```
|
|
|
|
|
|
|
@@ -78,18 +97,27 @@ backupmanager_ynh/
|
|
|
```sql
|
|
```sql
|
|
|
-- Jobs de sauvegarde
|
|
-- Jobs de sauvegarde
|
|
|
CREATE TABLE jobs (
|
|
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,
|
|
|
|
|
- destination_id INTEGER REFERENCES destinations(id),
|
|
|
|
|
- created_at DATETIME,
|
|
|
|
|
- updated_at DATETIME
|
|
|
|
|
|
|
+ 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" ou "" (manuel uniquement)
|
|
|
|
|
+ retention_mode TEXT NOT NULL, -- count|daily|gfs
|
|
|
|
|
+ retention_value INTEGER NOT NULL,
|
|
|
|
|
+ retention_gfs_config TEXT, -- JSON {"daily":N,"weekly":M,"monthly":P}
|
|
|
|
|
+ enabled BOOLEAN DEFAULT 1,
|
|
|
|
|
+ core_only BOOLEAN DEFAULT 0,
|
|
|
|
|
+ created_at DATETIME,
|
|
|
|
|
+ updated_at DATETIME
|
|
|
|
|
+);
|
|
|
|
|
+
|
|
|
|
|
+-- Relation many-to-many Job ↔ Destination
|
|
|
|
|
+CREATE TABLE job_destinations (
|
|
|
|
|
+ id INTEGER PRIMARY KEY,
|
|
|
|
|
+ job_id INTEGER REFERENCES jobs(id) ON DELETE CASCADE,
|
|
|
|
|
+ dest_type TEXT NOT NULL, -- ssh | instance
|
|
|
|
|
+ dest_id INTEGER, -- FK → destinations.id (si ssh)
|
|
|
|
|
+ instance_id INTEGER -- FK → remote_instances.id (si instance)
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
-- Destinations de transfert SSH/rsync
|
|
-- Destinations de transfert SSH/rsync
|
|
@@ -100,7 +128,7 @@ CREATE TABLE destinations (
|
|
|
port INTEGER DEFAULT 22,
|
|
port INTEGER DEFAULT 22,
|
|
|
user TEXT NOT NULL DEFAULT 'root',
|
|
user TEXT NOT NULL DEFAULT 'root',
|
|
|
remote_path TEXT NOT NULL,
|
|
remote_path TEXT NOT NULL,
|
|
|
- key_name TEXT, -- fichier clé dans data_dir/keys/
|
|
|
|
|
|
|
+ key_name TEXT, -- fichier clé dans data_dir/keys/
|
|
|
enabled BOOLEAN DEFAULT 1,
|
|
enabled BOOLEAN DEFAULT 1,
|
|
|
created_at DATETIME
|
|
created_at DATETIME
|
|
|
);
|
|
);
|
|
@@ -111,26 +139,26 @@ CREATE TABLE runs (
|
|
|
job_id INTEGER REFERENCES jobs(id),
|
|
job_id INTEGER REFERENCES jobs(id),
|
|
|
started_at DATETIME,
|
|
started_at DATETIME,
|
|
|
finished_at DATETIME,
|
|
finished_at DATETIME,
|
|
|
- status TEXT, -- running|success|error
|
|
|
|
|
- log_text TEXT, -- préfixé [RESTAURATION] si restauration
|
|
|
|
|
|
|
+ status TEXT, -- running|success|warning|error
|
|
|
|
|
+ log_text TEXT, -- préfixé [RESTAURATION] si restauration
|
|
|
archive_name TEXT,
|
|
archive_name TEXT,
|
|
|
size_bytes INTEGER
|
|
size_bytes INTEGER
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
-- Paramètres SMTP et notifications
|
|
-- Paramètres SMTP et notifications
|
|
|
CREATE TABLE settings (
|
|
CREATE TABLE settings (
|
|
|
- key TEXT PRIMARY KEY, -- smtp_host, smtp_port, smtp_user, ...
|
|
|
|
|
|
|
+ key TEXT PRIMARY KEY, -- smtp_host, smtp_port, smtp_user, …
|
|
|
value TEXT NOT NULL DEFAULT ''
|
|
value TEXT NOT NULL DEFAULT ''
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
--- Instances distantes enregistrées (Phase 3)
|
|
|
|
|
|
|
+-- Instances distantes enregistrées (fédération)
|
|
|
CREATE TABLE remote_instances (
|
|
CREATE TABLE remote_instances (
|
|
|
id INTEGER PRIMARY KEY,
|
|
id INTEGER PRIMARY KEY,
|
|
|
- name TEXT NOT NULL, -- ex: "tom"
|
|
|
|
|
- url TEXT NOT NULL, -- https://tom.domaine.fr
|
|
|
|
|
|
|
+ name TEXT NOT NULL, -- ex: "tom"
|
|
|
|
|
+ url TEXT NOT NULL, -- https://tom.domaine.fr
|
|
|
api_key TEXT NOT NULL,
|
|
api_key TEXT NOT NULL,
|
|
|
last_seen DATETIME,
|
|
last_seen DATETIME,
|
|
|
- status TEXT, -- online|offline|error
|
|
|
|
|
|
|
+ status TEXT, -- online|offline|error
|
|
|
created_at DATETIME
|
|
created_at DATETIME
|
|
|
);
|
|
);
|
|
|
|
|
|
|
@@ -138,23 +166,25 @@ CREATE TABLE remote_instances (
|
|
|
CREATE TABLE remote_runs (
|
|
CREATE TABLE remote_runs (
|
|
|
id INTEGER PRIMARY KEY,
|
|
id INTEGER PRIMARY KEY,
|
|
|
instance_id INTEGER REFERENCES remote_instances(id),
|
|
instance_id INTEGER REFERENCES remote_instances(id),
|
|
|
|
|
+ job_id INTEGER,
|
|
|
job_name TEXT,
|
|
job_name TEXT,
|
|
|
|
|
+ job_type TEXT,
|
|
|
last_run_at DATETIME,
|
|
last_run_at DATETIME,
|
|
|
last_status TEXT,
|
|
last_status TEXT,
|
|
|
- archive_count INTEGER,
|
|
|
|
|
|
|
+ last_archive_name TEXT,
|
|
|
last_size_bytes INTEGER
|
|
last_size_bytes INTEGER
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
-- Transferts chunked en cours
|
|
-- Transferts chunked en cours
|
|
|
CREATE TABLE uploads (
|
|
CREATE TABLE uploads (
|
|
|
- upload_id TEXT PRIMARY KEY, -- uuid4
|
|
|
|
|
|
|
+ upload_id TEXT PRIMARY KEY, -- uuid4
|
|
|
filename TEXT,
|
|
filename TEXT,
|
|
|
total_size INTEGER,
|
|
total_size INTEGER,
|
|
|
chunk_size INTEGER,
|
|
chunk_size INTEGER,
|
|
|
chunks_received INTEGER DEFAULT 0,
|
|
chunks_received INTEGER DEFAULT 0,
|
|
|
- checksum TEXT, -- SHA256
|
|
|
|
|
|
|
+ checksum TEXT, -- SHA256
|
|
|
started_at DATETIME,
|
|
started_at DATETIME,
|
|
|
- status TEXT -- pending|in_progress|complete|error
|
|
|
|
|
|
|
+ status TEXT -- pending|in_progress|complete|error
|
|
|
);
|
|
);
|
|
|
```
|
|
```
|
|
|
|
|
|
|
@@ -170,14 +200,16 @@ BACKUP_CORE_ONLY=1 yunohost backup create --apps nextcloud --name <archive_name>
|
|
|
```json
|
|
```json
|
|
|
{ "app_id": "nocodb", "core_only": false }
|
|
{ "app_id": "nocodb", "core_only": false }
|
|
|
```
|
|
```
|
|
|
|
|
+Le nom du job est auto-rempli depuis le label de l'application YunoHost sélectionnée.
|
|
|
|
|
|
|
|
### `ynh_system`
|
|
### `ynh_system`
|
|
|
```bash
|
|
```bash
|
|
|
-yunohost backup create --system --name <archive_name>
|
|
|
|
|
|
|
+yunohost backup create --system [hook1 hook2 …] --name <archive_name>
|
|
|
```
|
|
```
|
|
|
```json
|
|
```json
|
|
|
-{ "parts": [] }
|
|
|
|
|
|
|
+{ "hooks": ["conf_nginx", "conf_ssowat"] }
|
|
|
```
|
|
```
|
|
|
|
|
+Les hooks système disponibles sont listés dynamiquement. La liste vide déclenche une sauvegarde système complète.
|
|
|
|
|
|
|
|
### `custom_dir`
|
|
### `custom_dir`
|
|
|
```json
|
|
```json
|
|
@@ -217,30 +249,42 @@ jerry_hermes-agent_20260508.info.json # SÉPARÉ hors tar, requis YNH
|
|
|
|
|
|
|
|
## 6. Rétention
|
|
## 6. Rétention
|
|
|
|
|
|
|
|
|
|
+La rétention est appliquée **après chaque backup réussi**, localement et sur chaque destination. Les échecs de suppression sont loggés explicitement dans le run (plus de silence silencieux).
|
|
|
|
|
+
|
|
|
### Mode `count`
|
|
### Mode `count`
|
|
|
```
|
|
```
|
|
|
-→ Garde les N dernières archives
|
|
|
|
|
-→ Supprime les plus anciennes
|
|
|
|
|
|
|
+→ Garde les N dernières archives par job
|
|
|
|
|
+→ Supprime les plus anciennes au-delà de N
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
### Mode `daily` — fenêtre glissante
|
|
### Mode `daily` — fenêtre glissante
|
|
|
```
|
|
```
|
|
|
-retention_value: 7
|
|
|
|
|
-→ 1 archive par jour sur les 7 derniers jours
|
|
|
|
|
-→ Fenêtre glissante J-1 à J-7
|
|
|
|
|
-→ Toute archive > J-7 supprimée
|
|
|
|
|
|
|
+retention_value: 30
|
|
|
|
|
+→ 1 archive par jour sur les 30 derniers jours
|
|
|
|
|
+→ Fenêtre glissante : toute archive > J-30 supprimée
|
|
|
|
|
+→ Doublons du même jour éliminés (garde le plus récent)
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### Mode `gfs` *(Phase 4)*
|
|
|
|
|
-```
|
|
|
|
|
-daily: 7 / weekly: 4 / monthly: 3
|
|
|
|
|
|
|
+### Mode `gfs` — Grandfather-Father-Son
|
|
|
|
|
+```json
|
|
|
|
|
+{ "daily": 7, "weekly": 4, "monthly": 12 }
|
|
|
```
|
|
```
|
|
|
|
|
+- **Fils** : conserve les N archives les plus récentes
|
|
|
|
|
+- **Père** : conserve 1 archive par semaine (la plus récente) sur M semaines
|
|
|
|
|
+- **Grand-Père** : conserve 1 archive par mois (la plus récente) sur P mois
|
|
|
|
|
+- Une archive peut satisfaire plusieurs niveaux simultanément
|
|
|
|
|
+
|
|
|
|
|
+### Rétention distante SSH
|
|
|
|
|
+Après chaque transfert rsync SSH réussi, la même politique est appliquée sur le serveur distant via SSH (`find` pour lister, `rm -f` pour supprimer). Les échecs de connexion ou de suppression sont loggés sans bloquer le job.
|
|
|
|
|
+
|
|
|
|
|
+### Rétention distante instance fédérée
|
|
|
|
|
+Après chaque push HTTP chunked, la rétention est appliquée via l'API REST de l'instance distante (`DELETE /api/v1/archives/<name>`).
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 7. Nommage des archives
|
|
## 7. Nommage des archives
|
|
|
|
|
|
|
|
-**Format :** `{instance}_{app}_{YYYYMMDD}.tar`
|
|
|
|
|
|
|
+**Format :** `{instance}_{label}_{YYYYMMDD}.tar`
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
jerry_nocodb_20260508.tar
|
|
jerry_nocodb_20260508.tar
|
|
@@ -250,22 +294,35 @@ jerry_hermes-agent_20260508.tar
|
|
|
jerry_mysql_mabase_20260508.tar
|
|
jerry_mysql_mabase_20260508.tar
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+Si le nom du jour existe déjà (backup manuel + automatique le même jour), un suffixe est ajouté : `jerry_nextcloud_20260508_2.tar`.
|
|
|
|
|
+
|
|
|
**`backup_info.json` embarqué dans le tar :**
|
|
**`backup_info.json` embarqué dans le tar :**
|
|
|
```json
|
|
```json
|
|
|
{
|
|
{
|
|
|
"instance_name": "jerry",
|
|
"instance_name": "jerry",
|
|
|
"instance_url": "https://jerry.mondomaine.fr",
|
|
"instance_url": "https://jerry.mondomaine.fr",
|
|
|
- "app": "nocodb",
|
|
|
|
|
"type": "ynh_app",
|
|
"type": "ynh_app",
|
|
|
"created_at": "2026-05-08T03:00:00",
|
|
"created_at": "2026-05-08T03:00:00",
|
|
|
- "backupmanager_version": "1.0.0",
|
|
|
|
|
- "yunohost_version": "12.1.17"
|
|
|
|
|
|
|
+ "backupmanager_version": "1.0.0"
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 8. Intégration YunoHost
|
|
|
|
|
|
|
+## 8. Destinations (many-to-many)
|
|
|
|
|
+
|
|
|
|
|
+Un job peut avoir **plusieurs destinations** : aucune (local uniquement), une ou plusieurs SSH, une ou plusieurs instances fédérées, ou toute combinaison.
|
|
|
|
|
+
|
|
|
|
|
+Après chaque backup réussi :
|
|
|
|
|
+1. Rétention locale appliquée
|
|
|
|
|
+2. Pour chaque destination dans l'ordre :
|
|
|
|
|
+ - Transfert (rsync SSH ou HTTP chunked)
|
|
|
|
|
+ - Rétention distante appliquée sur cette destination
|
|
|
|
|
+ - Log intermédiaire persisté en base après chaque étape (checkpoint)
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## 9. Intégration YunoHost
|
|
|
|
|
|
|
|
| Outil YNH | Usage |
|
|
| Outil YNH | Usage |
|
|
|
|---|---|
|
|
|---|---|
|
|
@@ -288,55 +345,52 @@ auth_header = false
|
|
|
protected = true
|
|
protected = true
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
+**Sudoers :** les commandes autorisées couvrent `yunohost backup`, `mysqldump`, `pg_dump`, `stat`, `find`, `rsync`, `tar`, `rm`, `mkdir`, `chown`, `chmod`, `useradd`, `systemctl`.
|
|
|
|
|
+
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 9. API REST
|
|
|
|
|
|
|
+## 10. API REST
|
|
|
|
|
|
|
|
Tous les endpoints protégés par `X-BackupManager-Key`.
|
|
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
|
|
|
|
|
|
|
+GET /api/v1/health
|
|
|
|
|
+GET /api/v1/summary
|
|
|
|
|
+GET /api/v1/jobs
|
|
|
|
|
+GET /api/v1/jobs/<id>/runs
|
|
|
|
|
+POST /api/v1/jobs/<id>/run
|
|
|
|
|
+GET /api/v1/running
|
|
|
|
|
+GET /api/v1/archives
|
|
|
|
|
+GET /api/v1/archives/<name>/info
|
|
|
DELETE /api/v1/archives/<name>
|
|
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
|
|
|
|
|
|
|
+GET /api/v1/archives/<name>/download
|
|
|
|
|
+GET /api/v1/archives/<name>/info-json-download
|
|
|
|
|
+POST /api/v1/archives/<name>/restore
|
|
|
|
|
+GET /api/v1/archives/<name>/restore/status
|
|
|
|
|
+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>
|
|
DELETE /api/v1/archives/upload/<id>
|
|
|
-POST /api/v1/archives/<name>/restore # Phase 3
|
|
|
|
|
-GET /api/v1/archives/<name>/restore/status # Phase 3
|
|
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 10. Transfert inter-instances
|
|
|
|
|
|
|
+## 11. Transfert inter-instances
|
|
|
|
|
|
|
|
-### HTTP Chunked (défaut)
|
|
|
|
|
|
|
+### HTTP Chunked (fédération)
|
|
|
```
|
|
```
|
|
|
Chunks : 50 MB | Reprise : upload_id SQLite | Vérif : SHA256 | Transport : HTTPS
|
|
Chunks : 50 MB | Reprise : upload_id SQLite | Vérif : SHA256 | Transport : HTTPS
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### SSH/rsync (optionnel, activable par instance)
|
|
|
|
|
|
|
+### SSH/rsync (destinations)
|
|
|
```bash
|
|
```bash
|
|
|
-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/
|
|
|
|
|
|
|
+ssh-keygen -t ed25519 -f $data_dir/keys/dest_<name>_ed25519 -N "" -C "backupmanager@<dest>"
|
|
|
|
|
+rsync -az -e "ssh -i $key -p $port" archive.tar archive.info.json user@host:/remote/path/
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-### Sélection automatique
|
|
|
|
|
-| Situation | Mode |
|
|
|
|
|
-|---|---|
|
|
|
|
|
-| SSH non configuré | HTTP chunked |
|
|
|
|
|
-| SSH + archive < 2 GB | HTTP chunked |
|
|
|
|
|
-| SSH + archive > 2 GB | rsync auto |
|
|
|
|
|
-| Override manuel | Au choix |
|
|
|
|
|
-
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 11. Restauration assistée
|
|
|
|
|
|
|
+## 12. Restauration assistée
|
|
|
|
|
|
|
|
| Action | Dashboard BM | Webadmin YNH |
|
|
| Action | Dashboard BM | Webadmin YNH |
|
|
|
|---|---|---|
|
|
|---|---|---|
|
|
@@ -348,27 +402,27 @@ rsync -az -e "ssh -i $key -p $port" archive.tar archive.info.json user@host:/hom
|
|
|
| Permissions | ✅ `chown/chmod` | ❌ |
|
|
| Permissions | ✅ `chown/chmod` | ❌ |
|
|
|
| Post-restore commands | ✅ | ❌ |
|
|
| Post-restore commands | ✅ | ❌ |
|
|
|
| MySQL / PostgreSQL | ✅ | ❌ |
|
|
| MySQL / PostgreSQL | ✅ | ❌ |
|
|
|
-| Port firewall | ✅ `yunohost firewall` | ❌ |
|
|
|
|
|
-| Instance distante | ✅ Phase 3 | ❌ |
|
|
|
|
|
|
|
+| Instance distante | ✅ | ❌ |
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 12. Sécurité
|
|
|
|
|
|
|
+## 13. Sécurité
|
|
|
|
|
|
|
|
| Élément | Mesure |
|
|
| Élément | Mesure |
|
|
|
|---|---|
|
|
|---|---|
|
|
|
| Dashboard | SSO YunoHost admins |
|
|
| Dashboard | SSO YunoHost admins |
|
|
|
| Token API | `secrets.token_hex(32)`, hashé bcrypt |
|
|
| Token API | `secrets.token_hex(32)`, hashé bcrypt |
|
|
|
| Clé SSH | ed25519, `$data_dir/keys/`, permissions `600` |
|
|
| Clé SSH | ed25519, `$data_dir/keys/`, permissions `600` |
|
|
|
-| Credentials DB | Jamais en clair, config protégée |
|
|
|
|
|
|
|
+| Credentials DB | Jamais en clair, config protégée (`chmod 600`) |
|
|
|
| Logs | Sans credentials |
|
|
| Logs | Sans credentials |
|
|
|
| Inter-instances | HTTPS obligatoire |
|
|
| Inter-instances | HTTPS obligatoire |
|
|
|
|
|
+| sudo_rm | Vérifie le code retour, logue les échecs explicitement |
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 13. Phases de développement
|
|
|
|
|
|
|
+## 14. Phases de développement
|
|
|
|
|
|
|
|
-### Phase 1 — MVP local
|
|
|
|
|
|
|
+### Phase 1 — MVP local ✅
|
|
|
- [x] manifest.toml, install, remove, nginx, systemd
|
|
- [x] manifest.toml, install, remove, nginx, systemd
|
|
|
- [x] Flask + SQLite + APScheduler
|
|
- [x] Flask + SQLite + APScheduler
|
|
|
- [x] Jobs ynh_app et ynh_system
|
|
- [x] Jobs ynh_app et ynh_system
|
|
@@ -388,79 +442,60 @@ rsync -az -e "ssh -i $key -p $port" archive.tar archive.info.json user@host:/hom
|
|
|
- [x] Accès archives root-owned via sudo (stat/find/tar/rsync)
|
|
- [x] Accès archives root-owned via sudo (stat/find/tar/rsync)
|
|
|
|
|
|
|
|
### Phase 3 — Fédération ✅ (testé VPS 2026-05-10)
|
|
### Phase 3 — Fédération ✅ (testé VPS 2026-05-10)
|
|
|
-**Sous-phases :**
|
|
|
|
|
-- **3A** — Fondations : DB (RemoteInstance, RemoteRun, Upload) + API REST complète
|
|
|
|
|
-- **3B** — Instances distantes : UI enregistrement + test connexion + sync état
|
|
|
|
|
-- **3C** — Dashboard réseau : vue agrégée multi-instances
|
|
|
|
|
-- **3D** — Transfert HTTP chunked : push/pull archives entre instances
|
|
|
|
|
-- **3E** — Contrôle distant : déclencher backup/restauration sur instance distante
|
|
|
|
|
-
|
|
|
|
|
-**Avancement :**
|
|
|
|
|
-- [x] 3A — Modèles DB RemoteInstance / RemoteRun / Upload
|
|
|
|
|
-- [x] 3A — API : /summary, /archives/<name>/info, /archives/<name>/restore (+status), upload chunked
|
|
|
|
|
-- [x] 3A — API : /archives/<name>/download, /archives/<name>/info-json-download
|
|
|
|
|
-- [x] 3B — UI instances distantes (liste, ajout, édition, suppression, test, sync)
|
|
|
|
|
-- [x] 3B — federation/client.py (FederationClient + sync_instance)
|
|
|
|
|
-- [x] 3C — Dashboard réseau (vue agrégée locale + distante avec statuts)
|
|
|
|
|
-- [x] 3D — Push archive HTTP chunked (sha256 + reprise upload_id)
|
|
|
|
|
-- [x] 3D — Pull dernière archive d'un job distant + .info.json
|
|
|
|
|
-- [x] 3E — Lancer un job sur instance distante depuis le dashboard réseau
|
|
|
|
|
-- [x] 3E — Token API affiché dans les Paramètres + URL instance
|
|
|
|
|
-
|
|
|
|
|
-**Notes techniques Phase 3 :**
|
|
|
|
|
-- sudo rsync crée des fichiers /tmp owned root → cleanup via sudo rm -rf (sudoers)
|
|
|
|
|
-- Pull : récupère archive_name via get_job_runs() pour toujours avoir la dernière version
|
|
|
|
|
-- Runs bloqués "en cours" nettoyés toutes les heures par APScheduler (> 6h → error)
|
|
|
|
|
|
|
+- [x] Modèles DB RemoteInstance / RemoteRun / Upload
|
|
|
|
|
+- [x] API REST complète (/summary, /archives, /restore, upload chunked, /running)
|
|
|
|
|
+- [x] UI instances distantes (liste, ajout, édition, suppression, test, sync)
|
|
|
|
|
+- [x] FederationClient + sync_instance
|
|
|
|
|
+- [x] Dashboard réseau (vue agrégée locale + distante avec statuts)
|
|
|
|
|
+- [x] Push archive HTTP chunked (sha256 + reprise upload_id)
|
|
|
|
|
+- [x] Pull dernière archive d'un job distant + .info.json
|
|
|
|
|
+- [x] Lancer un job sur instance distante depuis le dashboard réseau
|
|
|
|
|
+- [x] Token API affiché dans les Paramètres + URL instance
|
|
|
|
|
+- [x] Rétention distante après push HTTP chunked
|
|
|
|
|
|
|
|
### Phase 3 bis — Refactoring & corrections ✅ (2026-05-10)
|
|
### Phase 3 bis — Refactoring & corrections ✅ (2026-05-10)
|
|
|
-- [x] Découpage app.py (1142 lignes) → 5 blueprints Flask + helpers.py
|
|
|
|
|
- - `blueprints/jobs.py` — Blueprint("jobs") : dashboard, CRUD, restauration, archives
|
|
|
|
|
- - `blueprints/destinations.py` — Blueprint("dest")
|
|
|
|
|
- - `blueprints/network.py` — Blueprint("network") : fédération, push/pull
|
|
|
|
|
- - `blueprints/settings.py` — Blueprint("cfg")
|
|
|
|
|
- - `blueprints/api.py` — Blueprint("api", url_prefix="/api/v1") + auth before_request
|
|
|
|
|
-- [x] Fix : `sudo cat` non autorisé dans sudoers → remplacé par `sudo rsync → open → sudo rm`
|
|
|
|
|
-- [x] Fix : .info.json non rapatrié (500 sur `os.unlink` root-owned) → `finally` avec `sudo rm -rf`
|
|
|
|
|
|
|
+- [x] Découpage app.py → 5 blueprints Flask + helpers.py
|
|
|
|
|
+- [x] Fix : `sudo cat` non autorisé → remplacé par `sudo rsync → open → sudo rm`
|
|
|
|
|
+- [x] Fix : .info.json non rapatrié → `finally` avec `sudo rm -rf`
|
|
|
- [x] Tous les templates mis à jour (`url_for('blueprint.fonction')`)
|
|
- [x] Tous les templates mis à jour (`url_for('blueprint.fonction')`)
|
|
|
|
|
|
|
|
-**Notes techniques :**
|
|
|
|
|
-- Threads background : `app = current_app._get_current_object()` avant le thread, `with app.app_context()` dans le thread
|
|
|
|
|
-- API auth scoped au blueprint via `@bp.before_request` (pas de filtre global)
|
|
|
|
|
-
|
|
|
|
|
### Phase 4 — Refonte UI ✅ (2026-05-10)
|
|
### Phase 4 — Refonte UI ✅ (2026-05-10)
|
|
|
-- [x] Navigateur d'archives `/archives` — tableau filtrable (nom/type/statut), actions Restaurer · Pousser · Télécharger · Supprimer ; listing via `sudo_listdir`
|
|
|
|
|
-- [x] Barre d'activité sticky — bandeau sous le header, polling `/api/v1/running` toutes les 5 s, disparaît si idle ; rechargement auto au terme d'un job
|
|
|
|
|
-- [x] Endpoint `GET /api/v1/running` — retourne les runs locaux en status=running
|
|
|
|
|
-- [x] Boutons unifiés — 4 classes CSS (`btn-primary/secondary/ghost/danger`) en cartouche `rounded-full` avec couleurs distinctes ; balise `<style type="text/tailwindcss">` (CDN)
|
|
|
|
|
-- [x] Navigation simplifiée — 3 liens : Dashboard · Archives · Paramètres
|
|
|
|
|
-- [x] Dashboard home — colonne "Transfert" (destination rsync ou Local), "Prochaine exéc." supprimée ; section "Serveurs fédérés" en dessous avec jobs distants + Lancer + Rapatrier
|
|
|
|
|
-- [x] Paramètres multi-onglets — Destinations (1er) · Instances · Configuration ; bouton contextuel selon onglet actif
|
|
|
|
|
-- [x] Icône application — favicon + logo navbar + `doc/LOGO.png` pour le webadmin YunoHost
|
|
|
|
|
-
|
|
|
|
|
-### Phase 5 — Finitions
|
|
|
|
|
-- [ ] Rétention GFS (daily N / weekly N / monthly N)
|
|
|
|
|
-- [ ] Export/import config JSON (jobs, destinations, instances)
|
|
|
|
|
|
|
+- [x] Navigateur d'archives `/archives` — tableau filtrable, actions Restaurer · Pousser · Télécharger · Supprimer
|
|
|
|
|
+- [x] Barre d'activité sticky — polling `/api/v1/running` toutes les 5 s, rechargement auto
|
|
|
|
|
+- [x] Boutons unifiés — 4 classes CSS (`btn-primary/secondary/ghost/danger`)
|
|
|
|
|
+- [x] Navigation simplifiée — Dashboard · Archives · Paramètres
|
|
|
|
|
+- [x] Dashboard home — colonne Transfert, section Serveurs fédérés
|
|
|
|
|
+- [x] Paramètres multi-onglets — Destinations · Instances · Configuration
|
|
|
|
|
+- [x] Icône application — favicon + logo navbar
|
|
|
|
|
+
|
|
|
|
|
+### Phase 5 — Consolidation ✅ (2026-07-17)
|
|
|
|
|
+- [x] Rétention GFS (Grandfather-Father-Son : daily/weekly/monthly)
|
|
|
|
|
+- [x] Export/import configuration JSON (jobs, destinations, instances, SMTP)
|
|
|
|
|
+- [x] Tests automatisés pytest — rétention count/daily/GFS (17 tests)
|
|
|
|
|
+- [x] Destinations multiples par job (many-to-many : SSH + instances combinables)
|
|
|
|
|
+- [x] Sélection des hooks système pour les jobs ynh_system
|
|
|
|
|
+- [x] Auto-remplissage du nom depuis le label de l'app YunoHost sélectionnée
|
|
|
|
|
+- [x] Logs intermédiaires persistés après chaque étape (checkpoint en base)
|
|
|
|
|
+- [x] Fix rétention silencieuse : `sudo_rm` vérifie le code retour, échecs loggés
|
|
|
|
|
+- [x] Rétention distante SSH : après chaque transfert rsync, nettoyage via SSH
|
|
|
|
|
+- [x] Vue globale des archives `/overview` : local + SSH + instances, suppression manuelle, rétention à la demande
|
|
|
|
|
+
|
|
|
|
|
+### En cours / À venir
|
|
|
- [ ] Script backup/restore de l'app pour YNH (SQLite + clés SSH)
|
|
- [ ] Script backup/restore de l'app pour YNH (SQLite + clés SSH)
|
|
|
-- [ ] Tests automatisés (pytest)
|
|
|
|
|
|
|
+- [ ] Pagination de l'historique des runs
|
|
|
|
|
+- [ ] Notifications Gotify / Matrix (en plus de SMTP)
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-## 14. Feuille de route UI
|
|
|
|
|
-
|
|
|
|
|
-*Réalisé le 2026-05-10 — toutes les propositions initiales implémentées (avec adaptations).*
|
|
|
|
|
-
|
|
|
|
|
-### A — Navigateur d'archives ✅
|
|
|
|
|
-Page `/archives` avec tableau filtrable (nom, type, statut), listing via `sudo_listdir`, actions par ligne : Restaurer · Pousser (dropdown instances) · Télécharger · Supprimer.
|
|
|
|
|
-
|
|
|
|
|
-### B — Barre d'activité globale ✅
|
|
|
|
|
-Bandeau sticky sous le header, toujours visible pendant la navigation. Polling `/api/v1/running` toutes les 5 s. Affiche chaque activité : type · nom · durée. Rechargement automatique de la page au terme d'un job pour mettre à jour les statuts.
|
|
|
|
|
-
|
|
|
|
|
-### C — Navigation revue ✅
|
|
|
|
|
-Navbar réduite à 3 liens : **Dashboard** · **Archives** · **Paramètres**. Les instances distantes sont dans `Paramètres > Instances`. Le dashboard home intègre directement les serveurs fédérés.
|
|
|
|
|
|
|
+## 15. Notes techniques
|
|
|
|
|
|
|
|
-### D — Serveurs fédérés sur la page d'accueil ✅
|
|
|
|
|
-Section "Serveurs fédérés" sous le tableau des jobs locaux : statut, liste des jobs distants avec statut/taille, boutons Lancer et Rapatrier. Bouton "Synchroniser tout" en en-tête.
|
|
|
|
|
|
|
+- **Threads APScheduler** : `app = current_app._get_current_object()` avant le thread, `with app.app_context()` dans le thread
|
|
|
|
|
+- **API auth** : scoped au blueprint via `@bp.before_request` (pas de filtre global)
|
|
|
|
|
+- **sudo rsync** : crée des fichiers `/tmp` owned root → cleanup via `sudo rm -rf` (sudoers)
|
|
|
|
|
+- **Runs bloqués** : nettoyés toutes les heures par APScheduler (> 6h → status error)
|
|
|
|
|
+- **Migration SQLite** : colonnes ajoutées à chaud via `ALTER TABLE` dans `app.py` au démarrage
|
|
|
|
|
+- **Rétention SSH** : utilise `find -printf '%f\t%s\n'` (GNU find, disponible sur Debian/Ubuntu)
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
-*backupmanager_ynh — CDC v4.5 — Avancement mis à jour le 2026-05-10 | Phase 1 ✅ | Phase 2 ✅ | Phase 3 ✅ | Phase 3bis ✅ | Phase 4 ✅ | Phase 5 en cours*
|
|
|
|
|
|
|
+*backupmanager_ynh — CDC v5.0 — 2026-07-17 | Phase 1 ✅ | Phase 2 ✅ | Phase 3 ✅ | Phase 3bis ✅ | Phase 4 ✅ | Phase 5 ✅*
|