transfer.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. import os
  2. import re
  3. import subprocess
  4. def generate_key(dest_name, data_dir):
  5. """Génère une paire de clés ed25519 pour cette destination si elle n'existe pas encore."""
  6. key_name = f"dest_{_slugify(dest_name)}_ed25519"
  7. key_path = os.path.join(data_dir, "keys", key_name)
  8. if not os.path.exists(key_path):
  9. subprocess.run(
  10. [
  11. "ssh-keygen", "-t", "ed25519",
  12. "-f", key_path,
  13. "-N", "",
  14. "-C", f"backupmanager@{dest_name}",
  15. ],
  16. check=True,
  17. capture_output=True,
  18. )
  19. os.chmod(key_path, 0o600)
  20. return key_name
  21. def get_public_key(key_name, data_dir):
  22. """Retourne la clé publique à copier dans authorized_keys sur le serveur distant."""
  23. pub_path = os.path.join(data_dir, "keys", key_name + ".pub")
  24. try:
  25. with open(pub_path) as f:
  26. return f.read().strip()
  27. except FileNotFoundError:
  28. return None
  29. def transfer_archive(archive_name, destination, backup_dir, data_dir):
  30. """Transfère .tar + .info.json vers la destination via rsync SSH."""
  31. key_path = os.path.join(data_dir, "keys", destination.key_name)
  32. if not os.path.exists(key_path):
  33. raise FileNotFoundError(
  34. f"Clé SSH introuvable : {key_path}\n"
  35. "Accédez à la page de la destination pour afficher la clé publique."
  36. )
  37. files = []
  38. for ext in (".tar", ".info.json"):
  39. path = os.path.join(backup_dir, archive_name + ext)
  40. if os.path.exists(path):
  41. files.append(path)
  42. if not files:
  43. raise FileNotFoundError(f"Aucun fichier à transférer pour {archive_name}.")
  44. remote = f"{destination.user}@{destination.host}:{destination.remote_path}/"
  45. ssh_opts = (
  46. f"ssh -i {key_path} -p {destination.port}"
  47. " -o StrictHostKeyChecking=accept-new"
  48. " -o BatchMode=yes"
  49. " -o ConnectTimeout=30"
  50. )
  51. # sudo car les archives YNH (ynh_app/ynh_system) sont owned root
  52. cmd = ["sudo", "rsync", "-az", "--stats", "-e", ssh_opts] + files + [remote]
  53. result = subprocess.run(cmd, capture_output=True, text=True, timeout=7200)
  54. log = (result.stdout + result.stderr).strip()
  55. if result.returncode != 0:
  56. raise RuntimeError(
  57. f"rsync vers {destination.host} échoué (code {result.returncode}) :\n{log}"
  58. )
  59. return log
  60. def test_connection(destination, data_dir):
  61. """Teste la connexion SSH vers la destination. Retourne (True, msg) ou (False, msg)."""
  62. key_path = os.path.join(data_dir, "keys", destination.key_name)
  63. if not os.path.exists(key_path):
  64. return False, "Clé SSH non générée. Affichez la destination pour la créer."
  65. result = subprocess.run(
  66. [
  67. "ssh",
  68. "-i", key_path,
  69. "-p", str(destination.port),
  70. "-o", "StrictHostKeyChecking=accept-new",
  71. "-o", "BatchMode=yes",
  72. "-o", "ConnectTimeout=10",
  73. f"{destination.user}@{destination.host}",
  74. "echo ok",
  75. ],
  76. capture_output=True,
  77. text=True,
  78. timeout=20,
  79. )
  80. if result.returncode == 0:
  81. return True, f"Connexion réussie vers {destination.host}:{destination.port}."
  82. return False, result.stderr.strip() or f"Connexion échouée (code {result.returncode})."
  83. def _slugify(s):
  84. return re.sub(r'[^a-z0-9]+', '-', s.lower().strip()).strip('-')