module

Beryl::CLI::Info

beryl info [host] [--usage] : inventaire des serveurs.

  • sans host : tableau récap de TOUS les hosts (gamme, CPU, RAM, disques) + total parc. Lecture HORS-LIGNE (depuis les host.yml alimentés par beryl scan — instantané, pas de réseau).
  • avec host : fiche détaillée d'un host.
  • --usage : utilisation LIVE (zpool/df/uptime via SSH) en plus.

Constants

ASCIIDOCTOR_PDF_TOOL = "crystal-asciidoctor-pdf"

URL du dépôt de l'outil de conversion PDF (message d'erreur s'il manque).

ASCIIDOCTOR_PDF_URL = "https://github.com/aloli-crystal/crystal-asciidoctor-pdf"
EXIT_OK = 0
EXIT_USAGE = 1
HTML_SORT_JS = "document.querySelectorAll(\"table.sortable\").forEach(function (table) {\n var heads = table.tHead.rows[0].cells;\n Array.prototype.forEach.call(heads, function (th, idx) {\n th.addEventListener(\"click\", function () {\n var asc = th.getAttribute(\"data-asc\") !== \"true\";\n Array.prototype.forEach.call(heads, function (o) { o.removeAttribute(\"data-asc\"); });\n th.setAttribute(\"data-asc\", asc ? \"true\" : \"false\");\n var tb = table.tBodies[0];\n var rows = Array.prototype.slice.call(tb.rows);\n rows.sort(function (a, b) {\n var ca = a.cells[idx], cb = b.cells[idx];\n if (!ca || !cb) return 0;\n var xa = ca.getAttribute(\"data-sort\"), xb = cb.getAttribute(\"data-sort\");\n var num = function (v) { return v.trim() !== \"\" && !isNaN(Number(v)); };\n var r;\n if (xa !== null && xb !== null && !(num(xa) && num(xb))) {\n // Clés textuelles (IPv6 en forme complète) : ordre des caractères.\n r = xa < xb ? -1 : (xa > xb ? 1 : 0);\n } else {\n var na = parseFloat(xa !== null ? xa : ca.textContent);\n var nb = parseFloat(xb !== null ? xb : cb.textContent);\n if (!isNaN(na) && !isNaN(nb)) { r = na - nb; }\n else { r = ca.textContent.trim().localeCompare(cb.textContent.trim()); }\n }\n return asc ? r : -r;\n });\n rows.forEach(function (row) { tb.appendChild(row); });\n });\n });\n});"

Tri des tableaux .sortable au clic sur l'en-tête (numérique via data-sort, sinon alphabétique ; bascule asc/desc). Vanilla JS, aucune dépendance.

HTML_STYLE = ":root { --fg:#1c1e21; --muted:#65676b; --line:#dfe1e5; --accent:#1a73e8; --warn-bg:#fff4e5; --warn-bd:#f0a020; --low:#fde8e8; }\n* { box-sizing:border-box; }\nbody { font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif; color:var(--fg); margin:0 auto; max-width:1100px; padding:1.5rem; line-height:1.45; }\nh1 { font-size:1.6rem; margin:.2rem 0 .4rem; }\nh2 { font-size:1.15rem; margin:1.6rem 0 .5rem; border-bottom:2px solid var(--line); padding-bottom:.2rem; }\na { color:var(--accent); text-decoration:none; } a:hover { text-decoration:underline; }\np.meta { color:var(--muted); font-size:.85rem; margin:.2rem 0 1rem; }\ntable { border-collapse:collapse; width:100%; font-size:.9rem; margin:.3rem 0; }\nth, td { border:1px solid var(--line); padding:.35rem .55rem; text-align:left; }\nthead th { background:#f5f6f7; position:sticky; top:0; }\ntable.sortable thead th { cursor:pointer; user-select:none; }\ntable.sortable thead th::after { content:\"\\2195\"; color:var(--muted); font-size:.75em; margin-left:.35em; }\ntable.sortable thead th[data-asc=\"true\"]::after { content:\"\\2191\"; color:var(--accent); }\ntable.sortable thead th[data-asc=\"false\"]::after { content:\"\\2193\"; color:var(--accent); }\ntable.kv th { background:#f5f6f7; width:14rem; }\np.notes { white-space:pre-wrap; }\ntbody tr:nth-child(even) { background:#fafbfc; }\ntr.low td { background:var(--low); }\ndiv.warn { background:var(--warn-bg); border-left:4px solid var(--warn-bd); padding:.6rem .9rem; margin:1rem 0; border-radius:3px; }\ndiv.warn ul { margin:.4rem 0 0; }"

