1143 lines
35 KiB
Bash
1143 lines
35 KiB
Bash
#!/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}' | 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 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 |