[FIX] script qemu: use /releases/ Ubuntu URLs, drop EOL versions, quiet crypt

Interim Ubuntu releases (24.10, 25.04) were removed from cloud-images
/current/ once EOL, so their image URL 404'd. Switch the Ubuntu image
URL to /releases/<version>/release/ubuntu-<version>-server-cloudimg-
<arch>.img, which stays available for every published release, and drop
the EOL interim versions from the catalogue (keep supported LTS + 25.10
+ 26.04).

Also:
- silence the "crypt is deprecated" DeprecationWarning (openssl fallback
  already covers Python 3.13+);
- a 404 during download now reports "image not found (EOL/removed), pick
  a supported LTS" instead of blaming connectivity;
- add a NoCloud network-config (DHCP on e*) to the seed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mathieu Benoit 2026-07-19 08:41:24 +00:00
parent 19e3801d2c
commit 990c81c0e7
2 changed files with 62 additions and 11 deletions

View file

@ -56,7 +56,9 @@ import sys
import tempfile
import textwrap
import time
import urllib.error
import urllib.request
import warnings
from pathlib import Path
# --------------------------------------------------------------------------- #
@ -67,12 +69,13 @@ from pathlib import Path
# DÉFAUT — la VM démarre au plus juste sans gaspiller la RAM de l'hôte.
# --codename / --osinfo / --memory / --disk-size surchargent au besoin.
# --------------------------------------------------------------------------- #
# NB : les versions intermédiaires (non-LTS) sont RETIRÉES du miroir
# cloud-images une fois EOL (leur /current/ renvoie 404). On ne garde donc
# que les LTS + les intermédiaires encore publiées. À réviser au fil du temps.
UBUNTU_VERSIONS: dict[str, tuple[str, str, int, str]] = {
"20.04": ("focal", "ubuntu20.04", 2048, "5G"),
"22.04": ("jammy", "ubuntu22.04", 2048, "10G"),
"24.04": ("noble", "ubuntu24.04", 3072, "20G"),
"24.10": ("oracular", "ubuntu24.10", 3072, "20G"),
"25.04": ("plucky", "ubuntu25.04", 3072, "20G"),
"25.10": ("questing", "ubuntu25.10", 3072, "20G"),
"26.04": ("resolute", "ubuntu26.04", 3072, "20G"),
}
@ -142,9 +145,12 @@ def image_candidates(
pour Debian ; une seule URL pour Ubuntu/Fedora."""
a = distro_arch(distro, arch)
if distro == "ubuntu":
# /releases/<version>/release/ : présent pour toutes les versions
# publiées (redirige vers l'image datée), contrairement à /current/
# qui disparaît quand une version intermédiaire devient EOL.
return [
f"{CLOUD_IMG_BASE}/{code}/current/"
f"{code}-server-cloudimg-{a}.img"
f"{CLOUD_IMG_BASE}/releases/{version}/release/"
f"ubuntu-{version}-server-cloudimg-{a}.img"
]
if distro == "debian":
return [
@ -184,7 +190,7 @@ def default_image_name(distro: str, code: str, arch: str, version: str) -> str:
"""Nom de fichier local pour le cache d'image."""
a = distro_arch(distro, arch)
if distro == "ubuntu":
return f"{code}-server-cloudimg-{a}.img"
return f"ubuntu-{version}-server-cloudimg-{a}.img"
if distro == "debian":
return f"debian-{version}-genericcloud-{a}.qcow2"
return f"fedora-cloud-{version}-{a}.qcow2"
@ -628,6 +634,7 @@ def download_image(
)
tmp = dest.with_suffix(dest.suffix + ".part")
errors = []
had_404 = False
for i, url in enumerate(urls, 1):
tag = "" if len(urls) == 1 else f" (miroir {i}/{len(urls)})"
print(f" Téléchargement{tag} : {url}", flush=True)
@ -635,15 +642,27 @@ def download_image(
_download_one(url, tmp, timeout)
tmp.replace(dest)
return
except Exception as exc: # réseau/timeout : on tente le miroir suivant
except urllib.error.HTTPError as exc: # image absente sur ce miroir
tmp.unlink(missing_ok=True)
had_404 = had_404 or exc.code == 404
print(f"\n Échec : HTTP {exc.code}", flush=True)
errors.append(f"{url} -> HTTP {exc.code}")
except Exception as exc: # réseau/timeout : miroir suivant
tmp.unlink(missing_ok=True)
print(f"\n Échec : {exc}", flush=True)
errors.append(f"{url} -> {exc}")
hint = (
"\n Image introuvable (404) : cette version est probablement EOL et"
" a été retirée du miroir. Choisissez une version LTS encore"
" supportée (voir --list-images)."
if had_404
else "\n Vérifiez la connectivité (IPv6 ?), réessayez plus tard, ou"
" fournissez un chemin d'image local en argument positionnel."
)
sys.exit(
"\nÉchec du téléchargement depuis tous les miroirs :\n "
+ "\n ".join(errors)
+ "\n Vérifiez la connectivité (IPv6 ?), réessayez plus tard, ou "
"fournissez un chemin d'image local en argument positionnel."
+ hint
)
@ -692,7 +711,11 @@ def verify_sha256(url: str, image: Path, dry_run: bool) -> None:
def hash_password(plain: str) -> str:
"""Hash SHA-512 crypt ($6$...) via crypt (< 3.13) ou openssl en repli."""
try:
import crypt # supprimé en Python 3.13
with warnings.catch_warnings():
# crypt est déprécié (retiré en 3.13) : on masque l'avertissement,
# le repli openssl couvre les versions récentes de Python.
warnings.simplefilter("ignore", DeprecationWarning)
import crypt
return crypt.crypt(plain, crypt.mksalt(crypt.METHOD_SHA512))
except (ImportError, AttributeError):
@ -761,6 +784,20 @@ def build_cloud_config(
return "\n".join(lines) + "\n"
# network-config (cloud-init v2) : DHCP sur toute interface « e* ». Les images
# Debian genericcloud ne configurent pas toujours le réseau sans ça (le NIC
# reste down -> pas d'IP), contrairement à Ubuntu. Inoffensif pour Ubuntu.
NETWORK_CONFIG = (
"version: 2\n"
"ethernets:\n"
" primary:\n"
" match:\n"
' name: "e*"\n'
" dhcp4: true\n"
" dhcp6: false\n"
)
def build_seed(
cloud_cfg: str, hostname: str, seed_dest: Path, runner: Runner
) -> None:
@ -771,17 +808,30 @@ def build_seed(
tmp_path = Path(tmp)
ud = tmp_path / "user-data"
md = tmp_path / "meta-data"
nc = tmp_path / "network-config"
local_iso = tmp_path / "seed.iso"
if runner.dry_run:
print(" [dry-run] user-data qui serait généré :")
print(textwrap.indent(cloud_cfg, " "))
print(" [dry-run] network-config :")
print(textwrap.indent(NETWORK_CONFIG, " "))
else:
ud.write_text(cloud_cfg)
md.write_text(meta_data)
nc.write_text(NETWORK_CONFIG)
if runner.dry_run or shutil.which("cloud-localds"):
runner.run(["cloud-localds", str(local_iso), str(ud), str(md)])
runner.run(
[
"cloud-localds",
"--network-config",
str(nc),
str(local_iso),
str(ud),
str(md),
]
)
else:
need_tool("genisoimage")
runner.run(
@ -795,6 +845,7 @@ def build_seed(
"-rock",
str(ud),
str(md),
str(nc),
]
)

View file

@ -805,7 +805,7 @@ class TODO:
# deploy_qemu.py ; ceci ne sert qu'au sélecteur interactif.
_QEMU_DISTROS = {
"ubuntu": (
["20.04", "22.04", "24.04", "24.10", "25.04", "25.10", "26.04"],
["20.04", "22.04", "24.04", "25.10", "26.04"],
"24.04",
),
"debian": (["11", "12", "13"], "12"),