class

Beryl::Bootstrap::QemuInRescue

Inherits Reference < Object

Bootstrap complet FreeBSD via mfsBSD-in-QEMU + post-install no-chroot.

Flux validé manuellement sur loulou le 21 avril 2026 :

. SSH rescue Linux → apt install qemu-system-x86 ovmf sshpass curl . Download mfsBSD SE (cache) . NOGO si BSD déjà sur le disque cible . Upload rescue-run-vm.sh via scp (évite Process.run stdin-pipe) . Exécute le driver shell sur le rescue (UN seul ssh outer, zéro nested côté Crystal → zéro hang macOS). Le driver : .. systemd-run QEMU UEFI (survit à la fermeture ssh) .. attend SSH mfsBSD .. pré-fetch txz + bsdinstall PRÉAMBULE SEUL (pas de post-install chroot) .. remonte ZFS + post-install HORS chroot ciblant /mnt (contourne Capsicum) .. unmount + poweroff . OVH API boot_from_disk + wait SSH FreeBSD (admin user)

Voir ADR-013 pour la justification détaillée du pattern no-chroot.

Constants

DEFAULT_MFSBSD_URL_TEMPLATE = "https://github.com/mmatuska/mfsbsd/releases/latest/download/mfsbsd-se-__VERSION_MFS__-RELEASE-amd64.iso"

Template URL GitHub releases (source officielle actuelle mfsBSD). L'endpoint /releases/latest/download/<asset> redirige toujours vers la release la plus récente — pas besoin de tag à maintenir.

INSTALLERCFG = "#{WORK_DIR}/installerconfig"
MFSBSD_ROOT_PASSWORD = "mfsroot"
OVMF_CODE_SOURCE = "/usr/share/OVMF/OVMF_CODE_4M.fd"
OVMF_VARS_PATH = "#{WORK_DIR}/vars.fd"
OVMF_VARS_SOURCE = "/usr/share/OVMF/OVMF_VARS_4M.fd"
QEMU_MAX_RUNTIME = 45.minutes
QEMU_PATTERN = "qemu-system-x86_64.*mfsbsd-se-"

Matche tous les mfsBSD-SE quelle que soit leur version / extension (.iso côté GitHub, .img côté ancien vx.sk).

QEMU_SERIAL = "#{WORK_DIR}/qemu-serial.log"
REBOOT_GRACE_PERIOD = 30.seconds
RESCUE_RUN_VM_PATH = "#{WORK_DIR}/rescue-run-vm.sh"
SSH_POLL_INTERVAL = 15.seconds
SSH_WAIT_TIMEOUT = 30.minutes
TEMPLATE_INSTALLERCONFIG = {{ read_file("/tmp/tmp.gEHJHE/src/src/beryl/bootstrap/templates/installerconfig.sh") }}
TEMPLATE_RESCUE_RUN_VM = {{ read_file("/tmp/tmp.gEHJHE/src/src/beryl/bootstrap/templates/rescue-run-vm.sh") }}
VALID_INSTALL_TYPES = ["distribution_sets", "packages"] of ::String
VALID_RAID = ["stripe", "mirror", "raidz", "raidz2", "raidz3"] of ::String
VM_BOOT_TIMEOUT = 3.minutes
VM_BSDINSTALL_LG = "/tmp/bsdinstall.log"
VM_INSTALLERCFG = "/tmp/installerconfig"
VM_SSH_HOST = "127.0.0.1"
VM_SSH_PORT = 2223
WORK_DIR = "/root/beryl-test"

Constructors

new(rescue_conn : SSH::Connection, disks : Array(String), hostname : String, users : Array(UserSpec), freebsd_version : String, mfsbsd_version : String, abi : String, raid : String = "stripe", timezone : String = "Europe/Paris", iso_url : String | Nil = nil, pool_name : String = "zroot", swap_gb : Int32 = 4, qemu_ram_mb : Int32 = 4096, qemu_cpus : Int32 = 4, installed_user : String = "admin", installed_port : Int32 = 22, ovh_client : OvhApi::Client | Nil = nil, ovh_service_name : String | Nil = nil, dedibox_client : DediboxApi::Client | Nil = nil, dedibox_server_id : Int32 | Nil = nil, scaleway_client : ScalewayApi::Client | Nil = nil, scaleway_server_id : String | Nil = nil, scaleway_zone : String | Nil = nil, packages : Array(String) = [] of String, sudoers : Array(String) = [] of String, install_type : String = "distribution_sets", data_pools : Array(DataPoolSpec) = [] of DataPoolSpec, follow_hint_host_name : String | Nil = nil)
Source

Class methods

