#!/usr/bin/env bash set -Eeuo pipefail trap 'printf "Erreur: echec de la commande [%s] a la ligne %s.\n" "$BASH_COMMAND" "$LINENO" >&2' ERR usage() { cat <<'EOF' Usage: ./scripts/install-proxmox-lxc.sh \ --proxmox-host 192.168.1.10 \ --proxmox-user root@pam \ --proxmox-password 'motdepasse' ./scripts/install-proxmox-lxc.sh --local Options principales: --proxmox-host IP ou nom DNS du serveur Proxmox --proxmox-user Utilisateur SSH Proxmox (defaut: root@pam) --proxmox-password Mot de passe SSH Proxmox --ssh-port Port SSH Proxmox (defaut: 22) --local Execute directement sur l'hote Proxmox local --ctid CTID Proxmox. Si vide, le prochain ID libre est utilise --hostname Nom du LXC (defaut: chesscubing-web) --lxc-ip IP du LXC ou 'dhcp' (defaut: dhcp) --gateway Passerelle si IP statique --bridge Bridge reseau Proxmox (defaut: vmbr0) --cores Nombre de vCPU du LXC (defaut: 2) --memory Memoire RAM en Mo (defaut: 1024) --swap Swap en Mo (defaut: 512) --disk-gb Taille disque du LXC en Go (defaut: 6) --template-storage Stockage Proxmox pour les templates --rootfs-storage Stockage Proxmox pour le disque LXC --repo-url Depot Git a deployer --branch Branche Git a deployer (defaut: main) --lxc-password Mot de passe root du LXC. Genere si absent -h, --help Affiche cette aide Exemple: ./scripts/install-proxmox-lxc.sh \ --proxmox-host 10.0.0.2 \ --proxmox-user root@pam \ --proxmox-password 'secret' EOF } die() { printf 'Erreur: %s\n' "$*" >&2 exit 1 } need_cmd() { command -v "$1" >/dev/null 2>&1 || die "La commande '$1' est requise." } PROXMOX_HOST="" PROXMOX_USER="root@pam" PROXMOX_PASSWORD="${PROXMOX_PASSWORD:-}" PROXMOX_PORT="22" LOCAL_MODE="0" CTID="" LXC_HOSTNAME="chesscubing-web" LXC_IP="dhcp" LXC_GATEWAY="" LXC_BRIDGE="vmbr0" LXC_CORES="2" LXC_MEMORY="1024" LXC_SWAP="512" LXC_DISK_GB="6" TEMPLATE_STORAGE="" ROOTFS_STORAGE="" REPO_URL="https://git.jeannerot.fr/christophe/chesscubing.git" REPO_BRANCH="main" LXC_PASSWORD="" while [[ $# -gt 0 ]]; do case "$1" in --proxmox-host) PROXMOX_HOST="${2:-}" shift 2 ;; --proxmox-user) PROXMOX_USER="${2:-}" shift 2 ;; --proxmox-password) PROXMOX_PASSWORD="${2:-}" shift 2 ;; --ssh-port) PROXMOX_PORT="${2:-}" shift 2 ;; --local) LOCAL_MODE="1" shift ;; --ctid) CTID="${2:-}" shift 2 ;; --hostname) LXC_HOSTNAME="${2:-}" shift 2 ;; --lxc-ip) LXC_IP="${2:-}" shift 2 ;; --gateway) LXC_GATEWAY="${2:-}" shift 2 ;; --bridge) LXC_BRIDGE="${2:-}" shift 2 ;; --cores) LXC_CORES="${2:-}" shift 2 ;; --memory) LXC_MEMORY="${2:-}" shift 2 ;; --swap) LXC_SWAP="${2:-}" shift 2 ;; --disk-gb) LXC_DISK_GB="${2:-}" shift 2 ;; --template-storage) TEMPLATE_STORAGE="${2:-}" shift 2 ;; --rootfs-storage) ROOTFS_STORAGE="${2:-}" shift 2 ;; --repo-url) REPO_URL="${2:-}" shift 2 ;; --branch) REPO_BRANCH="${2:-}" shift 2 ;; --lxc-password) LXC_PASSWORD="${2:-}" shift 2 ;; -h | --help) usage exit 0 ;; *) die "Option inconnue: $1" ;; esac done if [[ "$LOCAL_MODE" != "1" && -z "$PROXMOX_HOST" ]]; then if command -v pct >/dev/null 2>&1 && command -v pveam >/dev/null 2>&1; then LOCAL_MODE="1" fi fi payload_script="$(mktemp)" cleanup() { rm -f "$payload_script" } trap cleanup EXIT cat >"$payload_script" <<'REMOTE' set -Eeuo pipefail trap 'printf "Erreur: echec de la commande [%s] a la ligne %s.\n" "$BASH_COMMAND" "$LINENO" >&2' ERR ctid="$1" lxc_hostname="$2" lxc_ip="$3" lxc_gateway="$4" lxc_bridge="$5" lxc_cores="$6" lxc_memory="$7" lxc_swap="$8" lxc_disk_gb="$9" shift 9 template_storage="$1" rootfs_storage="$2" repo_url="$3" repo_branch="$4" lxc_password="$5" die() { printf 'Erreur: %s\n' "$*" >&2 exit 1 } need_remote_cmd() { command -v "$1" >/dev/null 2>&1 || die "La commande '$1' est absente sur Proxmox." } pick_storage() { local candidate="" for candidate in "$@"; do if pvesm status 2>/dev/null | awk 'NR > 1 { print $1 }' | grep -Fxq "$candidate"; then printf '%s\n' "$candidate" return 0 fi done return 1 } pick_first_dir_storage() { pvesm status 2>/dev/null | awk 'NR > 1 && $2 == "dir" { print $1; exit }' } find_ctid_by_hostname() { local wanted="$1" local candidate="" local candidate_hostname="" while read -r candidate; do [[ -n "$candidate" ]] || continue candidate_hostname="$(pct config "$candidate" 2>/dev/null | awk -F ': ' '/^hostname:/ { print $2; exit }')" if [[ "$candidate_hostname" == "$wanted" ]]; then printf '%s\n' "$candidate" return 0 fi done < <(pct list | awk 'NR > 1 { print $1 }') return 1 } ct_exec() { pct exec "$ctid" -- bash -lc "$1" } need_remote_cmd pct need_remote_cmd pveam need_remote_cmd pvesm if [[ -z "$ctid" ]]; then command -v pvesh >/dev/null 2>&1 || die "Impossible de calculer le prochain CTID sans pvesh." ctid="$(pvesh get /cluster/nextid)" fi if pct status "$ctid" >/dev/null 2>&1; then die "Le CTID $ctid existe deja." fi if existing_ctid="$(find_ctid_by_hostname "$lxc_hostname")"; then die "Un conteneur nomme $lxc_hostname existe deja sous le CTID $existing_ctid." fi if [[ -z "$template_storage" ]]; then template_storage="$(pick_storage local)" if [[ -z "$template_storage" ]]; then template_storage="$(pick_first_dir_storage)" fi fi [[ -n "$template_storage" ]] || die "Aucun stockage de template detecte. Passe --template-storage." if [[ -z "$rootfs_storage" ]]; then rootfs_storage="$(pick_storage local-lvm local-zfs local)" fi [[ -n "$rootfs_storage" ]] || die "Aucun stockage rootfs detecte. Passe --rootfs-storage." printf 'Mise a jour du catalogue de templates Proxmox...\n' pveam update >/dev/null template_name="$(pveam available --section system | awk '/debian-12-standard/ { print $2 }' | sort -V | tail -n 1)" if [[ -z "$template_name" ]]; then template_name="debian-12-standard_12.7-1_amd64.tar.zst" fi if ! pveam list "$template_storage" 2>/dev/null | grep -Fq "$template_name"; then printf 'Telechargement du template %s sur %s...\n' "$template_name" "$template_storage" pveam download "$template_storage" "$template_name" >/dev/null fi template_ref="${template_storage}:vztmpl/${template_name}" if [[ -z "$lxc_password" ]]; then lxc_password="$(od -An -N12 -tx1 /dev/urandom | tr -d ' \n')" fi net0="name=eth0,bridge=${lxc_bridge},ip=dhcp" if [[ "$lxc_ip" != "dhcp" ]]; then net0="name=eth0,bridge=${lxc_bridge},ip=${lxc_ip}" if [[ -n "$lxc_gateway" ]]; then net0="${net0},gw=${lxc_gateway}" fi fi printf 'Creation du LXC %s (%s)...\n' "$ctid" "$lxc_hostname" pct create "$ctid" "$template_ref" \ --hostname "$lxc_hostname" \ --cores "$lxc_cores" \ --memory "$lxc_memory" \ --swap "$lxc_swap" \ --rootfs "${rootfs_storage}:${lxc_disk_gb}" \ --net0 "$net0" \ --onboot 1 \ --ostype debian \ --password "$lxc_password" \ --unprivileged 1 pct start "$ctid" printf 'Attente du demarrage du LXC...\n' for _ in $(seq 1 20); do if pct exec "$ctid" -- true >/dev/null 2>&1; then break fi sleep 2 done pct exec "$ctid" -- true >/dev/null 2>&1 || die "Le LXC n'est pas joignable apres le demarrage." printf 'Installation de nginx, git et rsync dans le conteneur...\n' ct_exec "apt-get update && apt-get install -y ca-certificates git nginx rsync" ct_exec "install -d -m 0755 /opt/chesscubing/repo /var/www/chesscubing/current" printf 'Clonage du depot %s...\n' "$repo_url" ct_exec "if [ ! -d /opt/chesscubing/repo/.git ]; then \ rm -rf /opt/chesscubing/repo/* /opt/chesscubing/repo/.[!.]* /opt/chesscubing/repo/..?* 2>/dev/null || true; \ git clone --branch '$repo_branch' --single-branch '$repo_url' /opt/chesscubing/repo; \ else \ cd /opt/chesscubing/repo && git checkout '$repo_branch' && git pull --ff-only origin '$repo_branch'; \ fi" ct_exec "cat > /usr/local/bin/update-chesscubing <<'SCRIPT' #!/usr/bin/env bash set -Eeuo pipefail trap 'printf \"Erreur: echec de la commande [%s] a la ligne %s.\\n\" \"\$BASH_COMMAND\" \"\$LINENO\" >&2' ERR repo_dir='/opt/chesscubing/repo' web_root='/var/www/chesscubing/current' branch=\"\${1:-${repo_branch}}\" cd \"\$repo_dir\" if ! git diff --quiet || ! git diff --cached --quiet; then echo 'Le depot de production contient des modifications locales. Mise a jour annulee.' >&2 exit 1 fi current_branch=\"\$(git rev-parse --abbrev-ref HEAD)\" if [[ \"\$current_branch\" != \"\$branch\" ]]; then git checkout \"\$branch\" fi git fetch origin \"\$branch\" git pull --ff-only origin \"\$branch\" install -d -m 0755 \"\$web_root\" rsync -a --delete \ --include='*/' \ --include='*.html' \ --include='*.css' \ --include='*.js' \ --include='*.png' \ --include='*.jpg' \ --include='*.jpeg' \ --include='*.svg' \ --include='*.webp' \ --include='*.ico' \ --include='*.pdf' \ --exclude='*' \ \"\$repo_dir/\" \"\$web_root/\" chown -R www-data:www-data \"\$web_root\" nginx -t systemctl reload nginx SCRIPT chmod +x /usr/local/bin/update-chesscubing" ct_exec "cat > /etc/nginx/sites-available/chesscubing.conf <<'NGINX' server { listen 80; listen [::]:80; server_name _; root /var/www/chesscubing/current; index index.html; location / { try_files \$uri \$uri/ /index.html; } location ~* \.(?:css|js|png|jpg|jpeg|svg|webp|ico|pdf)$ { expires 7d; add_header Cache-Control 'public, max-age=604800'; } } NGINX rm -f /etc/nginx/sites-enabled/default ln -sf /etc/nginx/sites-available/chesscubing.conf /etc/nginx/sites-enabled/chesscubing.conf" printf 'Publication du site dans le LXC...\n' ct_exec "/usr/local/bin/update-chesscubing '$repo_branch'" ct_exec "systemctl enable nginx >/dev/null && systemctl restart nginx" container_ip="$(pct exec "$ctid" -- bash -lc "hostname -I | awk '{print \$1}'" 2>/dev/null | tr -d '\r' || true)" cat <} Pour mettre a jour l'application plus tard: ./scripts/update-proxmox-lxc.sh --proxmox-host --proxmox-user --proxmox-password '' --ctid $ctid EOF REMOTE if [[ "$LOCAL_MODE" == "1" ]]; then printf 'Creation du LXC "%s" en local sur cet hote Proxmox...\n' "$LXC_HOSTNAME" bash "$payload_script" \ "$CTID" \ "$LXC_HOSTNAME" \ "$LXC_IP" \ "$LXC_GATEWAY" \ "$LXC_BRIDGE" \ "$LXC_CORES" \ "$LXC_MEMORY" \ "$LXC_SWAP" \ "$LXC_DISK_GB" \ "$TEMPLATE_STORAGE" \ "$ROOTFS_STORAGE" \ "$REPO_URL" \ "$REPO_BRANCH" \ "$LXC_PASSWORD" exit 0 fi [[ -n "$PROXMOX_HOST" ]] || die "Merci de fournir --proxmox-host." [[ -n "$PROXMOX_USER" ]] || die "Merci de fournir --proxmox-user." if [[ -z "$PROXMOX_PASSWORD" ]]; then read -rsp "Mot de passe SSH pour ${PROXMOX_USER}@${PROXMOX_HOST}: " PROXMOX_PASSWORD echo fi need_cmd ssh need_cmd sshpass printf 'Creation du LXC "%s" sur %s...\n' "$LXC_HOSTNAME" "$PROXMOX_HOST" sshpass -p "$PROXMOX_PASSWORD" \ ssh \ -p "$PROXMOX_PORT" \ -o StrictHostKeyChecking=accept-new \ -o PreferredAuthentications=password \ -o PubkeyAuthentication=no \ "$PROXMOX_USER@$PROXMOX_HOST" \ bash -s -- \ "$CTID" \ "$LXC_HOSTNAME" \ "$LXC_IP" \ "$LXC_GATEWAY" \ "$LXC_BRIDGE" \ "$LXC_CORES" \ "$LXC_MEMORY" \ "$LXC_SWAP" \ "$LXC_DISK_GB" \ "$TEMPLATE_STORAGE" \ "$ROOTFS_STORAGE" \ "$REPO_URL" \ "$REPO_BRANCH" \ "$LXC_PASSWORD" < "$payload_script"