#!/bin/bash ############################################# # Script de Gestion LXC pour Proxmox VE # Date: 18/12/2025 # 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 NC='\033[0m' # No Color # 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 afficher le logo show_logo() { clear echo -e "${BLUE}" echo "╔═══════════════════════════════════════════════╗" echo "║ GESTIONNAIRE LXC PROXMOX VE ║" echo "║ Version 2.0 - Sécurisé ║" echo "╚═══════════════════════════════════════════════╝" echo -e "${NC}" } # 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}" pvesm status | grep -E "^(local|local-lvm)" || pvesm status echo "" local storage read -rp "Stockage pour le conteneur (ex: local-lvm): " storage if [[ -z "$storage" ]]; then error_exit "Le stockage ne peut pas être vide" 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 # 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" # 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; 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 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 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:${NC}" pvesm status | grep -v "^Disk" echo "" local dumpdir read -rp "Stockage pour la sauvegarde (défaut: local): " dumpdir dumpdir=${dumpdir:-local} # Vérifier que le stockage existe if ! pvesm status | grep -q "^${dumpdir}"; then error_exit "Le stockage '$dumpdir' n'existe pas" return 1 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 les sauvegardes disponibles echo -e "${BLUE}Sauvegardes disponibles:${NC}" pvesm list local | grep "vzdump" || echo "Aucune sauvegarde trouvée dans 'local'" echo "" 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" return 1 fi 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 local storage read -rp "Stockage pour le conteneur (défaut: local-lvm): " storage storage=${storage:-local-lvm} # Vérifier que le stockage existe if ! pvesm status | grep -q "^${storage}"; then error_exit "Le stockage '$storage' n'existe pas" return 1 fi echo -e "\n${YELLOW}Restauration en cours...${NC}" if pct restore "$new_vmid" "local:backup/${backup_file}" --storage "$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}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 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 echo -e "\n${YELLOW}Clonage en cours...${NC}" if pct clone "$source_vmid" "$new_vmid" --hostname "$new_hostname"; then echo -e "${GREEN}✓ Conteneur cloné avec succès${NC}" else error_exit "Échec du clonage" 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 "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;; 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