Compare commits

..

No commits in common. "c4f7d7dcbd2bcd12ffa172d6f725267bd76db557" and "19e3801d2c386338d87d08600ab071c812dbd974" have entirely different histories.

2 changed files with 14 additions and 68 deletions

View file

@ -56,9 +56,7 @@ import sys
import tempfile
import textwrap
import time
import urllib.error
import urllib.request
import warnings
from pathlib import Path
# --------------------------------------------------------------------------- #
@ -69,13 +67,12 @@ 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"),
}
@ -145,12 +142,9 @@ 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}/releases/{version}/release/"
f"ubuntu-{version}-server-cloudimg-{a}.img"
f"{CLOUD_IMG_BASE}/{code}/current/"
f"{code}-server-cloudimg-{a}.img"
]
if distro == "debian":
return [
@ -190,7 +184,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"ubuntu-{version}-server-cloudimg-{a}.img"
return f"{code}-server-cloudimg-{a}.img"
if distro == "debian":
return f"debian-{version}-genericcloud-{a}.qcow2"
return f"fedora-cloud-{version}-{a}.qcow2"
@ -634,7 +628,6 @@ 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)
@ -642,27 +635,15 @@ def download_image(
_download_one(url, tmp, timeout)
tmp.replace(dest)
return
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
except Exception as exc: # réseau/timeout : on tente le 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)
+ hint
+ "\n Vérifiez la connectivité (IPv6 ?), réessayez plus tard, ou "
"fournissez un chemin d'image local en argument positionnel."
)
@ -711,11 +692,7 @@ 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:
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
import crypt # supprimé en Python 3.13
return crypt.crypt(plain, crypt.mksalt(crypt.METHOD_SHA512))
except (ImportError, AttributeError):
@ -784,20 +761,6 @@ 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:
@ -808,30 +771,17 @@ 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",
"--network-config",
str(nc),
str(local_iso),
str(ud),
str(md),
]
)
runner.run(["cloud-localds", str(local_iso), str(ud), str(md)])
else:
need_tool("genisoimage")
runner.run(
@ -845,7 +795,6 @@ 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", "25.10", "26.04"],
["20.04", "22.04", "24.04", "24.10", "25.04", "25.10", "26.04"],
"24.04",
),
"debian": (["11", "12", "13"], "12"),
@ -1083,13 +1083,10 @@ class TODO:
if os.path.exists(cfg):
with open(cfg, encoding="utf-8") as fh:
existing = fh.read()
# Retire un ancien bloc du même Host (ses lignes indentées). Pas de
# drapeau DOTALL : « . » ne doit PAS traverser les sauts de ligne,
# sinon .* engloutirait tout jusqu'à la fin du fichier et effacerait
# les entrées suivantes.
# Retire un ancien bloc du même Host (jusqu'au prochain Host / EOF).
pattern = re.compile(
rf"(?m)^[ \t]*Host[ \t]+{re.escape(host)}[ \t]*\n"
r"(?:[ \t]+[^\n]*\n?)*"
rf"(?ms)^[ \t]*Host[ \t]+{re.escape(host)}[ \t]*\n"
r"(?:[ \t]+.*\n?)*"
)
existing = pattern.sub("", existing).rstrip("\n")
block = (