CDC_backupmanager_ynh.md 16 KB

Cahier des charges — backupmanager_ynh

Version 4.1 — 2026-05-09

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 (Job, Run, Destination, Setting)
│   ├── retention.py            # Moteur count/daily/gfs
│   ├── notifications.py        # Email SMTP (succès/erreur)
│   ├── jobs/
│   │   ├── ynh_backup.py       # yunohost backup create/restore
│   │   ├── custom_dir.py       # tar + rsync chemins libres
│   │   ├── db_dump.py          # mysqldump / pg_dump
│   │   ├── transfer.py         # rsync SSH
│   │   └── utils.py            # sudo_exists/getsize/listdir/read_backup_info
│   ├── federation/             # Phase 3 (non commencé)
│   └── templates/
│       ├── base.html
│       ├── dashboard_local.html
│       ├── job_form.html
│       ├── job_history.html
│       ├── restore_confirm.html
│       ├── settings.html
│       └── destinations.html
└── doc/

4. Modèle de données SQLite

-- 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,
  destination_id  INTEGER REFERENCES destinations(id),
  created_at      DATETIME,
  updated_at      DATETIME
);

-- Destinations de transfert SSH/rsync
CREATE TABLE destinations (
  id          INTEGER PRIMARY KEY,
  name        TEXT NOT NULL,
  host        TEXT NOT NULL,
  port        INTEGER DEFAULT 22,
  user        TEXT NOT NULL DEFAULT 'root',
  remote_path TEXT NOT NULL,
  key_name    TEXT,                  -- fichier clé dans data_dir/keys/
  enabled     BOOLEAN DEFAULT 1,
  created_at  DATETIME
);

-- Historique des exécutions (backup ET restauration)
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,               -- préfixé [RESTAURATION] si restauration
  archive_name  TEXT,
  size_bytes    INTEGER
);

-- Paramètres SMTP et notifications
CREATE TABLE settings (
  key   TEXT PRIMARY KEY,           -- smtp_host, smtp_port, smtp_user, ...
  value TEXT NOT NULL DEFAULT ''
);

-- Instances distantes enregistrées (Phase 3)
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

yunohost 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_system

yunohost 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" }

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 :

{
  "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) :

[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)

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 ✅ (testé VPS 2026-05-09)

  • Jobs custom_dir (exclusions + format YNH compatible)
  • Jobs mysql et postgresql
  • Restauration complète custom_dir, mysql, postgresql, ynh_app, ynh_system
  • Restaurations tracées dans l'historique du job
  • Destinations rsync SSH
  • Transfert automatique post-backup
  • Nommage unique (suffixe _2, _3… si doublon du jour)
  • Notifications email SMTP (succès/erreur)
  • Liste BDD live dans le formulaire (mysql/postgresql)
  • Accès archives root-owned via sudo (stat/find/tar/rsync)

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 :

  • 3A — Modèles DB RemoteInstance / RemoteRun / Upload
  • 3A — API : /summary, /archives//info, /archives//restore (+status), upload chunked
  • 3A — API : /archives//download, /archives//info-json-download
  • 3B — UI instances distantes (liste, ajout, édition, suppression, test, sync)
  • 3B — federation/client.py (FederationClient + sync_instance)
  • 3C — Dashboard réseau (vue agrégée locale + distante avec statuts)
  • 3D — Push archive HTTP chunked (sha256 + reprise upload_id)
  • 3D — Pull dernière archive d'un job distant + .info.json
  • 3E — Lancer un job sur instance distante depuis le dashboard réseau
  • 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)

    Phase 3 bis — Refactoring & corrections ✅ (2026-05-10)

    • 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
    • Fix : sudo cat non autorisé dans sudoers → remplacé par sudo rsync → open → sudo rm
    • Fix : .info.json non rapatrié (500 sur os.unlink root-owned) → finally avec sudo rm -rf
    • 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)

    • Navigateur d'archives /archives — tableau filtrable (nom/type/statut), actions Restaurer · Pousser · Télécharger · Supprimer ; listing via sudo_listdir
    • 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
    • Endpoint GET /api/v1/running — retourne les runs locaux en status=running
    • Boutons unifiés — 4 classes CSS (btn-primary/secondary/ghost/danger) en cartouche rounded-full avec couleurs distinctes ; balise <style type="text/tailwindcss"> (CDN)
    • Navigation simplifiée — 3 liens : Dashboard · Archives · Paramètres
    • 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
    • Paramètres multi-onglets — Destinations (1er) · Instances · Configuration ; bouton contextuel selon onglet actif
    • 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)
    • Script backup/restore de l'app pour YNH (SQLite + clés SSH)
    • Tests automatisés (pytest)

    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.

    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.


    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