ソースを参照

fix: accès archives root — sudo stat/find pour les opérations sur backup_dir

Le répertoire /home/yunohost.backup/archives/ est 750 root, backupmanager
ne peut pas y faire stat()/listdir() directement. Fallback via sudo stat
et sudo find pour toutes les opérations (taille, mtime, listing).

- jobs/utils.py: helpers sudo_getsize, sudo_getmtime, sudo_listdir
- custom_dir.py, ynh_backup.py: sudo_getsize pour la taille des archives
- retention.py: sudo_listdir pour lister les archives
- app.py api_archives: sudo_listdir + sudo_getsize + sudo_getmtime
- conf/sudoers: ajout stat * et find /home/yunohost.backup/archives *

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cédric Hansen 1 日 前
コミット
8ec90bddc3
6 ファイル変更63 行追加6 行削除
  1. 2 0
      conf/sudoers
  2. 4 3
      sources/app.py
  3. 2 1
      sources/jobs/custom_dir.py
  4. 51 0
      sources/jobs/utils.py
  5. 2 1
      sources/jobs/ynh_backup.py
  6. 2 1
      sources/retention.py

+ 2 - 0
conf/sudoers

@@ -10,6 +10,8 @@ __APP__ ALL=(postgres) NOPASSWD: /usr/bin/pg_dump *
 __APP__ ALL=(postgres) NOPASSWD: /usr/bin/psql *
 __APP__ ALL=(postgres) NOPASSWD: /usr/bin/createdb *
 __APP__ ALL=(postgres) NOPASSWD: /usr/bin/dropdb *
+__APP__ ALL=(root) NOPASSWD: /usr/bin/stat *
+__APP__ ALL=(root) NOPASSWD: /usr/bin/find /home/yunohost.backup/archives *
 __APP__ ALL=(root) NOPASSWD: /usr/bin/rsync *
 __APP__ ALL=(root) NOPASSWD: /usr/bin/tar *
 __APP__ ALL=(root) NOPASSWD: /usr/bin/rm -rf /tmp/backupmanager_*

+ 4 - 3
sources/app.py

@@ -508,13 +508,14 @@ def api_archives():
     backup_dir = app.config["YUNOHOST_BACKUP_DIR"]
     archives = []
     try:
-        for fname in sorted(os.listdir(backup_dir)):
+        from jobs.utils import sudo_listdir, sudo_getsize, sudo_getmtime
+        for fname in sorted(sudo_listdir(backup_dir)):
             if fname.endswith(".tar"):
                 path = os.path.join(backup_dir, fname)
                 archives.append({
                     "name": fname[:-4],
-                    "size_bytes": os.path.getsize(path),
-                    "modified_at": datetime.utcfromtimestamp(os.path.getmtime(path)).isoformat(),
+                    "size_bytes": sudo_getsize(path),
+                    "modified_at": datetime.utcfromtimestamp(sudo_getmtime(path)).isoformat(),
                 })
     except OSError:
         pass

+ 2 - 1
sources/jobs/custom_dir.py

@@ -93,7 +93,8 @@ def backup_custom_dir(job, instance, backup_dir):
         )
 
         # .info.json YunoHost (hors tar, pour listing webadmin)
-        size = os.path.getsize(archive_path)
+        from jobs.utils import sudo_getsize
+        size = sudo_getsize(archive_path)
         ynh_info = {
             "created_at": int(time.time()),
             "description": f"BackupManager: custom_dir {source_path}",

+ 51 - 0
sources/jobs/utils.py

@@ -0,0 +1,51 @@
+import os
+import subprocess
+
+
+def sudo_getsize(path):
+    """Retourne la taille d'un fichier via sudo stat si non accessible directement."""
+    try:
+        return os.path.getsize(path)
+    except OSError:
+        result = subprocess.run(
+            ["sudo", "stat", "--format=%s", path],
+            capture_output=True, text=True,
+        )
+        if result.returncode == 0:
+            try:
+                return int(result.stdout.strip())
+            except ValueError:
+                pass
+        return 0
+
+
+def sudo_getmtime(path):
+    """Retourne le mtime d'un fichier via sudo stat si non accessible directement."""
+    try:
+        return os.path.getmtime(path)
+    except OSError:
+        result = subprocess.run(
+            ["sudo", "stat", "--format=%Y", path],
+            capture_output=True, text=True,
+        )
+        if result.returncode == 0:
+            try:
+                return float(result.stdout.strip())
+            except ValueError:
+                pass
+        return 0.0
+
+
+def sudo_listdir(directory):
+    """Liste les fichiers d'un répertoire via sudo find si non accessible directement."""
+    try:
+        return os.listdir(directory)
+    except OSError:
+        result = subprocess.run(
+            ["sudo", "find", directory, "-maxdepth", "1", "-mindepth", "1",
+             "-printf", "%f\n"],
+            capture_output=True, text=True,
+        )
+        if result.returncode == 0:
+            return [f for f in result.stdout.strip().split("\n") if f]
+        return []

+ 2 - 1
sources/jobs/ynh_backup.py

@@ -38,7 +38,8 @@ def execute_job(job_id):
             raise ValueError(f"Type de job non géré : {job.type}")
 
         archive_path = os.path.join(backup_dir, archive_name + ".tar")
-        size_bytes = os.path.getsize(archive_path) if os.path.exists(archive_path) else None
+        from jobs.utils import sudo_getsize
+        size_bytes = sudo_getsize(archive_path) or None
 
         run.status = "success"
         run.archive_name = archive_name

+ 2 - 1
sources/retention.py

@@ -48,8 +48,9 @@ def _list_archives_for_job(job, backup_dir):
     else:
         prefix = f"{instance}_{job.name.lower().replace(' ', '-')}_"
 
+    from jobs.utils import sudo_listdir
     archives = []
-    for fname in os.listdir(backup_dir):
+    for fname in sudo_listdir(backup_dir):
         if fname.startswith(prefix) and fname.endswith(".tar"):
             archives.append(fname)