#!/usr/bin/env bash set -euo pipefail # ========================== # Debian 12 Cloud VM (FR) # Proxmox VE (qm) # - Clavier FR (AZERTY) + locale fr_FR.UTF-8 via cloud-init user-data # ========================== # ---- Defaults (tu peux modifier) ---- VMID="${VMID:-}" # si vide -> auto NAME="${NAME:-debian12-fr}" STORAGE="${STORAGE:-local-lvm}" # stockage pour le disque VM SNIPPET_STORAGE="${SNIPPET_STORAGE:-local}" # stockage qui supporte "snippets" (souvent local) BRIDGE="${BRIDGE:-vmbr0}" CORES="${CORES:-2}" RAM="${RAM:-2048}" # MiB DISK_SIZE_GB="${DISK_SIZE_GB:-8}"# taille finale du disque (cloud image sera étendue) START_VM="${START_VM:-yes}" # yes/no # ---- Cloud image ---- DEBIAN_RELEASE="12" IMG_URL="https://cloud.debian.org/images/cloud/bookworm/latest/debian-${DEBIAN_RELEASE}-genericcloud-amd64.qcow2" IMG_NAME="debian-${DEBIAN_RELEASE}-genericcloud-amd64.qcow2" # ---- Locale/Keyboard ---- LOCALE="${LOCALE:-fr_FR.UTF-8}" KB_LAYOUT="${KB_LAYOUT:-fr}" # fr / be / us ... CONSOLE_KEYMAP="${CONSOLE_KEYMAP:-fr}" # fr / fr-latin9 ... # ---- Helpers ---- die() { echo "ERROR: $*" >&2; exit 1; } need_cmd() { command -v "$1" >/dev/null 2>&1 || die "Commande requise manquante: $1" } get_nextid() { pvesh get /cluster/nextid } # ---- Checks ---- need_cmd qm need_cmd pvesh need_cmd curl need_cmd qemu-img if [[ "$(id -u)" -ne 0 ]]; then die "Lance ce script en root (sur le nœud Proxmox)." fi # ---- Determine VMID ---- if [[ -z "${VMID}" ]]; then VMID="$(get_nextid)" fi # Prevent overwrite if qm status "${VMID}" &>/dev/null; then die "VMID ${VMID} déjà utilisé." fi echo "==> Création VMID=${VMID} NAME=${NAME}" # ---- Ensure snippet dir exists ---- # /var/lib/vz/snippets correspond au storage "local" (par défaut). Si tu utilises un autre storage pour snippets, # adapte SNIPPET_STORAGE et/ou le path. SNIPPET_DIR="/var/lib/vz/snippets" mkdir -p "${SNIPPET_DIR}" USERDATA_FILE="${SNIPPET_DIR}/${VMID}-user-data.yaml" cat > "${USERDATA_FILE}" < /etc/default/keyboard - printf "KEYMAP=\\"${CONSOLE_KEYMAP}\\"\\n" > /etc/default/console-setup - setupcon -k || true - command -v localectl >/dev/null 2>&1 && localectl set-locale LANG=${LOCALE} || true - command -v localectl >/dev/null 2>&1 && localectl set-keymap ${CONSOLE_KEYMAP} || true EOF echo "==> cloud-init user-data: ${USERDATA_FILE}" # ---- Download cloud image (cache in /var/lib/vz/template/iso) ---- IMG_CACHE_DIR="/var/lib/vz/template/iso" mkdir -p "${IMG_CACHE_DIR}" IMG_PATH="${IMG_CACHE_DIR}/${IMG_NAME}" if [[ ! -f "${IMG_PATH}" ]]; then echo "==> Téléchargement image: ${IMG_URL}" curl -fL "${IMG_URL}" -o "${IMG_PATH}" else echo "==> Image déjà présente: ${IMG_PATH}" fi # ---- Create VM shell ---- qm create "${VMID}" \ --name "${NAME}" \ --memory "${RAM}" \ --cores "${CORES}" \ --net0 "virtio,bridge=${BRIDGE}" \ --scsihw virtio-scsi-pci \ --agent enabled=1 \ --serial0 socket \ --vga serial0 # ---- Import disk & attach ---- echo "==> Import disque sur ${STORAGE}" qm importdisk "${VMID}" "${IMG_PATH}" "${STORAGE}" # The imported disk name differs by storage type; use qm config to find it. IMPORTED_DISK="$(qm config "${VMID}" | awk -v st="${STORAGE}" ' $1 ~ /^unused[0-9]+:/ && $2 ~ "^"st":" {print $1; sub(":","",$1); print $2} ' | tail -n 1 | tail -n 1 || true)" if [[ -z "${IMPORTED_DISK}" ]]; then # Fallback: take first unusedX line IMPORTED_DISK="$(qm config "${VMID}" | awk '$1 ~ /^unused[0-9]+:/ {print $2; exit}' || true)" fi [[ -n "${IMPORTED_DISK}" ]] || die "Impossible de retrouver le disque importé (unusedX)." echo "==> Disque importé: ${IMPORTED_DISK}" qm set "${VMID}" --scsi0 "${IMPORTED_DISK}" # remove unused entry UNUSED_KEY="$(qm config "${VMID}" | awk '$1 ~ /^unused[0-9]+:/ {print $1}' | head -n 1 | tr -d ':')" if [[ -n "${UNUSED_KEY}" ]]; then qm set "${VMID}" --delete "${UNUSED_KEY}" >/dev/null || true fi # ---- Cloud-init drive + custom user-data ---- qm set "${VMID}" --ide2 "${STORAGE}:cloudinit" qm set "${VMID}" --boot order=scsi0 # Resize disk to target echo "==> Resize disque à ${DISK_SIZE_GB}G" qm resize "${VMID}" scsi0 "${DISK_SIZE_GB}G" # Attach cicustom user-data echo "==> Attache user-data via cicustom (storage snippets: ${SNIPPET_STORAGE})" qm set "${VMID}" --cicustom "user=${SNIPPET_STORAGE}:snippets/${VMID}-user-data.yaml" # Optional: DHCP on cloud image (default). You can pin IP via --ipconfig0 if needed. echo "==> VM créée. Cloud-init appliquera locale/clavier FR au premier boot." if [[ "${START_VM}" == "yes" ]]; then echo "==> Démarrage VM" qm start "${VMID}" fi echo "✅ Terminé. VMID=${VMID}"