[ADD] qemu : déployer Proxmox VE (amd64, arm64)
Proxmox ne publie aucune image cloud : son ISO est un installateur qui formate
le disque. On prend donc la voie que l'amont documente lui-même — Proxmox VE
sur Debian — depuis l'image cloud trixie, partagée avec un déploiement
Debian 13 au lieu d'être téléchargée deux fois.
s390x n'y est pas et n'y sera pas par cette voie : le dépôt n'a aucun index
binary-s390x. arm64 y est, officiel depuis PVE 9. Le catalogue le dit AVANT le
déploiement, au lieu d'échouer au premier apt.
Vérifié sur une VM réelle : pve-manager 9.2.11, noyau 7.0.14-12-pve, quatre
services actifs, interface web en HTTP 200.
--- EN ---
Proxmox publishes no cloud image: its ISO is an installer that formats the
disk. So we take the path upstream documents itself — Proxmox VE on Debian —
from the trixie cloud image, shared with a Debian 13 deployment instead of
being downloaded twice.
s390x is not there and will not be by this route: the repository has no
binary-s390x index. arm64 is, official since PVE 9. The catalog says so BEFORE
the deployment rather than failing at the first apt.
Verified on a real VM: pve-manager 9.2.11, kernel 7.0.14-12-pve, four services
active, web UI answering HTTP 200.
Assisted-by: Claude Opus 5
2026-08-23 03:28:03 -04:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
#
|
|
|
|
|
# Installe Proxmox VE — https://www.proxmox.com — SUR UNE DEBIAN existante.
|
|
|
|
|
#
|
|
|
|
|
# Pourquoi sur Debian : Proxmox ne publie aucune image cloud. Son ISO est un
|
|
|
|
|
# installateur qui formate le disque, ce qui ne se pilote pas depuis un
|
|
|
|
|
# déploiement cloud-init. La voie que l'amont documente lui-même pour ce cas
|
|
|
|
|
# est « Install Proxmox VE on Debian » : on part de l'image cloud Debian, on
|
|
|
|
|
# ajoute le dépôt pve, et les paquets font le reste. Le résultat est le même
|
|
|
|
|
# hyperviseur, avec le noyau Proxmox et l'interface web sur :8006.
|
|
|
|
|
#
|
|
|
|
|
# Architectures : amd64 et arm64 (arm64 officiel depuis PVE 9 — le Release de
|
|
|
|
|
# trixie annonce « amd64 arm64 »). PAS s390x : l'index « binary-s390x » du
|
|
|
|
|
# dépôt répond 404, le port n'existe pas.
|
|
|
|
|
#
|
|
|
|
|
# Réglages, par variables d'environnement :
|
|
|
|
|
# PVE_SUITE suite Debian visée (défaut : trixie, = PVE 9)
|
|
|
|
|
# PVE_REBOOT à 1, redémarre à la fin (défaut : ne redémarre PAS —
|
|
|
|
|
# lancé par SSH, un reboot couperait la session et ferait
|
|
|
|
|
# passer une installation réussie pour un échec)
|
|
|
|
|
# PVE_KEEP_DEBIAN_KERNEL à 1, garde le noyau Debian à côté du noyau pve
|
|
|
|
|
# PVE_OS_RELEASE fichier os-release à lire (défaut : /etc/os-release) ;
|
|
|
|
|
# sert à vérifier le script depuis une autre distribution
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
Red='\033[0;31m'
|
|
|
|
|
Green='\033[0;32m'
|
|
|
|
|
Yellow='\033[0;33m'
|
|
|
|
|
Color_Off='\033[0m'
|
|
|
|
|
|
|
|
|
|
SUITE="${PVE_SUITE:-trixie}"
|
|
|
|
|
KEYRING=/usr/share/keyrings/proxmox-archive-keyring.gpg
|
|
|
|
|
SOURCES=/etc/apt/sources.list.d/pve-install-repo.sources
|
|
|
|
|
|
|
|
|
|
# Somme relevée sur la page amont « Install Proxmox VE on Debian 13 Trixie »,
|
|
|
|
|
# et VÉRIFIÉE contre le fichier servi. Elle vaut ceinture ET bretelle : c'est
|
|
|
|
|
# la clé qui authentifiera tout le reste, et la télécharger sans la contrôler
|
|
|
|
|
# reviendrait à faire confiance au seul transport.
|
|
|
|
|
KEY_URL="https://enterprise.proxmox.com/debian/proxmox-archive-keyring-${SUITE}.gpg"
|
|
|
|
|
KEY_SHA256_trixie=136673be77aba35dcce385b28737689ad64fd785a797e57897589aed08db6e45
|
|
|
|
|
|
|
|
|
|
# Ce qui a changé : décide du redémarrage final. Une installation déjà faite ne
|
|
|
|
|
# doit pas redémarrer la machine pour rien.
|
|
|
|
|
CHANGED=0
|
|
|
|
|
|
|
|
|
|
say() { echo -e "$@"; }
|
|
|
|
|
die() { say "${Red}✗${Color_Off} $*"; exit 1; }
|
|
|
|
|
|
|
|
|
|
DRY=0
|
|
|
|
|
usage() {
|
|
|
|
|
cat <<EOF
|
|
|
|
|
Installe Proxmox VE sur une Debian existante (amd64, arm64).
|
|
|
|
|
|
|
|
|
|
--dry-run dit ce qu'il ferait, sans rien changer
|
|
|
|
|
--help cette aide
|
|
|
|
|
|
|
|
|
|
Variables : PVE_SUITE (défaut trixie), PVE_REBOOT, PVE_KEEP_DEBIAN_KERNEL,
|
|
|
|
|
PVE_OS_RELEASE
|
|
|
|
|
EOF
|
|
|
|
|
}
|
|
|
|
|
while [ $# -gt 0 ]; do
|
|
|
|
|
case "$1" in
|
|
|
|
|
--dry-run) DRY=1 ;;
|
|
|
|
|
-h | --help) usage; exit 0 ;;
|
|
|
|
|
*) die "option inconnue : $1" ;;
|
|
|
|
|
esac
|
|
|
|
|
shift
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
# Toute action qui MODIFIE la machine passe par ici. En dry-run elle est
|
|
|
|
|
# annoncée, pas exécutée : c'est ce qui rend le script vérifiable de bout en
|
|
|
|
|
# bout sans hyperviseur ni dépôt à disposition.
|
|
|
|
|
run() {
|
|
|
|
|
if [ "${DRY}" = "1" ]; then
|
|
|
|
|
say " ${Yellow}[dry-run]${Color_Off} $*"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
"$@"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# --- 1. Architecture --------------------------------------------------------
|
|
|
|
|
case "$(uname -m)" in
|
|
|
|
|
x86_64) ARCH=amd64 ;;
|
|
|
|
|
aarch64 | arm64) ARCH=arm64 ;;
|
|
|
|
|
*)
|
|
|
|
|
die "Proxmox VE n'existe pas pour $(uname -m) :" \
|
|
|
|
|
"le dépôt ne publie que amd64 et arm64."
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
|
|
|
|
|
# --- 2. Debian, et la bonne suite ------------------------------------------
|
|
|
|
|
OS_RELEASE="${PVE_OS_RELEASE:-/etc/os-release}"
|
|
|
|
|
[ -r "${OS_RELEASE}" ] || die "${OS_RELEASE} illisible : Debian attendue."
|
|
|
|
|
# shellcheck disable=SC1090
|
|
|
|
|
. "${OS_RELEASE}"
|
|
|
|
|
if [ "${ID:-}" != "debian" ]; then
|
|
|
|
|
die "Proxmox VE s'installe sur Debian, pas sur « ${ID:-inconnu} »." \
|
|
|
|
|
"\n Redéployer la VM avec --distro proxmox (base Debian ${SUITE})."
|
|
|
|
|
fi
|
|
|
|
|
CODENAME="${VERSION_CODENAME:-}"
|
|
|
|
|
if [ -n "${CODENAME}" ] && [ "${CODENAME}" != "${SUITE}" ]; then
|
|
|
|
|
say "${Yellow}⚠${Color_Off} Debian « ${CODENAME} » alors que le dépôt visé" \
|
|
|
|
|
"est « ${SUITE} » : PVE_SUITE=${CODENAME} si c'est voulu."
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# --- 3. /etc/hosts : le nom d'hôte doit résoudre vers une IP ROUTABLE -------
|
|
|
|
|
# Ce n'est pas un détail de confort. Le système de grappe de Proxmox parcourt
|
|
|
|
|
# toutes les adresses du nom d'hôte jusqu'à en trouver une qui ne soit pas de
|
|
|
|
|
# bouclage ; l'entrée « 127.0.1.1 <nom> » que pose l'image cloud Debian le
|
|
|
|
|
# mène droit dans le mur, et pveproxy comme pvecm s'en trouvent mal.
|
|
|
|
|
# « hostname --ip-address » est le test que l'amont donne lui-même.
|
|
|
|
|
host_ip() {
|
|
|
|
|
local ip=""
|
|
|
|
|
for ip in \
|
|
|
|
|
"$(ip -4 route get 1 2>/dev/null | awk '{print $7; exit}')" \
|
|
|
|
|
"$(hostname -I 2>/dev/null | awk '{print $1}')" \
|
|
|
|
|
"$(ip -4 -o addr show scope global 2>/dev/null \
|
|
|
|
|
| awk '{split($4, a, "/"); print a[1]; exit}')"
|
|
|
|
|
do
|
|
|
|
|
case "$ip" in
|
|
|
|
|
127.*) continue ;;
|
|
|
|
|
[0-9]*.[0-9]*.[0-9]*.[0-9]*) echo "$ip"; return 0 ;;
|
|
|
|
|
esac
|
|
|
|
|
done
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fix_hosts() {
|
|
|
|
|
local ip fqdn short
|
|
|
|
|
ip="$(host_ip)" || die "aucune adresse IPv4 routable : réseau absent ?"
|
|
|
|
|
short="$(hostname -s)"
|
|
|
|
|
fqdn="$(hostname -f 2>/dev/null || echo "${short}")"
|
|
|
|
|
[ "${fqdn}" = "${short}" ] && fqdn="${short}.local"
|
|
|
|
|
if grep -qE "^[[:space:]]*127\.0\.1\.1[[:space:]]" /etc/hosts; then
|
|
|
|
|
say " retrait de l'entrée 127.0.1.1 (bouclage) pour ${short}"
|
|
|
|
|
run sudo sed -i -E "/^[[:space:]]*127\.0\.1\.1[[:space:]]/d" \
|
|
|
|
|
/etc/hosts
|
|
|
|
|
CHANGED=1
|
|
|
|
|
fi
|
|
|
|
|
if ! grep -qE "^[[:space:]]*${ip//./\\.}[[:space:]]+.*\b${short}\b" \
|
|
|
|
|
/etc/hosts; then
|
|
|
|
|
say " ${short} -> ${ip} dans /etc/hosts"
|
|
|
|
|
if [ "${DRY}" = "1" ]; then
|
|
|
|
|
say " ${Yellow}[dry-run]${Color_Off} ${ip} ${fqdn} ${short}" \
|
|
|
|
|
">> /etc/hosts"
|
|
|
|
|
else
|
|
|
|
|
printf '%s\t%s %s\n' "${ip}" "${fqdn}" "${short}" \
|
|
|
|
|
| sudo tee -a /etc/hosts >/dev/null
|
|
|
|
|
fi
|
|
|
|
|
CHANGED=1
|
|
|
|
|
fi
|
|
|
|
|
# Le test de l'amont, mot pour mot : au moins une adresse non-bouclage.
|
|
|
|
|
local vu routables
|
|
|
|
|
vu="$(hostname --ip-address 2>/dev/null || true)"
|
|
|
|
|
routables="$(printf '%s\n' ${vu} | grep -vE '^(127\.|::1$)' || true)"
|
|
|
|
|
[ -n "${routables}" ] || die \
|
|
|
|
|
"« hostname --ip-address » rend « ${vu:-rien} » : le nom d'hôte ne" \
|
|
|
|
|
"résout toujours pas vers une adresse routable."
|
|
|
|
|
say " hostname --ip-address : $(printf '%s ' ${routables})"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# --- 4. Dépôt et clé --------------------------------------------------------
|
|
|
|
|
add_repo() {
|
|
|
|
|
local attendu="KEY_SHA256_${SUITE}"
|
|
|
|
|
attendu="${!attendu:-}"
|
|
|
|
|
if [ ! -s "${KEYRING}" ]; then
|
|
|
|
|
say " clé du dépôt -> ${KEYRING}"
|
|
|
|
|
run sudo mkdir -p "$(dirname "${KEYRING}")"
|
|
|
|
|
run sudo wget -q "${KEY_URL}" -O "${KEYRING}" \
|
|
|
|
|
|| die "téléchargement de la clé impossible : ${KEY_URL}"
|
|
|
|
|
# Un wget qui rend 0 sans laisser de fichier — proxy captif, disque
|
|
|
|
|
# plein — ferait mourir la suite sur un message de sha256sum au lieu
|
|
|
|
|
# d'un diagnostic.
|
|
|
|
|
if [ "${DRY}" != "1" ]; then
|
|
|
|
|
sudo test -s "${KEYRING}" \
|
|
|
|
|
|| die "clé absente ou vide après téléchargement : ${KEYRING}"
|
|
|
|
|
fi
|
|
|
|
|
CHANGED=1
|
|
|
|
|
fi
|
|
|
|
|
if [ -n "${attendu}" ] && [ "${DRY}" = "1" ]; then
|
|
|
|
|
say " clé à vérifier contre sha256 ${attendu:0:16}…"
|
|
|
|
|
elif [ -n "${attendu}" ]; then
|
|
|
|
|
local vu
|
|
|
|
|
vu="$(sudo sha256sum "${KEYRING}" 2>/dev/null | awk '{print $1}')" \
|
|
|
|
|
|| true
|
|
|
|
|
[ -n "${vu}" ] || die "somme de la clé illisible : ${KEYRING}"
|
|
|
|
|
if [ "${vu}" != "${attendu}" ]; then
|
|
|
|
|
# On efface la clé douteuse : la laisser en place ferait passer la
|
|
|
|
|
# prochaine exécution pour bonne, le fichier étant non vide.
|
|
|
|
|
run sudo rm -f "${KEYRING}"
|
|
|
|
|
die "somme de la clé inattendue :\n vue ${vu}\n" \
|
|
|
|
|
" attendue ${attendu}"
|
|
|
|
|
fi
|
|
|
|
|
say " clé vérifiée (sha256 ${vu:0:16}…)"
|
|
|
|
|
else
|
|
|
|
|
say "${Yellow}⚠${Color_Off} aucune somme connue pour « ${SUITE} » :" \
|
|
|
|
|
"clé acceptée sans contrôle."
|
|
|
|
|
fi
|
|
|
|
|
# Format deb822, celui que l'amont recommande sur Debian 13.
|
|
|
|
|
local voulu
|
|
|
|
|
voulu="$(printf 'Types: deb\nURIs: http://download.proxmox.com/debian/pve\nSuites: %s\nComponents: pve-no-subscription\nSigned-By: %s\n' \
|
|
|
|
|
"${SUITE}" "${KEYRING}")"
|
|
|
|
|
if [ "$(cat "${SOURCES}" 2>/dev/null || true)" != "${voulu}" ]; then
|
|
|
|
|
say " dépôt pve-no-subscription -> ${SOURCES}"
|
|
|
|
|
if [ "${DRY}" = "1" ]; then
|
|
|
|
|
say " ${Yellow}[dry-run]${Color_Off} ${SOURCES} :" \
|
|
|
|
|
"$(printf '%s' "${voulu}" | tr '\n' '|')"
|
|
|
|
|
else
|
|
|
|
|
printf '%s\n' "${voulu}" | sudo tee "${SOURCES}" >/dev/null
|
|
|
|
|
fi
|
|
|
|
|
CHANGED=1
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# --- 6. Dépôt entreprise ----------------------------------------------------
|
|
|
|
|
# « proxmox-ve » installe SON dépôt entreprise (pve-enterprise.sources), qui
|
|
|
|
|
# exige un abonnement payant. Sans lui, tout « apt update » ultérieur échoue :
|
|
|
|
|
# « 401 Unauthorized » puis « The repository is not signed », et la machine ne
|
|
|
|
|
# peut plus rien installer — pas même une mise à jour de sécurité. Mesuré sur
|
|
|
|
|
# la VM d'essai, au deuxième passage du script.
|
|
|
|
|
#
|
|
|
|
|
# On le désactive au lieu de l'effacer : « Enabled: false » est la forme deb822
|
|
|
|
|
# prévue pour cela, elle survit aux mises à jour du paquet, et il suffit de
|
|
|
|
|
# retirer la ligne le jour où un abonnement existe.
|
|
|
|
|
disable_enterprise() {
|
|
|
|
|
local f
|
|
|
|
|
for f in /etc/apt/sources.list.d/pve-enterprise.sources \
|
|
|
|
|
/etc/apt/sources.list.d/ceph.sources; do
|
|
|
|
|
[ -e "${f}" ] || continue
|
|
|
|
|
if sudo grep -qiE '^[[:space:]]*Enabled:[[:space:]]*(false|no)' \
|
|
|
|
|
"${f}"; then
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
say " dépôt entreprise désactivé : $(basename "${f}")"
|
|
|
|
|
if [ "${DRY}" = "1" ]; then
|
|
|
|
|
say " ${Yellow}[dry-run]${Color_Off} Enabled: false >> ${f}"
|
|
|
|
|
else
|
|
|
|
|
printf 'Enabled: false\n' | sudo tee -a "${f}" >/dev/null
|
|
|
|
|
fi
|
|
|
|
|
CHANGED=1
|
|
|
|
|
done
|
|
|
|
|
# Les anciennes formes « .list », au cas où une mise à jour les remette.
|
|
|
|
|
for f in /etc/apt/sources.list.d/pve-enterprise.list; do
|
|
|
|
|
[ -e "${f}" ] || continue
|
|
|
|
|
sudo grep -qE '^[[:space:]]*#' "${f}" && continue
|
|
|
|
|
say " dépôt entreprise commenté : $(basename "${f}")"
|
|
|
|
|
run sudo sed -i -E 's/^([[:space:]]*deb)/#\1/' "${f}"
|
|
|
|
|
CHANGED=1
|
|
|
|
|
done
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# --- 5. Les paquets ---------------------------------------------------------
|
|
|
|
|
# Disque d'amorçage, pour la préréponse de grub-pc.
|
|
|
|
|
boot_disk() {
|
|
|
|
|
local src
|
|
|
|
|
src="$(findmnt -no SOURCE /boot 2>/dev/null \
|
|
|
|
|
|| findmnt -no SOURCE / 2>/dev/null)"
|
|
|
|
|
[ -n "${src}" ] || return 1
|
|
|
|
|
local dq
|
|
|
|
|
dq="$(lsblk -no pkname "${src}" 2>/dev/null | head -1)"
|
|
|
|
|
[ -n "${dq}" ] || return 1
|
|
|
|
|
printf '/dev/%s\n' "${dq}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Deux paquets posent des questions debconf, et une seule sans réponse suffit à
|
|
|
|
|
# faire échouer TOUTE la transaction apt :
|
|
|
|
|
#
|
|
|
|
|
# - postfix demande son type et son nom de courrier. Sans préréponse,
|
|
|
|
|
# l'installation attend une saisie que personne ne verra — un déploiement
|
|
|
|
|
# par SSH y reste pendu.
|
|
|
|
|
# - grub-pc demande SUR QUEL DISQUE s'installer. C'est le piège propre à
|
|
|
|
|
# l'image cloud : elle amorce en EFI (grub-cloud-amd64), les paquets pve
|
|
|
|
|
# tirent grub-pc par-dessus, et sa post-installation refuse de deviner —
|
|
|
|
|
# « You must correct your GRUB install devices before proceeding », mesuré,
|
|
|
|
|
# dpkg s'arrête et le noyau Proxmox reste à moitié configuré.
|
|
|
|
|
preseed_debconf() {
|
|
|
|
|
command -v debconf-set-selections >/dev/null 2>&1 || return 0
|
|
|
|
|
local lignes disque
|
|
|
|
|
lignes="$(printf '%s\n' \
|
|
|
|
|
"postfix postfix/main_mailer_type select Local only" \
|
|
|
|
|
"postfix postfix/mailname string $(hostname -f 2>/dev/null \
|
|
|
|
|
|| hostname -s)")"
|
|
|
|
|
if disque="$(boot_disk)"; then
|
|
|
|
|
lignes="$(printf '%s\n%s\n' "${lignes}" \
|
|
|
|
|
"grub-pc grub-pc/install_devices multiselect ${disque}")"
|
|
|
|
|
else
|
|
|
|
|
say "${Yellow}⚠${Color_Off} disque d'amorçage introuvable :" \
|
|
|
|
|
"grub-pc pourrait demander où s'installer."
|
|
|
|
|
fi
|
|
|
|
|
# Pas de tube vers « run » : en dry-run il ne lirait pas son entrée, le
|
|
|
|
|
# printf recevrait SIGPIPE, et « set -o pipefail » emporterait le script —
|
|
|
|
|
# mort silencieuse sur un code 141. Vécu.
|
|
|
|
|
if [ "${DRY}" = "1" ]; then
|
|
|
|
|
say " ${Yellow}[dry-run]${Color_Off} debconf-set-selections :" \
|
|
|
|
|
"$(printf '%s' "${lignes}" | tr '\n' '|')"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
printf '%s\n' "${lignes}" | sudo debconf-set-selections
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# « DPkg::Lock::Timeout » : sur une VM fraîchement déployée, cloud-init tient
|
|
|
|
|
# encore le verrou d'apt — mesuré, « E: Could not get lock
|
|
|
|
|
# /var/lib/apt/lists/lock. It is held by process 996 (apt-get) », et le script
|
|
|
|
|
# mourait 40 secondes après le démarrage. apt sait ATTENDRE son tour depuis la
|
|
|
|
|
# 1.9 ; sans cette option il abandonne immédiatement.
|
|
|
|
|
apt_get() {
|
|
|
|
|
run sudo env DEBIAN_FRONTEND=noninteractive \
|
|
|
|
|
apt-get -o Dpkg::Options::=--force-confold \
|
|
|
|
|
-o DPkg::Lock::Timeout=600 -y "$@"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Attendre la fin de cloud-init AVANT de toucher à apt : c'est lui qui pose les
|
|
|
|
|
# paquets de la première mise en route. Son état final n'est PAS un critère —
|
|
|
|
|
# vérifié sur l'image Debian 13, où il rend « error » pour deux modules sans
|
|
|
|
|
# rapport (console-setup absent, update-locale) tout en ayant terminé son
|
|
|
|
|
# travail. On attend qu'il ait fini, pas qu'il soit content.
|
|
|
|
|
wait_cloud_init() {
|
|
|
|
|
command -v cloud-init >/dev/null 2>&1 || return 0
|
|
|
|
|
say " attente de la fin de cloud-init…"
|
|
|
|
|
if [ "${DRY}" = "1" ]; then
|
|
|
|
|
say " ${Yellow}[dry-run]${Color_Off} cloud-init status --wait"
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
timeout 600 cloud-init status --wait >/dev/null 2>&1 || true
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
install_pve() {
|
|
|
|
|
wait_cloud_init
|
|
|
|
|
preseed_debconf
|
|
|
|
|
# Réparer une transaction laissée à moitié par une exécution précédente :
|
|
|
|
|
# sans réponse à grub-pc, dpkg s'arrête au milieu et tout apt suivant
|
|
|
|
|
# refuse de travailler. Inoffensif quand rien n'est cassé.
|
|
|
|
|
if ! sudo dpkg -C >/dev/null 2>&1; then
|
|
|
|
|
say " paquets à moitié configurés : dpkg --configure -a"
|
|
|
|
|
run sudo env DEBIAN_FRONTEND=noninteractive dpkg --configure -a \
|
|
|
|
|
|| true
|
|
|
|
|
fi
|
|
|
|
|
# AVANT le premier apt : sur un second passage, le dépôt entreprise posé
|
|
|
|
|
# par proxmox-ve ferait échouer « apt update » (401) et rien n'irait plus
|
|
|
|
|
# loin — pas même la désactivation, si elle attendait la fin.
|
|
|
|
|
disable_enterprise
|
|
|
|
|
say "\n---- apt update ----"
|
|
|
|
|
apt_get update
|
|
|
|
|
# Le noyau d'abord, comme l'amont le prescrit : c'est lui qui porte les
|
|
|
|
|
# modules dont pve a besoin, et l'installer seul laisse une machine qui
|
|
|
|
|
# redémarre proprement même si la suite échoue.
|
|
|
|
|
if ! dpkg -s proxmox-default-kernel >/dev/null 2>&1; then
|
|
|
|
|
say "\n---- noyau Proxmox ----"
|
|
|
|
|
apt_get install proxmox-default-kernel
|
|
|
|
|
CHANGED=1
|
|
|
|
|
else
|
|
|
|
|
say " noyau Proxmox déjà posé"
|
|
|
|
|
fi
|
|
|
|
|
if ! dpkg -s proxmox-ve >/dev/null 2>&1; then
|
|
|
|
|
say "\n---- proxmox-ve, postfix, open-iscsi, chrony ----"
|
|
|
|
|
apt_get install proxmox-ve postfix open-iscsi chrony
|
|
|
|
|
CHANGED=1
|
|
|
|
|
# C'est CETTE installation qui vient de poser le dépôt entreprise :
|
|
|
|
|
# le désactiver tout de suite, avant que le ménage ne rappelle apt.
|
|
|
|
|
disable_enterprise
|
|
|
|
|
else
|
|
|
|
|
say " proxmox-ve déjà posé"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# --- 6. Ménage --------------------------------------------------------------
|
|
|
|
|
# os-prober ajoute au menu d'amorçage les systèmes trouvés sur les disques des
|
|
|
|
|
# VM invitées : sur un hyperviseur, c'est une liste de faux départs.
|
|
|
|
|
# Le noyau Debian, lui, n'a plus de raison d'être une fois celui de pve en
|
|
|
|
|
# place — et laissé par défaut, grub peut y revenir.
|
|
|
|
|
cleanup() {
|
|
|
|
|
if dpkg -s os-prober >/dev/null 2>&1; then
|
|
|
|
|
say " retrait d'os-prober"
|
|
|
|
|
apt_get remove os-prober
|
|
|
|
|
CHANGED=1
|
|
|
|
|
fi
|
|
|
|
|
[ "${PVE_KEEP_DEBIAN_KERNEL:-0}" = "1" ] && return 0
|
|
|
|
|
# Ne JAMAIS retirer le noyau Debian si celui de Proxmox n'est pas posé :
|
|
|
|
|
# la machine ne redémarrerait plus. Vérifié, pas supposé — une étape apt
|
|
|
|
|
# peut avoir échoué plus haut sans arrêter le reste.
|
|
|
|
|
if ! dpkg -s proxmox-default-kernel >/dev/null 2>&1; then
|
|
|
|
|
say " noyau Proxmox absent : le noyau Debian reste en place."
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
local metas
|
|
|
|
|
# Tout « linux-image-* » SAUF ceux de Proxmox. L'amont écrit
|
|
|
|
|
# « linux-image-6.12* », la version de trixie ; le motif large couvre les
|
|
|
|
|
# versions suivantes, et l'exclusion protège le noyau qu'on vient de
|
|
|
|
|
# poser — le retirer laisserait une VM qui n'amorce plus.
|
|
|
|
|
# « db:Status-Status » filtre les paquets RÉELLEMENT installés : dpkg
|
|
|
|
|
# connaît aussi ceux qu'il a désinstallés (« config-files »,
|
|
|
|
|
# « not-installed »), et les passer à apt donnait « is not installed, so
|
|
|
|
|
# not removed » — puis un CHANGED=1 qui réclamait un redémarrage inutile.
|
|
|
|
|
metas="$(dpkg-query -W -f '${Package} ${db:Status-Status}\n' \
|
|
|
|
|
'linux-image-*' 2>/dev/null \
|
|
|
|
|
| awk '$2 == "installed" {print $1}' \
|
|
|
|
|
| grep -vE 'pve|proxmox' || true)"
|
|
|
|
|
if [ -n "${metas}" ]; then
|
|
|
|
|
say " retrait du noyau Debian : $(echo "${metas}" | tr '\n' ' ')"
|
|
|
|
|
# shellcheck disable=SC2086
|
|
|
|
|
apt_get remove ${metas}
|
|
|
|
|
run sudo update-grub 2>/dev/null || true
|
|
|
|
|
CHANGED=1
|
|
|
|
|
fi
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# --- 6bis. Amorçage EFI de secours -----------------------------------------
|
|
|
|
|
# Une image cloud amorce par le CHEMIN DE SECOURS de l'UEFI —
|
|
|
|
|
# \EFI\BOOT\BOOTX64.EFI — parce qu'aucune entrée NVRAM ne la nomme. Les
|
|
|
|
|
# paquets grub de Proxmox y recopient bien leurs binaires, mais PAS le petit
|
|
|
|
|
# grub.cfg qui indique où trouver la vraie configuration. GRUB s'arrête alors
|
|
|
|
|
# sur son invite de secours « grub> » : ni menu, ni noyau.
|
|
|
|
|
#
|
|
|
|
|
# Vécu, capture d'écran à l'appui : après le premier redémarrage, la VM brûlait
|
|
|
|
|
# 100 % d'un cœur SANS lire une seule fois le disque, et l'écran affichait
|
|
|
|
|
# « starting Boot0001 UEFI Misc Device » suivi de « grub> ».
|
|
|
|
|
#
|
|
|
|
|
# On recopie le stub que Debian a généré pour son propre chemin : il porte
|
|
|
|
|
# l'UUID de la racine, donc il fonctionne depuis n'importe quel chargeur.
|
|
|
|
|
# « grub-install --removable » ferait la même chose en réécrivant le chargeur ;
|
|
|
|
|
# copier un fichier est plus sûr — cela ne touche pas au chemin qui marche.
|
|
|
|
|
fix_efi_fallback() {
|
|
|
|
|
local esp="${PVE_ESP:-/boot/efi}"
|
|
|
|
|
local secours="${esp}/EFI/BOOT"
|
[ADD] proxmox : déployer des VM sur un hôte Proxmox distant
Nouvelle entrée sous QEMU/KVM, avec l'équivalent de ses dix-sept commandes.
Toute la différence tient en une phrase : l'hyperviseur est ailleurs. On
choisit donc l'hôte — VM QEMU locale, adresse, ou ~/.ssh/config — et on le
vérifie : pveversion le prouve, id/sudo décident du privilège, et une clé
d'hôte inconnue s'enregistre par ssh-keyscan plutôt qu'en désactivant le
contrôle.
Quatre pièges trouvés sur un hôte réel. Une Proxmox installée sur Debian n'a
aucun pont : on en propose un INTERNE, car ajouter l'interface physique
déplace l'adresse de l'hôte et coupe la session — à distance, sans retour.
--- EN ---
A new entry under QEMU/KVM, with the counterpart of its seventeen commands.
The whole difference fits in one sentence: the hypervisor is elsewhere. So the
host is chosen — local QEMU VM, address, or ~/.ssh/config — and then checked:
pveversion proves it, id/sudo decide about privilege, and an unknown host key
is recorded with ssh-keyscan rather than by disabling the check.
Four traps found on a real host. A Proxmox installed on Debian has no bridge:
we offer an INTERNAL one, because adding the physical NIC moves the host
address and cuts the session — remotely, with no way back.
Assisted-by: Claude Opus 5
2026-08-23 05:54:38 -04:00
|
|
|
# sudo sur CHAQUE lecture. /boot/efi est une vfat montée « umask=077 » :
|
|
|
|
|
# root seul y entre, et un « [ -d ] » non privilégié y répond FAUX. Ce
|
|
|
|
|
# correctif ne faisait donc RIEN, en silence, et la VM retombait sur
|
|
|
|
|
# « grub> » au redémarrage suivant — vécu deux fois. Le glob du shell est
|
|
|
|
|
# aveugle pour la même raison : il faut énumérer avec sudo.
|
|
|
|
|
sudo test -d "${secours}" || return 0
|
|
|
|
|
sudo test -e "${secours}/grub.cfg" && return 0
|
|
|
|
|
local stub=""
|
|
|
|
|
# « || true » : quand aucun stub n'existe, grep ne trouve rien et rend 1
|
|
|
|
|
# — avec « set -o pipefail », l'affectation échoue et le script s'arrête
|
|
|
|
|
# AVANT d'avoir dit ce qui manque. Attrapé par un test, pas sur la machine
|
|
|
|
|
# réelle, où un stub existait et masquait le cas.
|
|
|
|
|
stub="$(sudo sh -c "ls ${esp}/EFI/*/grub.cfg 2>/dev/null" \
|
|
|
|
|
| grep -v '/EFI/BOOT/grub.cfg' | head -1 || true)"
|
[ADD] qemu : déployer Proxmox VE (amd64, arm64)
Proxmox ne publie aucune image cloud : son ISO est un installateur qui formate
le disque. On prend donc la voie que l'amont documente lui-même — Proxmox VE
sur Debian — depuis l'image cloud trixie, partagée avec un déploiement
Debian 13 au lieu d'être téléchargée deux fois.
s390x n'y est pas et n'y sera pas par cette voie : le dépôt n'a aucun index
binary-s390x. arm64 y est, officiel depuis PVE 9. Le catalogue le dit AVANT le
déploiement, au lieu d'échouer au premier apt.
Vérifié sur une VM réelle : pve-manager 9.2.11, noyau 7.0.14-12-pve, quatre
services actifs, interface web en HTTP 200.
--- EN ---
Proxmox publishes no cloud image: its ISO is an installer that formats the
disk. So we take the path upstream documents itself — Proxmox VE on Debian —
from the trixie cloud image, shared with a Debian 13 deployment instead of
being downloaded twice.
s390x is not there and will not be by this route: the repository has no
binary-s390x index. arm64 is, official since PVE 9. The catalog says so BEFORE
the deployment rather than failing at the first apt.
Verified on a real VM: pve-manager 9.2.11, kernel 7.0.14-12-pve, four services
active, web UI answering HTTP 200.
Assisted-by: Claude Opus 5
2026-08-23 03:28:03 -04:00
|
|
|
if [ -z "${stub}" ]; then
|
|
|
|
|
say "${Yellow}⚠${Color_Off} aucun grub.cfg à recopier sous ${esp} :" \
|
|
|
|
|
"vérifier l'amorçage avant de redémarrer."
|
|
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
say " amorçage de secours : $(dirname "${stub}" | xargs basename)/grub.cfg" \
|
|
|
|
|
"-> EFI/BOOT/"
|
|
|
|
|
run sudo cp "${stub}" "${secours}/grub.cfg"
|
|
|
|
|
CHANGED=1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# --- 7. Déroulé -------------------------------------------------------------
|
|
|
|
|
say "${Green}==>${Color_Off} Proxmox VE ${SUITE} sur ${ARCH}"
|
|
|
|
|
say "\n---- nom d'hôte et /etc/hosts ----"
|
|
|
|
|
fix_hosts
|
|
|
|
|
say "\n---- dépôt Proxmox ----"
|
|
|
|
|
add_repo
|
|
|
|
|
install_pve
|
|
|
|
|
say "\n---- ménage ----"
|
|
|
|
|
cleanup
|
|
|
|
|
say "\n---- amorçage ----"
|
|
|
|
|
fix_efi_fallback
|
|
|
|
|
|
|
|
|
|
IP="$(host_ip || echo localhost)"
|
|
|
|
|
if [ "${DRY}" = "1" ]; then
|
|
|
|
|
# Ne rien annoncer qui n'ait eu lieu : en dry-run, rien n'a été installé.
|
|
|
|
|
say "\n${Yellow}✓${Color_Off} dry-run terminé : rien n'a été changé."
|
|
|
|
|
say " Ce qui serait joignable ensuite : https://${IP}:8006"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
say "\n${Green}✓${Color_Off} Proxmox VE installé."
|
|
|
|
|
say " Interface web : ${Green}https://${IP}:8006${Color_Off}"
|
|
|
|
|
# Proxmox authentifie par PAM : c'est le root du système qui ouvre l'interface.
|
|
|
|
|
# Sur une image cloud il est VERROUILLÉ (« passwd -S root » rend « L »), donc
|
|
|
|
|
# l'interface est inutilisable tant qu'on ne lui a pas donné un mot de passe.
|
|
|
|
|
# Le dire ici plutôt que de laisser chercher devant un formulaire qui refuse.
|
|
|
|
|
if [ "$(sudo passwd -S root 2>/dev/null | awk '{print $2}')" = "L" ]; then
|
|
|
|
|
say " Compte : root, ${Yellow}sans mot de passe${Color_Off} —" \
|
|
|
|
|
"l'interface le refusera. À faire : ${Green}sudo passwd root${Color_Off}"
|
|
|
|
|
else
|
|
|
|
|
say " Compte : root (mot de passe du système)"
|
|
|
|
|
fi
|
|
|
|
|
say " Une VM Proxmox est un hyperviseur DANS une VM : ses propres invités"
|
|
|
|
|
say " demandent la virtualisation imbriquée à tous les étages."
|
|
|
|
|
|
|
|
|
|
if [ "${CHANGED}" = "0" ]; then
|
|
|
|
|
say "\n Rien n'a changé : pas de redémarrage."
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
if [ "${PVE_REBOOT:-0}" != "1" ]; then
|
|
|
|
|
say "\n${Yellow}⚠${Color_Off} Redémarrage NÉCESSAIRE pour amorcer le" \
|
|
|
|
|
"noyau Proxmox : sudo reboot"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
say "\n---- redémarrage pour amorcer le noyau Proxmox ----"
|
|
|
|
|
run sudo systemctl reboot
|