diff --git a/lxc_manager.sh b/lxc_manager.sh index 1fafe9a..da118ce 100644 --- a/lxc_manager.sh +++ b/lxc_manager.sh @@ -2,7 +2,7 @@ ############################################# # Script de Gestion LXC pour Proxmox VE -# Date: 18/12/2025 +# Auteur: Généré par Claude # Description: Gestion complète des conteneurs LXC ############################################# @@ -14,8 +14,12 @@ 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 @@ -59,15 +63,76 @@ validate_positive_number() { 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.0 - Sécurisé ║" + 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 @@ -163,22 +228,28 @@ create_container() { # Stockage disponible echo -e "\n${BLUE}Stockages disponibles:${NC}" - pvesm status | grep -E "^(local|local-lvm)" || pvesm status + 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 - read -rp "Stockage pour le conteneur (ex: local-lvm): " storage + local storage_choice + read -rp "Choisir le stockage pour le conteneur (défaut: 1): " storage_choice + storage_choice=${storage_choice:-1} - if [[ -z "$storage" ]]; then - error_exit "Le stockage ne peut pas être vide" + if [[ ! "$storage_choice" =~ ^[0-9]+$ ]] || [[ $storage_choice -lt 1 ]] || [[ $storage_choice -gt ${#storages_list[@]} ]]; then + error_exit "Choix invalide" return 1 fi - # Vérifier que le stockage existe - if ! pvesm status | grep -q "^${storage}"; then - error_exit "Le stockage '$storage' n'existe pas" - return 1 - fi + local storage="${storages_list[$((storage_choice-1))]}" # Template disponible echo -e "\n${BLUE}Templates disponibles:${NC}" @@ -290,6 +361,12 @@ create_container() { 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}" @@ -302,7 +379,8 @@ create_container() { --cores "$cores" \ --net0 "$net_config" \ --onboot "$onboot" \ - --unprivileged 1; then + --unprivileged 1 \ + $protection_flag; then echo -e "${GREEN}✓ Conteneur $vmid créé avec succès!${NC}" @@ -430,15 +508,41 @@ delete_container() { 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 - 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 + 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 @@ -507,20 +611,54 @@ backup_local() { fi # Stockage disponible pour backup - echo -e "\n${BLUE}Stockages disponibles:${NC}" - pvesm status | grep -v "^Disk" + 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 - read -rp "Stockage pour la sauvegarde (défaut: local): " dumpdir - dumpdir=${dumpdir:-local} + local dumpdir_choice + read -rp "Choisir le stockage pour la sauvegarde (défaut: 1): " dumpdir_choice + dumpdir_choice=${dumpdir_choice:-1} - # Vérifier que le stockage existe - if ! pvesm status | grep -q "^${dumpdir}"; then - error_exit "Le stockage '$dumpdir' n'existe pas" + 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)" @@ -568,19 +706,69 @@ restore_backup() { show_logo echo -e "${GREEN}Restauration d'une sauvegarde${NC}\n" - # Lister les sauvegardes disponibles - echo -e "${BLUE}Sauvegardes disponibles:${NC}" - pvesm list local | grep "vzdump" || echo "Aucune sauvegarde trouvée dans 'local'" - echo "" + # Lister tous les stockages disponibles + echo -e "${BLUE}Stockages disponibles:${NC}" + mapfile -t storages < <(pvesm status | awk 'NR>1 {print $1}') - local backup_file - read -rp "Nom complet du fichier de sauvegarde: " backup_file - - if [[ -z "$backup_file" ]]; then - error_exit "Le nom du fichier ne peut pas être vide" + 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}' | sed "s/^${selected_storage}://") + + 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 (sans le préfixe storage:) + local filename=$(basename "$backup_name") + local vmid_backup=$(echo "$filename" | grep -oP 'vzdump-lxc-\K[0-9]+' || echo "N/A") + local date_backup=$(echo "$filename" | 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} $filename" + 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 @@ -597,19 +785,30 @@ restore_backup() { break done - local storage - read -rp "Stockage pour le conteneur (défaut: local-lvm): " storage - storage=${storage:-local-lvm} + # 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 "" - # Vérifier que le stockage existe - if ! pvesm status | grep -q "^${storage}"; then - error_exit "Le stockage '$storage' n'existe pas" + 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 - echo -e "\n${YELLOW}Restauration en cours...${NC}" + local restore_storage="${storages[$((restore_storage_choice-1))]}" - if pct restore "$new_vmid" "local:backup/${backup_file}" --storage "$storage"; then + echo -e "\n${YELLOW}Restauration en cours...${NC}" + echo -e "${BLUE}Source:${NC} ${selected_storage}:${selected_backup}" + echo -e "${BLUE}Destination:${NC} VMID $new_vmid sur $restore_storage" + echo "" + + if pct restore "$new_vmid" "${selected_storage}:${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" @@ -644,6 +843,14 @@ show_container_info() { 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 "" @@ -701,6 +908,46 @@ clone_container() { 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 @@ -733,12 +980,104 @@ clone_container() { 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}" - if pct clone "$source_vmid" "$new_vmid" --hostname "$new_hostname"; then + # 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 - error_exit "Échec du clonage" + 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..." @@ -761,6 +1100,12 @@ main_menu() { 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 "" @@ -780,6 +1125,8 @@ main_menu() { 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