|
|
@@ -0,0 +1,379 @@
|
|
|
+# Cahier des charges — `backupmanager_ynh`
|
|
|
+**Version 4.0 — Finale | 2026-05-08**
|
|
|
+
|
|
|
+## 1. Vision générale
|
|
|
+
|
|
|
+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"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 2. Décisions clés
|
|
|
+
|
|
|
+| 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 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 3. Structure du dépôt
|
|
|
+
|
|
|
+```
|
|
|
+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/
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 4. Modèle de données SQLite
|
|
|
+
|
|
|
+```sql
|
|
|
+-- 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
|
|
|
+);
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 5. Types de jobs
|
|
|
+
|
|
|
+### `ynh_app`
|
|
|
+```bash
|
|
|
+yunohost backup create --apps <app_id> --name <archive_name>
|
|
|
+BACKUP_CORE_ONLY=1 yunohost backup create --apps nextcloud --name <archive_name>
|
|
|
+```
|
|
|
+```json
|
|
|
+{ "app_id": "nocodb", "core_only": false }
|
|
|
+```
|
|
|
+
|
|
|
+### `ynh_system`
|
|
|
+```bash
|
|
|
+yunohost backup create --system --name <archive_name>
|
|
|
+```
|
|
|
+```json
|
|
|
+{ "parts": [] }
|
|
|
+```
|
|
|
+
|
|
|
+### `custom_dir`
|
|
|
+```json
|
|
|
+{
|
|
|
+ "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`
|
|
|
+```json
|
|
|
+{ "database": "mabase", "user": "mabase_user" }
|
|
|
+```
|
|
|
+
|
|
|
+### `postgresql`
|
|
|
+```json
|
|
|
+{ "database": "mabase", "user": "postgres" }
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 6. Rétention
|
|
|
+
|
|
|
+### Mode `count`
|
|
|
+```
|
|
|
+→ Garde les N dernières archives
|
|
|
+→ Supprime les plus anciennes
|
|
|
+```
|
|
|
+
|
|
|
+### 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
|
|
|
+```
|
|
|
+
|
|
|
+### Mode `gfs` *(Phase 4)*
|
|
|
+```
|
|
|
+daily: 7 / weekly: 4 / monthly: 3
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 7. Nommage des archives
|
|
|
+
|
|
|
+**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 :**
|
|
|
+```json
|
|
|
+{
|
|
|
+ "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"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 8. Intégration YunoHost
|
|
|
+
|
|
|
+| 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`) :**
|
|
|
+```toml
|
|
|
+[resources.permissions.main]
|
|
|
+url = "/"
|
|
|
+allowed = "admins"
|
|
|
+
|
|
|
+[resources.permissions.api]
|
|
|
+url = "/api"
|
|
|
+allowed = "visitors" # sécurisé par token Flask
|
|
|
+auth_header = false
|
|
|
+protected = true
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 9. API REST
|
|
|
+
|
|
|
+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
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 10. Transfert inter-instances
|
|
|
+
|
|
|
+### HTTP Chunked (défaut)
|
|
|
+```
|
|
|
+Chunks : 50 MB | Reprise : upload_id SQLite | Vérif : SHA256 | Transport : HTTPS
|
|
|
+```
|
|
|
+
|
|
|
+### SSH/rsync (optionnel, activable par instance)
|
|
|
+```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/
|
|
|
+```
|
|
|
+
|
|
|
+### 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
|
|
|
+
|
|
|
+| 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 | ❌ |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 12. Sécurité
|
|
|
+
|
|
|
+| É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 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 13. Phases de développement
|
|
|
+
|
|
|
+### Phase 1 — MVP local
|
|
|
+- [ ] manifest.toml, install, remove, nginx, systemd
|
|
|
+- [ ] Flask + SQLite + APScheduler
|
|
|
+- [ ] Jobs ynh_app et ynh_system
|
|
|
+- [ ] Rétention count et daily
|
|
|
+- [ ] Dashboard local (jobs + historique + Run now)
|
|
|
+
|
|
|
+### Phase 2 — Périmètre complet
|
|
|
+- [ ] Jobs custom_dir (exclusions + format YNH compatible)
|
|
|
+- [ ] Jobs mysql et postgresql
|
|
|
+- [ ] Restauration complète custom_dir + DB
|
|
|
+- [ ] Destinations rsync SSH / SFTP
|
|
|
+- [ ] Notifications email
|
|
|
+
|
|
|
+### Phase 3 — Fédération
|
|
|
+- [ ] API REST complète
|
|
|
+- [ ] Enregistrement instances distantes
|
|
|
+- [ ] Dashboard vue réseau
|
|
|
+- [ ] Transfert HTTP chunked + SSH/rsync optionnel
|
|
|
+- [ ] Restauration distante
|
|
|
+
|
|
|
+### Phase 4 — Finitions
|
|
|
+- [ ] Rétention gfs
|
|
|
+- [ ] Export/import config JSON
|
|
|
+- [ ] Script backup/restore de l'app pour YNH
|
|
|
+- [ ] Tests automatisés
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+*backupmanager_ynh — CDC v4.0 — 2026-05-08*
|