Feuille de style du site (écrite une fois dans info/style.css).

Class methods

build_adoc(hosts : Array(Beryl::Config::ResolvedHost), scope : String | Nil, usage : Hash(String, Array(UsageRow) | Nil) | Nil = nil, os_map : Hash(String, String) | Nil = nil, generated : String = Beryl.format_timestamp(Time.local)) : String

Document AsciiDoc récapitulatif : synthèse + matériel + réseau vRack + (si usage fourni) utilisation disque LIVE. Régénérable : beryl info --adoc [--usage] <périmètre> > inventaire.adoc.

Source
build_html_host(host : Beryl::Config::ResolvedHost, rows : Array(UsageRow) | Nil, show_usage : Bool, generated : String, live_os : String | Nil = nil) : String

Page détaillée d'UN serveur (fiche + son utilisation disque). live_os = OS déduit du serveur (uname -sr) ; remplace l'os: théorique du YAML.

Source
build_html_index(hosts : Array(Beryl::Config::ResolvedHost), scope : String | Nil, usage : Hash(String, Array(UsageRow) | Nil) | Nil, generated : String, os_map : Hash(String, String) | Nil = nil) : String

Page d'index : synthèse + alertes + tableaux TRIABLES (clic sur l'en-tête), avec lien vers la page de chaque serveur.

Source
free_pct(used : String) : String

Convertit un pourcentage d'UTILISÉ (ex. zpool cap, df Capacity) en pourcentage de LIBRE (36% → 64%). Renvoie la chaîne d'origine si on n'arrive pas à la lire (jamais censé arriver).

Source
freebsd_upgrade_hint(live_os : String, by_branch : Hash(Int32, String)) : String | Nil

À partir d'un OS live (ex. « FreeBSD 15.0-RELEASE-p10 ») et des dernières RELEASE par branche, renvoie un flag d'upgrade (« ↑ 15.1-RELEASE dispo » et/ou « branche 16.x dispo ») ou nil si à jour / version illisible. Pur.

Source
gather_pkg_map(host : Beryl::Config::ResolvedHost, system_ssh : Bool, rquery : Bool) : Tuple(Hash(String, String) | Nil, String | Nil)

Map paquet→version pour un host (live SSH). rquery: true interroge le dépôt (versions DISPONIBLES) au lieu de l'installé. Tente les users comme gather_usage. {nil, nil} si injoignable. csh-safe (pas de redir Bourne).

Source
hesc(s : String) : String

Échappe une valeur pour insertion HTML (texte ou attribut).

Source
info_dir(config_root : String, scope : String | Nil, hosts : Array(Beryl::Config::ResolvedHost)) : String

Répertoire de sortie : <config>/<société>/info (périmètre société) ou <config>/<société>/<domaine>/info (périmètre domaine ou host). Régénéré à chaque lancement (comme la gem coverage).

Source
ipv4_sort_key(ip : String | Nil) : Int64

Clé de tri d'une IPv4 (data-sort) : un nombre, octet par octet (192.168.42.4 → 192168042004) ; sans adresse, -1 (en tête). Pur.

Source
ipv6_sort_key(ip : String | Nil) : String

Clé de tri d'une IPv6 (data-sort) : la forme COMPLÈTE, 8 groupes de 4 chiffres hexadécimaux, comparée comme du texte (2001:41d0:364:4100::1 → 2001:41d0:0364:4100:0000:0000:0000:0001) ; sans adresse, chaîne vide. Pur.

Source
ovh_block(service_name : String, commercial : String | Nil, rack : String | Nil, ipv4 : String | Nil, ipv6 : String | Nil, price : String | Nil, ipv6_gateway : String | Nil = nil, datacenter : String | Nil = nil, ipv6_block : String | Nil = nil) : Array(String)

