#!/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 pour le rootfs echo -e "\n${BLUE}Stockages disponibles pour le conteneur:${NC}" mapfile -t storages_rootfs < <(pvesm status | awk 'NR>1 {print $1}') if [[ ${#storages_rootfs[@]} -eq 0 ]]; then error_exit "Aucun stockage disponible" return 1 fi for i in "${!storages_rootfs[@]}"; do local storage_info=$(pvesm status | grep "^${storages_rootfs[$i]}" | awk '{print $1" ("$3")"}') echo "$((i+1))) $storage_info" 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_rootfs[@]} ]]; then error_exit "Choix invalide" return 1 fi local storage="${storages_rootfs[$((storage_choice-1))]}" # Trouver un stockage pour les templates (doit supporter les templates) local template_storage="local" mapfile -t template_storages < <(pvesm status -content vztmpl 2>/dev/null | awk 'NR>1 {print $1}') if [[ ${#template_storages[@]} -gt 0 ]]; then template_storage="${template_storages[0]}" fi # Template disponible avec numérotation echo -e "\n${BLUE}Templates disponibles sur '$template_storage':${NC}" mapfile -t templates < <(pveam available | grep -i "system" | awk '{print $2}' | head -20) if [[ ${#templates[@]} -eq 0 ]]; then error_exit "Aucun template disponible" return 1 fi for i in "${!templates[@]}"; do local template_name="${templates[$i]}" # Extraire l'OS et la version local os_name=$(echo "$template_name" | cut -d'-' -f1) local os_version=$(echo "$template_name" | cut -d'-' -f2 | cut -d'_' -f1) echo "$((i+1))) $os_name $os_version - $template_name" done echo "" local template_choice read -rp "Choisir le template (défaut: 1): " template_choice template_choice=${template_choice:-1} if [[ ! "$template_choice" =~ ^[0-9]+$ ]] || [[ $template_choice -lt 1 ]] || [[ $template_choice -gt ${#templates[@]} ]]; then error_exit "Choix invalide" return 1 fi local template="${templates[$((template_choice-1))]}" # Si le template n'est pas téléchargé if ! pveam list "$template_storage" | grep -q "$template"; then echo -e "${YELLOW}Téléchargement du template sur '$template_storage'...${NC}" pveam download "$template_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 # Mode privilégié ou non echo -e "\n${BLUE}Mode du conteneur:${NC}" echo "1) Unprivileged (recommandé, plus sécurisé)" echo "2) Privileged (accès root complet)" local priv_choice unprivileged read -rp "Choix (défaut: 1): " priv_choice priv_choice=${priv_choice:-1} if [[ "$priv_choice" == "2" ]]; then unprivileged="0" echo -e "${YELLOW}⚠ Mode privileged sélectionné${NC}" else unprivileged="1" fi # Fonctionnalités avancées (nesting, fuse, etc.) echo -e "\n${BLUE}Fonctionnalités avancées:${NC}" local features="" read -rp "Activer nesting (Docker dans LXC)? (o/n, défaut: n): " enable_nesting if [[ "$enable_nesting" == "o" ]]; then features="${features}nesting=1," fi read -rp "Activer FUSE (systèmes de fichiers utilisateur)? (o/n, défaut: n): " enable_fuse if [[ "$enable_fuse" == "o" ]]; then features="${features}fuse=1," fi local mount_list="" read -rp "Activer NFS (montage NFS)? (o/n, défaut: n): " enable_nfs if [[ "$enable_nfs" == "o" ]]; then mount_list="${mount_list}nfs;" fi read -rp "Activer SMB/CIFS (montage Windows)? (o/n, défaut: n): " enable_cifs if [[ "$enable_cifs" == "o" ]]; then mount_list="${mount_list}cifs;" fi # Retirer le séparateur final mount_list="${mount_list%;}" # Ajouter mount aux features si nécessaire if [[ -n "$mount_list" ]]; then features="${features}mount=${mount_list}," fi # Retirer la virgule finale features="${features%,}" local features_param="" if [[ -n "$features" ]]; then features_param="--features $features" 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" "${template_storage}:vztmpl/${template}" \ --hostname "$hostname" \ --password "$password" \ --memory "$memory" \ --swap "$swap" \ --rootfs "${storage}:${disk}" \ --cores "$cores" \ --net0 "$net_config" \ --onboot "$onboot" \ --unprivileged "$unprivileged" \ $features_param \ $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}' | 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 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}:${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" 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 modifier les options d'un conteneur modify_container_options() { show_logo list_containers local vmid read -rp "Numéro du conteneur à modifier: " 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 le conteneur est arrêté if pct status "$vmid" | grep -q "running"; then echo -e "${YELLOW}⚠ Le conteneur est en cours d'exécution${NC}" echo -e "${YELLOW}Certaines modifications nécessitent un redémarrage${NC}\n" fi # Afficher les options actuelles echo -e "\n${BLUE}Configuration actuelle:${NC}" local current_config=$(pct config "$vmid" | grep -E "^(features|mount):") echo -e "${GREEN}${current_config:-Aucune fonctionnalité activée}${NC}" echo "" # Extraire les options actuelles depuis features local current_features=$(pct config "$vmid" | grep "^features:" | cut -d' ' -f2-) local current_nesting="0" local current_fuse="0" local current_nfs="0" local current_cifs="0" [[ "$current_features" =~ nesting=1 ]] && current_nesting="1" [[ "$current_features" =~ fuse=1 ]] && current_fuse="1" [[ "$current_features" =~ mount=.*nfs ]] && current_nfs="1" [[ "$current_features" =~ mount=.*cifs ]] && current_cifs="1" echo -e "${BLUE}Modification des fonctionnalités avancées:${NC}\n" # Nesting echo -e "Nesting (Docker dans LXC) - Actuellement: $([ "$current_nesting" == "1" ] && echo -e "${GREEN}activé${NC}" || echo -e "${YELLOW}désactivé${NC}")" read -rp "Modifier? (o/activer, n/désactiver, Entrée/garder): " mod_nesting case $mod_nesting in o) current_nesting="1";; n) current_nesting="0";; esac # FUSE echo -e "FUSE (systèmes de fichiers utilisateur) - Actuellement: $([ "$current_fuse" == "1" ] && echo -e "${GREEN}activé${NC}" || echo -e "${YELLOW}désactivé${NC}")" read -rp "Modifier? (o/activer, n/désactiver, Entrée/garder): " mod_fuse case $mod_fuse in o) current_fuse="1";; n) current_fuse="0";; esac # NFS echo -e "NFS (montage NFS) - Actuellement: $([ "$current_nfs" == "1" ] && echo -e "${GREEN}activé${NC}" || echo -e "${YELLOW}désactivé${NC}")" read -rp "Modifier? (o/activer, n/désactiver, Entrée/garder): " mod_nfs case $mod_nfs in o) current_nfs="1";; n) current_nfs="0";; esac # CIFS echo -e "SMB/CIFS (montage Windows) - Actuellement: $([ "$current_cifs" == "1" ] && echo -e "${GREEN}activé${NC}" || echo -e "${YELLOW}désactivé${NC}")" read -rp "Modifier? (o/activer, n/désactiver, Entrée/garder): " mod_cifs case $mod_cifs in o) current_cifs="1";; n) current_cifs="0";; esac # Construire la chaîne de fonctionnalités complète local new_features="" # Ajouter nesting et fuse [[ "$current_nesting" == "1" ]] && new_features="${new_features}nesting=1," [[ "$current_fuse" == "1" ]] && new_features="${new_features}fuse=1," # Ajouter mount si nécessaire local mount_list="" [[ "$current_nfs" == "1" ]] && mount_list="${mount_list}nfs;" [[ "$current_cifs" == "1" ]] && mount_list="${mount_list}cifs;" mount_list="${mount_list%;}" if [[ -n "$mount_list" ]]; then new_features="${new_features}mount=${mount_list}," fi # Retirer la virgule finale new_features="${new_features%,}" # Appliquer les modifications echo -e "\n${YELLOW}Application des modifications...${NC}" if [[ -n "$new_features" ]]; then if pct set "$vmid" --features "$new_features"; then echo -e "${GREEN}✓ Configuration mise à jour${NC}" echo -e "${BLUE}Nouvelles options: $new_features${NC}" else error_exit "Échec de la mise à jour" return 1 fi else if pct set "$vmid" --delete features 2>/dev/null; then echo -e "${GREEN}✓ Toutes les fonctionnalités ont été désactivées${NC}" else echo -e "${YELLOW}Note: Aucune fonctionnalité à désactiver${NC}" fi fi # Demander si un redémarrage est nécessaire if pct status "$vmid" | grep -q "running"; then echo -e "\n${YELLOW}Le conteneur doit être redémarré pour que les changements prennent effet${NC}" read -rp "Redémarrer maintenant? (o/n): " restart_choice if [[ "$restart_choice" == "o" ]]; then pct reboot "$vmid" && echo -e "${GREEN}✓ Conteneur redémarré${NC}" fi fi read -rp "Appuyez sur Entrée pour continuer..." } # Fonction pour supprimer des sauvegardes delete_backups() { show_logo echo -e "${GREEN}Suppression de sauvegardes${NC}\n" # Lister tous les conteneurs list_containers local vmid read -rp "Numéro du conteneur (ou Entrée pour toutes les sauvegardes): " vmid if [[ -n "$vmid" ]] && ! validate_vmid "$vmid"; then error_exit "VMID invalide" return 1 fi # Lister tous les stockages disponibles echo -e "\n${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 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 echo -e "\n${BLUE}Sauvegardes disponibles sur '$selected_storage':${NC}" local filter="" if [[ -n "$vmid" ]]; then filter="vzdump-lxc-${vmid}-" echo -e "${YELLOW}Filtrage pour le conteneur $vmid${NC}\n" fi mapfile -t backups < <(pvesm list "$selected_storage" | grep "vzdump" | grep "$filter" | awk '{print $1}' | sed "s/^${selected_storage}://") if [[ ${#backups[@]} -eq 0 ]]; then error_exit "Aucune sauvegarde trouvée" return 1 fi # Afficher les sauvegardes avec numéros for i in "${!backups[@]}"; do local backup_name="${backups[$i]}" 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 "") local size_backup=$(pvesm list "$selected_storage" | grep "$filename" | awk '{print $3}') echo -e "$((i+1))) ${MAGENTA}VMID:${NC} $vmid_backup ${MAGENTA}Date:${NC} $date_backup ${MAGENTA}Taille:${NC} $size_backup" echo -e " ${BLUE}Fichier:${NC} $filename" echo "" done # Sélection des sauvegardes à supprimer echo -e "${YELLOW}Options de sélection:${NC}" echo "- Un numéro: 3" echo "- Plusieurs numéros séparés par des espaces: 1 3 5" echo "- Une plage: 1-3" echo "- Combinaison: 1 3-5 7" echo "- 'all' pour tout supprimer" echo "" local selection read -rp "Sélection: " selection if [[ -z "$selection" ]]; then echo -e "${YELLOW}Aucune sélection, opération annulée${NC}" read -rp "Appuyez sur Entrée pour continuer..." return fi # Parser la sélection local -a selected_indices=() if [[ "$selection" == "all" ]]; then for i in "${!backups[@]}"; do selected_indices+=($i) done else for part in $selection; do if [[ "$part" =~ ^([0-9]+)-([0-9]+)$ ]]; then # Plage local start=${BASH_REMATCH[1]} local end=${BASH_REMATCH[2]} for ((j=start; j<=end; j++)); do if [[ $j -ge 1 ]] && [[ $j -le ${#backups[@]} ]]; then selected_indices+=($((j-1))) fi done elif [[ "$part" =~ ^[0-9]+$ ]]; then # Numéro simple if [[ $part -ge 1 ]] && [[ $part -le ${#backups[@]} ]]; then selected_indices+=($((part-1))) fi fi done fi if [[ ${#selected_indices[@]} -eq 0 ]]; then error_exit "Aucune sauvegarde valide sélectionnée" return 1 fi # Afficher les sauvegardes sélectionnées echo -e "\n${RED}⚠ Sauvegardes sélectionnées pour suppression:${NC}" local total_size=0 for idx in "${selected_indices[@]}"; do local backup_name="${backups[$idx]}" local filename=$(basename "$backup_name") echo -e "${RED} - $filename${NC}" done echo "" # Confirmation 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 # Suppression des sauvegardes echo -e "\n${YELLOW}Suppression en cours...${NC}\n" local success_count=0 local fail_count=0 for idx in "${selected_indices[@]}"; do local backup_path="${backups[$idx]}" local filename=$(basename "$backup_path") echo -e "${BLUE}Suppression de: $filename${NC}" if pvesm free "${selected_storage}:${backup_path}"; then echo -e "${GREEN}✓ Supprimé${NC}\n" ((success_count++)) else echo -e "${RED}✗ Échec${NC}\n" ((fail_count++)) fi done # Résumé echo -e "${BLUE}═══════════════════════════════════════════════${NC}" echo -e "${GREEN}✓ Supprimées avec succès: $success_count${NC}" if [[ $fail_count -gt 0 ]]; then echo -e "${RED}✗ Échecs: $fail_count${NC}" fi echo -e "${BLUE}═══════════════════════════════════════════════${NC}" 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) Supprimer des sauvegardes" echo "11) Afficher les informations" echo "12) Entrer dans un conteneur" echo "13) Cloner un conteneur" echo -e "14) ${MAGENTA}Modifier les options (nesting/fuse/nfs/cifs)${NC}" echo -e "15) ${MAGENTA}Gérer la protection d'un conteneur${NC}" if [[ "$PROTECTION_ENABLED" == true ]]; then echo -e "16) ${MAGENTA}Mode protection global (ACTIVÉ)${NC}" else echo -e "16) ${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) delete_backups || true;; 11) show_container_info || true;; 12) enter_container || true;; 13) clone_container || true;; 14) modify_container_options || true;; 15) toggle_protection || true;; 16) 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