default_mfsbsd_url(mfsbsd_version : String) : String
Source
timestamp
Source

Instance methods

all_qemu_disks

Ordre global des disques passés à QEMU : boot d'abord, puis pools data dans l'ordre de déclaration. Les index QEMU correspondent : vtbd0 = mfsBSD, vtbd1..vtbd(N) = boot, vtbd(N+1).. = data.

Source
data_pools
Source
data_pools_script

Script shell (non base64) qui crée les pools data sous /mnt via zpool create. Vide si aucun pool data. Pour chaque pool :

zpool create -f -R /mnt -m <mountpoint> <nom> <vdev...> zpool set cachefile=/mnt/boot/zfs/zpool.cache <nom>

-R /mnt = altroot : les cache files ZFS écrivent sous /mnt (bon emplacement au reboot bare metal). -f car les disques sont neufs, mais zpool create chipote parfois sur résidus.

Le zpool set cachefile est INDISPENSABLE : sans lui, l'export final (zpool export -a) retire le pool du cache, et au reboot bare-metal FreeBSD ne retrouve plus que zroot dans /boot/zfs/zpool.cache. Le pool data existe sur disque (visible via zpool import) mais doit être importé à la main. Constaté terrain quantas.aloli.net 24 avril 2026.

Pointer vers /mnt/boot/zfs/zpool.cache (le cachefile du système cible, dans zroot altroot /mnt) garantit qu'au reboot, le rc.d/zfs du FreeBSD installé importe les deux pools.

Pools chiffrés : si encryption_key_hex est posé, on génère :

  • Un bloc { ... } qui décode la clé base64 dans une variable locale KEY (jamais en argument de commande).
  • zpool create -O encryption=on -O keyformat=hex -O keylocation=prompt … avec la clé fournie sur stdin.
  • unset KEY pour libérer la mémoire shell après l'usage.
  • PAS de zpool set cachefile : on EXCLUT le pool chiffré du cache pour qu'au reboot bare-metal, FreeBSD ne tente pas d'importer un pool dont la clé n'est pas chargée. L'opérateur fera beryl unlock (qui appelle zpool import + zfs load-key). Voir zpool-encryption-architecture.adoc § « Au bootstrap ».

Le script complet est ensuite encodé en base64 et exécuté côté VM via bash -c "$(echo ... | base64 -d)" — la clé hex n'apparaît jamais dans la ligne de commande.

Source
data_pools_script_b64

Version base64 du script pour injection dans le template shell (évite les problèmes de quoting). Vide si aucun pool data.

Source
dedibox_client
Source
dedibox_server_id
Source
disks
Source
follow_hint_host_name

Nom à suggérer dans le hint beryl follow-install <name> (typiquement FQDN ou <société>/<host>). nil → fallback « <host> ».

Source
freebsd_version
Source
hostname
Source
install_type
Source
installed_port
Source
installed_user
Source
iso_url

Alias rétrocompat — ancien paramètre iso_url.

Source
mfsbsd_local_path

Chemin local (côté rescue) où l'image mfsBSD SE est téléchargée. Inclut la version pour qu'un changement de version upstream (ex: 14.2 → 15.0) force un nouveau téléchargement au lieu de réutiliser un cache périmé d'une version précédente. L'extension provient de l'URL (.iso GitHub, .img vx.sk).

Source
mfsbsd_url
Source
mfsbsd_version
Source
ovh_client
Source
ovh_service_name
Source
packages
Source
pool_name
Source
qemu_cpus
Source
qemu_ram_mb
Source
qemu_target_disks_args

Args -drive pour chaque disque cible passthrough dans QEMU. Inclut boot ET pools data : on veut que la VM voie tout pour créer les pools data post-install dans le même qemu-system-x86_64. Chaque disque devient un vtbd* dans la VM (vtbd0=mfsBSD, vtbd1+=cibles).

Source
raid
Source
render_installerconfig

Rend l'installerconfig minimaliste (préambule seul, pas de chroot). ZFSBOOT_DISKS : dans la VM QEMU, les disques passthrough sont vtbd1, vtbd2, … (vtbd0 est l'image mfsBSD elle-même). Ordre = ordre d'apparition dans le -drive côté QEMU.

Source
render_rescue_run_vm

Rend le driver shell. Tous les placeholders de la template sont substitués ici (noms mfsbsd, OVMF, QEMU args, users, packages, sudoers).

Source
rescue_conn
Source
sudoers
Source
sudoers_base64

Contenu base64 du fichier sudoers (chaque ligne == une règle). Vide si aucune règle → le driver shell skip la création du fichier.

Source
swap_gb
Source
target_disk
Source
timezone
Source
users
Source

Nested types