From 84a120e8eb155329f330ef9d1df25afa299a042b Mon Sep 17 00:00:00 2001 From: Johnny Date: Tue, 30 Dec 2025 08:18:59 +0000 Subject: [PATCH] Ajouter lxc-manager.sh --- lxc-manager.sh | 1142 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1142 insertions(+) create mode 100644 lxc-manager.sh diff --git a/lxc-manager.sh b/lxc-manager.sh new file mode 100644 index 0000000..01240e7 --- /dev/null +++ b/lxc-manager.sh @@ -0,0 +1,1142 @@ +#!/bin/bash + +############################################# +# Script de Gestion LXC pour Proxmox VE +# Auteur: Généré par Claude +# Description: Gestion complète des conteneurs LXC +############################################# + +# Sécurité stricte +set -euo pipefail + +# Couleurs pour l'affichage +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly MAGENTA='\033[0;35m' +readonly NC='\033[0m' # No Color + +# Variable globale pour la protection +PROTECTION_ENABLED=true + +# Fonction pour gérer les erreurs +error_exit() { + echo -e "${RED}Erreur: $1${NC}" >&2 + read -p "Appuyez sur Entrée pour continuer..." + return 1 +} + +# Fonction pour valider un VMID +validate_vmid() { + local vmid="$1" + if [[ ! "$vmid" =~ ^[1-9][0-9]{2,8}$ ]]; then + return 1 + fi + return 0 +} + +# Fonction pour valider un nom d'hôte +validate_hostname() { + local hostname="$1" + if [[ ! "$hostname" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$ ]]; then + return 1 + fi + return 0 +} + +# Fonction pour valider une adresse IP +validate_ip() { + local ip="$1" + if [[ ! "$ip" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then + return 1 + fi + return 0 +} + +# Fonction pour valider un nombre positif +validate_positive_number() { + local num="$1" + if [[ ! "$num" =~ ^[1-9][0-9]*$ ]]; then + return 1 + fi + return 0 +} + +# Fonction pour vérifier si un conteneur a la protection activée +check_container_protection() { + local vmid="$1" + local protection_status + protection_status=$(pct config "$vmid" | grep "^protection:" | awk '{print $2}') + + if [[ "$protection_status" == "1" ]]; then + return 0 # Protection activée + else + return 1 # Protection désactivée + fi +} + +# Fonction pour activer/désactiver la protection d'un conteneur +toggle_protection() { + show_logo + list_containers + + local vmid + read -rp "Numéro du conteneur: " vmid + + if ! validate_vmid "$vmid"; then + error_exit "VMID invalide" + return 1 + fi + + if ! container_exists "$vmid"; then + error_exit "Le conteneur $vmid n'existe pas" + return 1 + fi + + echo -e "\n${BLUE}État actuel de la protection:${NC}" + if check_container_protection "$vmid"; then + echo -e "${GREEN}✓ Protection activée${NC}" + echo "" + read -rp "Désactiver la protection? (o/n): " choice + + if [[ "$choice" == "o" ]]; then + pct set "$vmid" --protection 0 + echo -e "${YELLOW}✓ Protection désactivée pour le conteneur $vmid${NC}" + fi + else + echo -e "${YELLOW}✗ Protection désactivée${NC}" + echo "" + read -rp "Activer la protection? (o/n): " choice + + if [[ "$choice" == "o" ]]; then + pct set "$vmid" --protection 1 + echo -e "${GREEN}✓ Protection activée pour le conteneur $vmid${NC}" + fi + fi + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour afficher le logo +show_logo() { + clear + echo -e "${BLUE}" + echo "╔═══════════════════════════════════════════════╗" + echo "║ GESTIONNAIRE LXC PROXMOX VE ║" + echo "║ Version 2.1 - Sécurisé ║" + echo "╚═══════════════════════════════════════════════╝" + echo -e "${NC}" + + if [[ "$PROTECTION_ENABLED" == true ]]; then + echo -e "${GREEN}🔒 Mode protection: ACTIVÉ${NC}\n" + else + echo -e "${YELLOW}🔓 Mode protection: DÉSACTIVÉ${NC}\n" + fi +} + +# Fonction pour vérifier si on est sur Proxmox +check_proxmox() { + if ! command -v pct &> /dev/null; then + echo -e "${RED}Erreur: Ce script doit être exécuté sur un serveur Proxmox VE${NC}" + exit 1 + fi + + if [[ $EUID -ne 0 ]]; then + echo -e "${RED}Erreur: Ce script doit être exécuté en tant que root${NC}" + exit 1 + fi +} + +# Fonction pour lister les conteneurs +list_containers() { + echo -e "${BLUE}═══════════════════════════════════════════════${NC}" + echo -e "${GREEN}Liste des conteneurs LXC:${NC}" + echo -e "${BLUE}═══════════════════════════════════════════════${NC}" + pct list || error_exit "Impossible de lister les conteneurs" + echo "" +} + +# Fonction pour vérifier l'existence d'un conteneur +container_exists() { + local vmid="$1" + pct status "$vmid" &>/dev/null +} + +# Fonction pour créer un conteneur +create_container() { + show_logo + echo -e "${GREEN}Création d'un nouveau conteneur LXC${NC}\n" + + # Liste des conteneurs existants + list_containers + + # Demande et validation du VMID + local vmid + while true; do + read -rp "Entrez le numéro du conteneur (VMID, 100-999999999): " vmid + + if ! validate_vmid "$vmid"; then + echo -e "${RED}Erreur: VMID invalide (doit être entre 100 et 999999999)${NC}" + continue + fi + + if container_exists "$vmid"; then + echo -e "${RED}Erreur: Le VMID $vmid existe déjà${NC}" + continue + fi + break + done + + # Nom du conteneur avec validation + local hostname + while true; do + read -rp "Nom du conteneur (ex: web-server): " hostname + + if [[ -z "$hostname" ]]; then + echo -e "${RED}Erreur: Le nom ne peut pas être vide${NC}" + continue + fi + + if ! validate_hostname "$hostname"; then + echo -e "${RED}Erreur: Nom invalide (lettres, chiffres, tirets uniquement)${NC}" + continue + fi + break + done + + # Mot de passe root avec validation + local password password_confirm + while true; do + read -rsp "Mot de passe root (min 8 caractères): " password + echo "" + + if [[ ${#password} -lt 8 ]]; then + echo -e "${RED}Erreur: Le mot de passe doit contenir au moins 8 caractères${NC}" + continue + fi + + read -rsp "Confirmez le mot de passe: " password_confirm + echo "" + + if [[ "$password" != "$password_confirm" ]]; then + echo -e "${RED}Erreur: Les mots de passe ne correspondent pas${NC}" + continue + fi + break + done + + # Stockage disponible + echo -e "\n${BLUE}Stockages disponibles:${NC}" + mapfile -t storages_list < <(pvesm status | awk 'NR>1 {print $1}') + + if [[ ${#storages_list[@]} -eq 0 ]]; then + error_exit "Aucun stockage disponible" + return 1 + fi + + for i in "${!storages_list[@]}"; do + echo "$((i+1))) ${storages_list[$i]}" + done + echo "" + + local storage_choice + read -rp "Choisir le stockage pour le conteneur (défaut: 1): " storage_choice + storage_choice=${storage_choice:-1} + + if [[ ! "$storage_choice" =~ ^[0-9]+$ ]] || [[ $storage_choice -lt 1 ]] || [[ $storage_choice -gt ${#storages_list[@]} ]]; then + error_exit "Choix invalide" + return 1 + fi + + local storage="${storages_list[$((storage_choice-1))]}" + + # Template disponible + echo -e "\n${BLUE}Templates disponibles:${NC}" + pveam available | grep -i "system" | head -10 + echo "" + + local template + read -rp "Template à utiliser (ex: debian-12-standard): " template + + if [[ -z "$template" ]]; then + error_exit "Le template ne peut pas être vide" + return 1 + fi + + # Si le template n'est pas téléchargé + if ! pveam list "$storage" | grep -q "$template"; then + echo -e "${YELLOW}Téléchargement du template...${NC}" + pveam download "$storage" "$template" || { + error_exit "Échec du téléchargement du template" + return 1 + } + fi + + # Ressources avec validation + local memory + while true; do + read -rp "RAM en MB (défaut: 512): " memory + memory=${memory:-512} + + if validate_positive_number "$memory"; then + break + fi + echo -e "${RED}Erreur: Valeur invalide${NC}" + done + + local swap + while true; do + read -rp "Swap en MB (défaut: 512): " swap + swap=${swap:-512} + + if validate_positive_number "$swap"; then + break + fi + echo -e "${RED}Erreur: Valeur invalide${NC}" + done + + local disk + while true; do + read -rp "Espace disque en GB (défaut: 8): " disk + disk=${disk:-8} + + if validate_positive_number "$disk"; then + break + fi + echo -e "${RED}Erreur: Valeur invalide${NC}" + done + + local cores + while true; do + read -rp "Nombre de CPU cores (défaut: 1): " cores + cores=${cores:-1} + + if validate_positive_number "$cores"; then + break + fi + echo -e "${RED}Erreur: Valeur invalide${NC}" + done + + # Bridge réseau avec validation + local bridge + read -rp "Bridge réseau (défaut: vmbr0): " bridge + bridge=${bridge:-vmbr0} + + if [[ ! "$bridge" =~ ^vmbr[0-9]+$ ]]; then + error_exit "Bridge invalide (format: vmbr0, vmbr1, etc.)" + return 1 + fi + + # Configuration IP + echo -e "\n${BLUE}Configuration réseau:${NC}" + echo "1) DHCP" + echo "2) IP statique" + + local net_choice + read -rp "Choix: " net_choice + + local net_config + if [[ "$net_choice" == "2" ]]; then + local ip gateway + + while true; do + read -rp "Adresse IP/CIDR (ex: 192.168.1.100/24): " ip + + if validate_ip "$ip"; then + break + fi + echo -e "${RED}Erreur: Format IP invalide${NC}" + done + + read -rp "Passerelle: " gateway + net_config="name=eth0,bridge=${bridge},ip=${ip},gw=${gateway}" + else + net_config="name=eth0,bridge=${bridge},ip=dhcp" + fi + + # Démarrage automatique + local autostart onboot + read -rp "Démarrage automatique? (o/n, défaut: n): " autostart + onboot="0" + [[ "$autostart" == "o" ]] && onboot="1" + + # Protection + local protection + read -rp "Activer la protection contre la suppression? (o/n, défaut: n): " protection + local protection_flag="" + [[ "$protection" == "o" ]] && protection_flag="--protection 1" + + # Création du conteneur + echo -e "\n${YELLOW}Création du conteneur en cours...${NC}" + + if pct create "$vmid" "${storage}:vztmpl/${template}" \ + --hostname "$hostname" \ + --password "$password" \ + --memory "$memory" \ + --swap "$swap" \ + --rootfs "${storage}:${disk}" \ + --cores "$cores" \ + --net0 "$net_config" \ + --onboot "$onboot" \ + --unprivileged 1 \ + $protection_flag; then + + echo -e "${GREEN}✓ Conteneur $vmid créé avec succès!${NC}" + + local start + read -rp "Démarrer le conteneur maintenant? (o/n): " start + if [[ "$start" == "o" ]]; then + pct start "$vmid" && echo -e "${GREEN}✓ Conteneur démarré${NC}" + fi + else + error_exit "Échec de la création du conteneur" + fi + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour démarrer un conteneur +start_container() { + show_logo + list_containers + + local vmid + read -rp "Numéro du conteneur à démarrer: " vmid + + if ! validate_vmid "$vmid"; then + error_exit "VMID invalide" + return 1 + fi + + if ! container_exists "$vmid"; then + error_exit "Le conteneur $vmid n'existe pas" + return 1 + fi + + echo -e "${YELLOW}Démarrage du conteneur $vmid...${NC}" + + if pct start "$vmid"; then + echo -e "${GREEN}✓ Conteneur $vmid démarré avec succès${NC}" + else + error_exit "Échec du démarrage" + fi + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour arrêter un conteneur +stop_container() { + show_logo + list_containers + + local vmid + read -rp "Numéro du conteneur à arrêter: " vmid + + if ! validate_vmid "$vmid"; then + error_exit "VMID invalide" + return 1 + fi + + if ! container_exists "$vmid"; then + error_exit "Le conteneur $vmid n'existe pas" + return 1 + fi + + echo "1) Arrêt normal (shutdown)" + echo "2) Arrêt forcé (stop)" + + local choice + read -rp "Choix: " choice + + if [[ "$choice" == "1" ]]; then + echo -e "${YELLOW}Arrêt normal du conteneur $vmid...${NC}" + pct shutdown "$vmid" || error_exit "Échec de l'arrêt" + else + echo -e "${YELLOW}Arrêt forcé du conteneur $vmid...${NC}" + pct stop "$vmid" || error_exit "Échec de l'arrêt forcé" + fi + + echo -e "${GREEN}✓ Conteneur $vmid arrêté${NC}" + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour redémarrer un conteneur +reboot_container() { + show_logo + list_containers + + local vmid + read -rp "Numéro du conteneur à redémarrer: " vmid + + if ! validate_vmid "$vmid"; then + error_exit "VMID invalide" + return 1 + fi + + if ! container_exists "$vmid"; then + error_exit "Le conteneur $vmid n'existe pas" + return 1 + fi + + echo -e "${YELLOW}Redémarrage du conteneur $vmid...${NC}" + + if pct reboot "$vmid"; then + echo -e "${GREEN}✓ Conteneur $vmid redémarré${NC}" + else + error_exit "Échec du redémarrage" + fi + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour supprimer un conteneur +delete_container() { + show_logo + list_containers + + local vmid + read -rp "Numéro du conteneur à supprimer: " vmid + + if ! validate_vmid "$vmid"; then + error_exit "VMID invalide" + return 1 + fi + + if ! container_exists "$vmid"; then + error_exit "Le conteneur $vmid n'existe pas" + return 1 + fi + + # Vérifier si la protection est activée + if check_container_protection "$vmid"; then + echo -e "${RED}⚠ ATTENTION: Ce conteneur a la protection activée!${NC}" + echo -e "${YELLOW}La protection empêche la suppression accidentelle.${NC}\n" + + read -rp "Désactiver la protection et continuer? (o/n): " disable_protection + + if [[ "$disable_protection" == "o" ]]; then + pct set "$vmid" --protection 0 + echo -e "${YELLOW}✓ Protection désactivée${NC}\n" + else + echo -e "${YELLOW}Suppression annulée${NC}" + read -rp "Appuyez sur Entrée pour continuer..." + return + fi + fi + + echo -e "${RED}⚠ ATTENTION: Cette action est irréversible!${NC}" + + local confirm + if [[ "$PROTECTION_ENABLED" == true ]]; then + read -rp "Tapez exactement 'SUPPRIMER' pour confirmer: " confirm + + if [[ "$confirm" != "SUPPRIMER" ]]; then + echo -e "${YELLOW}Suppression annulée${NC}" + read -rp "Appuyez sur Entrée pour continuer..." + return + fi + else + read -rp "Confirmer la suppression? (o/n): " confirm + if [[ "$confirm" != "o" ]]; then + echo -e "${YELLOW}Suppression annulée${NC}" + read -rp "Appuyez sur Entrée pour continuer..." + return + fi + fi + + # Vérifier si le conteneur est en cours d'exécution + if pct status "$vmid" | grep -q "running"; then + echo -e "${YELLOW}Arrêt du conteneur en cours...${NC}" + pct stop "$vmid" || true + sleep 2 + fi + + echo -e "${YELLOW}Suppression du conteneur $vmid...${NC}" + + if pct destroy "$vmid" --purge; then + echo -e "${GREEN}✓ Conteneur $vmid supprimé avec succès${NC}" + else + error_exit "Échec de la suppression" + fi + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour déverrouiller un conteneur +unlock_container() { + show_logo + list_containers + + local vmid + read -rp "Numéro du conteneur à déverrouiller: " vmid + + if ! validate_vmid "$vmid"; then + error_exit "VMID invalide" + return 1 + fi + + if ! container_exists "$vmid"; then + error_exit "Le conteneur $vmid n'existe pas" + return 1 + fi + + echo -e "${YELLOW}Déverrouillage du conteneur $vmid...${NC}" + + if pct unlock "$vmid"; then + echo -e "${GREEN}✓ Conteneur $vmid déverrouillé${NC}" + else + error_exit "Échec du déverrouillage" + fi + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour backup local +backup_local() { + show_logo + list_containers + + local vmid + read -rp "Numéro du conteneur à sauvegarder: " vmid + + if ! validate_vmid "$vmid"; then + error_exit "VMID invalide" + return 1 + fi + + if ! container_exists "$vmid"; then + error_exit "Le conteneur $vmid n'existe pas" + return 1 + fi + + # Stockage disponible pour backup + echo -e "\n${BLUE}Stockages disponibles pour les sauvegardes:${NC}" + + # Filtrer uniquement les stockages avec content type "backup" ou "vztmpl,backup" + mapfile -t storages_backup < <(pvesm status -content backup 2>/dev/null | awk 'NR>1 {print $1}') + + if [[ ${#storages_backup[@]} -eq 0 ]]; then + # Fallback: essayer de lister tous les stockages actifs + echo -e "${YELLOW}Aucun stockage spécifique pour backup trouvé, affichage de tous les stockages...${NC}" + mapfile -t storages_backup < <(pvesm status | awk 'NR>1 && $2=="active" {print $1}') + + if [[ ${#storages_backup[@]} -eq 0 ]]; then + error_exit "Aucun stockage disponible" + return 1 + fi + fi + + for i in "${!storages_backup[@]}"; do + # Afficher les informations du stockage + local storage_info=$(pvesm status | grep "^${storages_backup[$i]}" | awk '{print $1" ("$3")"}') + echo "$((i+1))) $storage_info" + done + echo "" + echo -e "${YELLOW}Note: Seuls les stockages configurés pour les backups sont affichés${NC}" + echo "" + + local dumpdir_choice + read -rp "Choisir le stockage pour la sauvegarde (défaut: 1): " dumpdir_choice + dumpdir_choice=${dumpdir_choice:-1} + + if [[ ! "$dumpdir_choice" =~ ^[0-9]+$ ]] || [[ $dumpdir_choice -lt 1 ]] || [[ $dumpdir_choice -gt ${#storages_backup[@]} ]]; then + error_exit "Choix invalide" + return 1 + fi + + local dumpdir="${storages_backup[$((dumpdir_choice-1))]}" + + # Vérifier que le stockage accepte bien les backups + echo -e "${BLUE}Vérification du stockage...${NC}" + if ! pvesm status -content backup 2>/dev/null | grep -q "^${dumpdir}"; then + echo -e "${YELLOW}⚠ Avertissement: Ce stockage pourrait ne pas être configuré pour les backups${NC}" + read -rp "Continuer quand même? (o/n): " continue_backup + if [[ "$continue_backup" != "o" ]]; then + echo -e "${YELLOW}Sauvegarde annulée${NC}" + read -rp "Appuyez sur Entrée pour continuer..." + return + fi + fi + + # Type de compression + echo -e "\n${BLUE}Type de compression:${NC}" + echo "1) gzip (rapide, compression moyenne)" + echo "2) lzo (très rapide, faible compression)" + echo "3) zstd (bon compromis vitesse/compression)" + + local compress_choice compress + read -rp "Choix (défaut: 1): " compress_choice + + case $compress_choice in + 2) compress="lzo";; + 3) compress="zstd";; + *) compress="gzip";; + esac + + # Mode de backup + echo -e "\n${BLUE}Mode de sauvegarde:${NC}" + echo "1) Snapshot (plus rapide, nécessite ZFS/LVM)" + echo "2) Stop (arrêt du conteneur)" + echo "3) Suspend (suspension temporaire)" + + local mode_choice mode + read -rp "Choix (défaut: 1): " mode_choice + + case $mode_choice in + 2) mode="stop";; + 3) mode="suspend";; + *) mode="snapshot";; + esac + + echo -e "\n${YELLOW}Création de la sauvegarde...${NC}" + + if vzdump "$vmid" --storage "$dumpdir" --mode "$mode" --compress "$compress"; then + echo -e "${GREEN}✓ Sauvegarde créée avec succès${NC}" + echo -e "${BLUE}Emplacement: $dumpdir${NC}" + else + error_exit "Échec de la sauvegarde" + fi + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour restaurer un backup +restore_backup() { + show_logo + echo -e "${GREEN}Restauration d'une sauvegarde${NC}\n" + + # Lister tous les stockages disponibles + echo -e "${BLUE}Stockages disponibles:${NC}" + mapfile -t storages < <(pvesm status | awk 'NR>1 {print $1}') + + if [[ ${#storages[@]} -eq 0 ]]; then + error_exit "Aucun stockage disponible" + return 1 + fi + + for i in "${!storages[@]}"; do + echo "$((i+1))) ${storages[$i]}" + done + echo "" + + # Sélection du stockage source + local storage_choice + read -rp "Choisir le stockage contenant les sauvegardes (défaut: 1): " storage_choice + storage_choice=${storage_choice:-1} + + if [[ ! "$storage_choice" =~ ^[0-9]+$ ]] || [[ $storage_choice -lt 1 ]] || [[ $storage_choice -gt ${#storages[@]} ]]; then + error_exit "Choix invalide" + return 1 + fi + + local selected_storage="${storages[$((storage_choice-1))]}" + + # Lister les sauvegardes disponibles sur ce stockage + echo -e "\n${BLUE}Sauvegardes disponibles sur '$selected_storage':${NC}" + + mapfile -t backups < <(pvesm list "$selected_storage" | grep "vzdump" | awk '{print $1}') + + if [[ ${#backups[@]} -eq 0 ]]; then + error_exit "Aucune sauvegarde trouvée sur '$selected_storage'" + return 1 + fi + + # Afficher les sauvegardes avec numéros + for i in "${!backups[@]}"; do + local backup_name="${backups[$i]}" + # Extraire les informations du nom de fichier + local vmid_backup=$(echo "$backup_name" | grep -oP 'vzdump-lxc-\K[0-9]+' || echo "N/A") + local date_backup=$(echo "$backup_name" | grep -oP '\d{4}_\d{2}_\d{2}-\d{2}_\d{2}_\d{2}' || echo "") + + echo -e "$((i+1))) ${MAGENTA}VMID:${NC} $vmid_backup ${MAGENTA}Date:${NC} $date_backup" + echo -e " ${BLUE}Fichier:${NC} $backup_name" + echo "" + done + + # Sélection de la sauvegarde + local backup_choice + read -rp "Choisir la sauvegarde à restaurer (1-${#backups[@]}): " backup_choice + + if [[ ! "$backup_choice" =~ ^[0-9]+$ ]] || [[ $backup_choice -lt 1 ]] || [[ $backup_choice -gt ${#backups[@]} ]]; then + error_exit "Choix invalide" + return 1 + fi + + local selected_backup="${backups[$((backup_choice-1))]}" + + echo -e "\n${GREEN}Sauvegarde sélectionnée:${NC} $selected_backup" + + # Nouveau VMID + local new_vmid + while true; do + read -rp "Nouveau VMID pour la restauration: " new_vmid + + if ! validate_vmid "$new_vmid"; then + echo -e "${RED}VMID invalide${NC}" + continue + fi + + if container_exists "$new_vmid"; then + echo -e "${RED}Erreur: Le VMID $new_vmid existe déjà${NC}" + continue + fi + break + done + + # Stockage pour le conteneur restauré + echo -e "\n${BLUE}Stockages disponibles pour la restauration:${NC}" + for i in "${!storages[@]}"; do + echo "$((i+1))) ${storages[$i]}" + done + echo "" + + local restore_storage_choice + read -rp "Choisir le stockage de destination (défaut: 1): " restore_storage_choice + restore_storage_choice=${restore_storage_choice:-1} + + if [[ ! "$restore_storage_choice" =~ ^[0-9]+$ ]] || [[ $restore_storage_choice -lt 1 ]] || [[ $restore_storage_choice -gt ${#storages[@]} ]]; then + error_exit "Choix invalide" + return 1 + fi + + local restore_storage="${storages[$((restore_storage_choice-1))]}" + + echo -e "\n${YELLOW}Restauration en cours...${NC}" + echo -e "${BLUE}Source:${NC} $selected_storage:backup/$selected_backup" + echo -e "${BLUE}Destination:${NC} VMID $new_vmid sur $restore_storage" + echo "" + + if pct restore "$new_vmid" "${selected_storage}:backup/${selected_backup}" --storage "$restore_storage"; then + echo -e "${GREEN}✓ Sauvegarde restaurée avec succès sur VMID $new_vmid${NC}" + else + error_exit "Échec de la restauration" + fi + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour afficher les informations d'un conteneur +show_container_info() { + show_logo + list_containers + + local vmid + read -rp "Numéro du conteneur: " vmid + + if ! validate_vmid "$vmid"; then + error_exit "VMID invalide" + return 1 + fi + + if ! container_exists "$vmid"; then + error_exit "Le conteneur $vmid n'existe pas" + return 1 + fi + + echo -e "\n${BLUE}═══════════════════════════════════════════════${NC}" + echo -e "${GREEN}Informations du conteneur $vmid:${NC}" + echo -e "${BLUE}═══════════════════════════════════════════════${NC}\n" + + echo -e "${YELLOW}Statut:${NC}" + pct status "$vmid" || true + echo "" + + echo -e "${YELLOW}Protection:${NC}" + if check_container_protection "$vmid"; then + echo -e "${GREEN}✓ Activée${NC}" + else + echo -e "${YELLOW}✗ Désactivée${NC}" + fi + echo "" + + echo -e "${YELLOW}Configuration:${NC}" + pct config "$vmid" || true + echo "" + + echo -e "${YELLOW}Utilisation des ressources:${NC}" + pct df "$vmid" 2>/dev/null || echo "Conteneur arrêté" + echo "" + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour entrer dans un conteneur +enter_container() { + show_logo + list_containers + + local vmid + read -rp "Numéro du conteneur: " vmid + + if ! validate_vmid "$vmid"; then + error_exit "VMID invalide" + return 1 + fi + + if ! container_exists "$vmid"; then + error_exit "Le conteneur $vmid n'existe pas" + return 1 + fi + + if ! pct status "$vmid" | grep -q "running"; then + error_exit "Le conteneur doit être démarré" + return 1 + fi + + echo -e "${GREEN}Connexion au conteneur $vmid...${NC}" + echo -e "${YELLOW}(Tapez 'exit' pour quitter)${NC}\n" + pct enter "$vmid" || error_exit "Échec de la connexion" +} + +# Fonction pour cloner un conteneur +clone_container() { + show_logo + list_containers + + local source_vmid + read -rp "Numéro du conteneur source: " source_vmid + + if ! validate_vmid "$source_vmid"; then + error_exit "VMID source invalide" + return 1 + fi + + if ! container_exists "$source_vmid"; then + error_exit "Le conteneur $source_vmid n'existe pas" + return 1 + fi + + # Vérifier si le conteneur est en cours d'exécution + local is_running=false + if pct status "$source_vmid" | grep -q "running"; then + is_running=true + echo -e "\n${YELLOW}⚠ Le conteneur source est en cours d'exécution${NC}" + echo -e "${BLUE}Options disponibles:${NC}" + echo "1) Arrêter le conteneur avant le clonage (recommandé)" + echo "2) Créer un snapshot et cloner (plus rapide, nécessite ZFS/LVM)" + echo "3) Annuler" + + local clone_choice + read -rp "Choix: " clone_choice + + case $clone_choice in + 1) + echo -e "\n${YELLOW}Arrêt du conteneur...${NC}" + if ! pct shutdown "$source_vmid"; then + echo -e "${YELLOW}Arrêt normal échoué, arrêt forcé...${NC}" + pct stop "$source_vmid" || { + error_exit "Impossible d'arrêter le conteneur" + return 1 + } + fi + sleep 2 + ;; + 2) + echo -e "\n${YELLOW}Le clonage avec snapshot sera tenté...${NC}" + ;; + 3) + echo -e "${YELLOW}Clonage annulé${NC}" + read -rp "Appuyez sur Entrée pour continuer..." + return + ;; + *) + error_exit "Choix invalide" + return 1 + ;; + esac + fi + + local new_vmid + while true; do + read -rp "Numéro du nouveau conteneur: " new_vmid + + if ! validate_vmid "$new_vmid"; then + echo -e "${RED}VMID invalide${NC}" + continue + fi + + if container_exists "$new_vmid"; then + echo -e "${RED}Erreur: Le VMID $new_vmid existe déjà${NC}" + continue + fi + break + done + + local new_hostname + while true; do + read -rp "Nom du nouveau conteneur: " new_hostname + + if [[ -z "$new_hostname" ]]; then + echo -e "${RED}Le nom ne peut pas être vide${NC}" + continue + fi + + if ! validate_hostname "$new_hostname"; then + echo -e "${RED}Nom invalide${NC}" + continue + fi + break + done + + # Stockage pour le clone + echo -e "\n${BLUE}Stockages disponibles pour le clone:${NC}" + mapfile -t storages_clone < <(pvesm status | awk 'NR>1 {print $1}') + + if [[ ${#storages_clone[@]} -eq 0 ]]; then + error_exit "Aucun stockage disponible" + if [[ "$is_running" == true && "$clone_choice" == "1" ]]; then + pct start "$source_vmid" || true + fi + return 1 + fi + + for i in "${!storages_clone[@]}"; do + echo "$((i+1))) ${storages_clone[$i]}" + done + echo "0) Même stockage que la source" + echo "" + + local target_storage_choice + read -rp "Choix (défaut: 0): " target_storage_choice + target_storage_choice=${target_storage_choice:-0} + + local storage_param="" + if [[ "$target_storage_choice" != "0" ]]; then + if [[ ! "$target_storage_choice" =~ ^[0-9]+$ ]] || [[ $target_storage_choice -lt 1 ]] || [[ $target_storage_choice -gt ${#storages_clone[@]} ]]; then + error_exit "Choix invalide" + if [[ "$is_running" == true && "$clone_choice" == "1" ]]; then + pct start "$source_vmid" || true + fi + return 1 + fi + + local target_storage="${storages_clone[$((target_storage_choice-1))]}" + storage_param="--storage $target_storage" + fi + + echo -e "\n${YELLOW}Clonage en cours...${NC}" + + # Tenter le clonage avec snapshot si le conteneur est en cours d'exécution + local clone_cmd="pct clone $source_vmid $new_vmid --hostname $new_hostname" + if [[ -n "$storage_param" ]]; then + clone_cmd="$clone_cmd $storage_param" + fi + + # Si le conteneur est en cours d'exécution et qu'on a choisi l'option snapshot + if [[ "$is_running" == true && "$clone_choice" == "2" ]]; then + clone_cmd="$clone_cmd --snapname clone_snapshot" + fi + + if eval "$clone_cmd"; then + echo -e "${GREEN}✓ Conteneur cloné avec succès${NC}" + + # Redémarrer le conteneur source si on l'avait arrêté + if [[ "$is_running" == true && "$clone_choice" == "1" ]]; then + echo -e "\n${YELLOW}Redémarrage du conteneur source...${NC}" + pct start "$source_vmid" && echo -e "${GREEN}✓ Conteneur source redémarré${NC}" + fi + else + echo -e "${RED}✗ Échec du clonage${NC}" + + # Redémarrer le conteneur source si on l'avait arrêté + if [[ "$is_running" == true && "$clone_choice" == "1" ]]; then + echo -e "\n${YELLOW}Redémarrage du conteneur source...${NC}" + pct start "$source_vmid" && echo -e "${GREEN}✓ Conteneur source redémarré${NC}" + fi + + error_exit "Le clonage a échoué" + fi + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Fonction pour basculer le mode protection global +toggle_global_protection() { + show_logo + + if [[ "$PROTECTION_ENABLED" == true ]]; then + echo -e "${GREEN}Mode protection actuellement: ACTIVÉ${NC}" + echo -e "\n${YELLOW}Les confirmations de sécurité sont requises pour les opérations critiques.${NC}" + echo "" + read -rp "Désactiver le mode protection? (o/n): " choice + + if [[ "$choice" == "o" ]]; then + PROTECTION_ENABLED=false + echo -e "${YELLOW}✓ Mode protection DÉSACTIVÉ${NC}" + echo -e "${RED}⚠ Attention: Les opérations destructives ne nécessiteront plus de confirmation renforcée${NC}" + fi + else + echo -e "${YELLOW}Mode protection actuellement: DÉSACTIVÉ${NC}" + echo -e "\n${RED}⚠ Les opérations critiques peuvent être exécutées sans confirmation renforcée.${NC}" + echo "" + read -rp "Activer le mode protection? (o/n): " choice + + if [[ "$choice" == "o" ]]; then + PROTECTION_ENABLED=true + echo -e "${GREEN}✓ Mode protection ACTIVÉ${NC}" + echo -e "${GREEN}Les opérations critiques nécessiteront des confirmations de sécurité${NC}" + fi + fi + + read -rp "Appuyez sur Entrée pour continuer..." +} + +# Menu principal +main_menu() { + while true; do + show_logo + echo -e "${GREEN}Menu Principal:${NC}\n" + echo "1) Lister les conteneurs" + echo "2) Créer un conteneur" + echo "3) Démarrer un conteneur" + echo "4) Arrêter un conteneur" + echo "5) Redémarrer un conteneur" + echo "6) Supprimer un conteneur" + echo "7) Déverrouiller un conteneur" + echo "8) Sauvegarder un conteneur" + echo "9) Restaurer une sauvegarde" + echo "10) Afficher les informations" + echo "11) Entrer dans un conteneur" + echo "12) Cloner un conteneur" + echo -e "13) ${MAGENTA}Gérer la protection d'un conteneur${NC}" + if [[ "$PROTECTION_ENABLED" == true ]]; then + echo -e "14) ${MAGENTA}Mode protection global (ACTIVÉ)${NC}" + else + echo -e "14) ${MAGENTA}Mode protection global (DÉSACTIVÉ)${NC}" + fi + echo "0) Quitter" + echo "" + + local choice + read -rp "Votre choix: " choice + + case $choice in + 1) show_logo; list_containers; read -rp "Appuyez sur Entrée pour continuer...";; + 2) create_container || true;; + 3) start_container || true;; + 4) stop_container || true;; + 5) reboot_container || true;; + 6) delete_container || true;; + 7) unlock_container || true;; + 8) backup_local || true;; + 9) restore_backup || true;; + 10) show_container_info || true;; + 11) enter_container || true;; + 12) clone_container || true;; + 13) toggle_protection || true;; + 14) toggle_global_protection || true;; + 0) echo -e "${GREEN}Au revoir!${NC}"; exit 0;; + *) echo -e "${RED}Choix invalide${NC}"; sleep 2;; + esac + done +} + +# Point d'entrée principal +main() { + check_proxmox + main_menu +} + +# Lancement du script +main \ No newline at end of file