Lignes du bloc ovh: (service_name + gamme + datacentre + rack + IPs + prix/mois).

Source
packages_in_dirs(dirs : Array(String)) : Array(String)

Extrait (trié, dédupliqué) les paquets des steps pkg-install de tous les *.recipe.yml des dossiers donnés. Pur (filesystem) → testable.

Source
parse_df(output : String) : Array(UsageRow)

Parse df -h → utilisation par POOL : un dataset ZFS (zroot/..., zdata) est regroupé sous son pool (segment avant le 1er /), en gardant le montage le plus court (racine du pool) ; un device classique (UFS/ext4) = son montage. Pseudo-FS (devfs/tmpfs/proc…) filtrés. Fonction PURE (sans sudo côté hôte).

Source
parse_pkg_lines(output : String) : Hash(String, String)

Parse une sortie pkg query/rquery '%n %v' (une ligne « nom version » par paquet) en map nom→version. Pur → testable.

Source
parse_zfs_roots(output : String) : Array(UsageRow)

Parse zfs list -Hp -d 0 -o name,used,avail (racine de chaque pool, en octets) → une ligne par pool. Libre = AVAIL de ZFS, comme df -h : ce qu'on peut encore écrire, réserve de sécurité (1/32 du pool), parité RAIDZ et réservations déduites. (FREE de zpool list les compte encore : zora, 27 oct. 2026, 50,8 Go « libres » pour 21,5 Go écrivables.) Taille = utilisé + libre, comme df. Colonne % = le LIBRE.

Source
pct_int(s : String) : Int32 | Nil

Entier d'un pourcentage (64% → 64), ou nil.

Source
recipe_packages(config_root : String, hosts : Array(Beryl::Config::ResolvedHost)) : Array(String)

Paquets « gérés » = union des packages: des steps pkg-install de TOUTES les recettes du chemin de recherche des hosts du périmètre (générique + privé société). C'est la liste « logiciels nécessaires aux applications », fournie par la mise en œuvre des recettes (pas une liste à maintenir à part).

Source
run(config_root : String, args : Array(String)) : Int32
Source
scoped_hosts(root : Beryl::Config::Root, scope : String | Nil) : Array(Beryl::Config::ResolvedHost)

--refresh [périmètre] : pour chaque host OVH du périmètre (société, domaine, host, ou tous), récupère gamme + specs via l'API OVH et les écrit dans le host.yml. AUCUN SSH, aucune modif serveur. Le périmètre filtre par société / domaine / fqdn / nom court. Hosts résolus du périmètre (société / domaine / fqdn / nom court ; vide = tous). Public : réutilisé par beryl update/upgrade pour cibler une portée.

Source
size_bytes(s : String) : Int64

Convertit une taille humaine (14T, 460G, 512K, 8.0G) en octets, pour un tri numérique correct des colonnes (data-sort). 0 si illisible.

Source
sort_volumes(rows : Array(UsageRow)) : Array(UsageRow)

Ordre des volumes : zroot TOUJOURS en premier, puis les autres pools par ordre alphabétique (zdata, ztank…).

Source
upsert_block(content : String, key : String, block : Array(String)) : String

Remplace le bloc top-level key: (sa ligne + les lignes indentées qui suivent) par block, en PRÉSERVANT tout le reste (commentaires inclus). Si le bloc est absent, l'ajoute en fin de fichier (ligne vide de séparation).

Source
usage_attempts(host : Beryl::Config::ResolvedHost, system_ssh : Bool) : Array(Tuple(String | Nil, String))

Tentatives de connexion {user, cible} ordonnées :

  • --system-ssh : on IMITE ssh <fqdn> — on vise le FQDN et on laisse VOTRE ~/.ssh/config résoudre user/hostname/proxyjump. user nil = login par défaut (1ʳᵉ tentative), puis deploy en repli (hosts beryl).
  • clé beryl : on vise l'hôte SSH réel (service name / IP vRack) en connect_user (admin…) puis deploy.
Source
zfs_size(bytes : Int64) : String

Octets → forme de zfs list -h (puissances de 1024, 3 chiffres significatifs : 944G, 21.5G, 2.35G). Pur.

Source

Nested types