| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 |
- import os
- import re
- import subprocess
- def generate_key(dest_name, data_dir):
- """Génère une paire de clés ed25519 pour cette destination si elle n'existe pas encore."""
- key_name = f"dest_{_slugify(dest_name)}_ed25519"
- os.makedirs(os.path.join(data_dir, "keys"), exist_ok=True)
- key_path = os.path.join(data_dir, "keys", key_name)
- if not os.path.exists(key_path):
- subprocess.run(
- [
- "ssh-keygen", "-t", "ed25519",
- "-f", key_path,
- "-N", "",
- "-C", f"backupmanager@{dest_name}",
- ],
- check=True,
- capture_output=True,
- )
- os.chmod(key_path, 0o600)
- return key_name
- def get_public_key(key_name, data_dir):
- """Retourne la clé publique à copier dans authorized_keys sur le serveur distant."""
- pub_path = os.path.join(data_dir, "keys", key_name + ".pub")
- try:
- with open(pub_path) as f:
- return f.read().strip()
- except FileNotFoundError:
- return None
- def transfer_archive(archive_name, destination, backup_dir, data_dir):
- """Transfère .tar + .info.json vers la destination via rsync SSH."""
- key_path = os.path.join(data_dir, "keys", destination.key_name)
- if not os.path.exists(key_path):
- raise FileNotFoundError(
- f"Clé SSH introuvable : {key_path}\n"
- "Accédez à la page de la destination pour afficher la clé publique."
- )
- from jobs.utils import sudo_exists
- files = []
- for ext in (".tar", ".info.json"):
- path = os.path.join(backup_dir, archive_name + ext)
- if sudo_exists(path):
- files.append(path)
- if not files:
- raise FileNotFoundError(f"Aucun fichier à transférer pour {archive_name}.")
- remote = f"{destination.user}@{destination.host}:{destination.remote_path}/"
- ssh_opts = (
- f"ssh -i {key_path} -p {destination.port}"
- " -o StrictHostKeyChecking=accept-new"
- " -o BatchMode=yes"
- " -o ConnectTimeout=30"
- )
- # sudo car les archives YNH (ynh_app/ynh_system) sont owned root
- cmd = ["sudo", "rsync", "-az", "--stats", "-e", ssh_opts] + files + [remote]
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=7200)
- log = (result.stdout + result.stderr).strip()
- if result.returncode != 0:
- raise RuntimeError(
- f"rsync vers {destination.host} échoué (code {result.returncode}) :\n{log}"
- )
- return log
- def test_connection(destination, data_dir):
- """Teste la connexion SSH vers la destination. Retourne (True, msg) ou (False, msg)."""
- key_path = os.path.join(data_dir, "keys", destination.key_name)
- if not os.path.exists(key_path):
- return False, "Clé SSH non générée. Affichez la destination pour la créer."
- result = subprocess.run(
- [
- "ssh",
- "-i", key_path,
- "-p", str(destination.port),
- "-o", "StrictHostKeyChecking=accept-new",
- "-o", "BatchMode=yes",
- "-o", "ConnectTimeout=10",
- f"{destination.user}@{destination.host}",
- "echo ok",
- ],
- capture_output=True,
- text=True,
- timeout=20,
- )
- if result.returncode == 0:
- return True, f"Connexion réussie vers {destination.host}:{destination.port}."
- return False, result.stderr.strip() or f"Connexion échouée (code {result.returncode})."
- def push_archive_to_instance(archive_name, instance, backup_dir):
- """Pousse .tar + .info.json vers une instance fédérée via HTTP chunked. Retourne un log texte."""
- import hashlib
- from federation.client import FederationClient
- from jobs.utils import sudo_exists
- archive_path = os.path.join(backup_dir, archive_name + ".tar")
- tmp_tar = f"/tmp/backupmanager_push_{archive_name}.tar"
- tmp_info = f"/tmp/backupmanager_push_{archive_name}.info.json"
- try:
- result = subprocess.run(["sudo", "rsync", archive_path, tmp_tar],
- capture_output=True, text=True)
- if result.returncode != 0:
- raise RuntimeError(f"Copie locale échouée : {result.stderr.strip()}")
- total_size = os.path.getsize(tmp_tar)
- sha256 = hashlib.sha256()
- chunk_size = 50 * 1024 * 1024
- with open(tmp_tar, "rb") as f:
- while True:
- data = f.read(65536)
- if not data:
- break
- sha256.update(data)
- checksum = sha256.hexdigest()
- client = FederationClient(instance)
- upload_info = client.upload_start(archive_name + ".tar", total_size, checksum, chunk_size)
- upload_id = upload_info["upload_id"]
- with open(tmp_tar, "rb") as f:
- n = 0
- while True:
- data = f.read(chunk_size)
- if not data:
- break
- client.upload_chunk(upload_id, n, data)
- n += 1
- info_json_content = None
- info_path = os.path.join(backup_dir, archive_name + ".info.json")
- if sudo_exists(info_path):
- r = subprocess.run(["sudo", "rsync", info_path, tmp_info], capture_output=True)
- if r.returncode == 0:
- try:
- with open(tmp_info, "r", encoding="utf-8", errors="replace") as fh:
- info_json_content = fh.read()
- finally:
- subprocess.run(["sudo", "rm", "-rf", tmp_info], capture_output=True)
- client.upload_finish_with_info(upload_id, info_json_content)
- return f"Transfert HTTP chunked vers {instance.name} ({instance.url}) — {total_size // (1024*1024)} Mo en {n} chunks."
- finally:
- if os.path.exists(tmp_tar):
- os.unlink(tmp_tar)
- def _slugify(s):
- return re.sub(r'[^a-z0-9]+', '-', s.lower().strip()).strip('-')
|