module

Beryl::CLI::DnsSetup

Met en place les informations de nommage d'un serveur OVH fraîchement reçu pour qu'on puisse ensuite l'appeler par son nom custom et oublier son service_name OVH :

  1. Record A dans la zone DNS custom (ex. aloli.net) pointant directement vers l'IPv4 du serveur.
  2. Record AAAA dans la même zone pointant vers l'IPv6 (si IPv6 détectée, sinon étape ignorée).
  3. Refresh de la zone (OVH l'exige pour propager).
  4. Reverse DNS sur IPv4 + IPv6 → FQDN custom (loulou.aloli.net.). Les deux reverses pointent vers le MÊME FQDN, pas des variantes type loulou-v4/loulou-v6.
  5. Renommage du « display name » du serveur côté panel OVH.

Philippe 22 avril 2026 (terrain) : « Dans la zone aloli.net : deux CNAME ipv4 et ipv6 [A + AAAA en fait]. Chez OVH le reverse est un champs le FQDN. Donc deux CNAME dans la zone et un seul (FQDN) chez OVH. »

Route choisie vs CNAME-vers-service_name : poser A + AAAA en direct signifie que le FQDN custom reste valide même si OVH change le service_name ou si la résolution de leur zone a un hoquet. En contrepartie il faut mettre à jour aloli.net si l'IP du serveur change — ce qui ne se produit pas sans intervention manuelle.

Implémentation : 100% via le shard ovh-api 0.3.0 qui expose client.domains (records, refresh, ensure_record idempotent), client.dedicated_servers.update(display_name:) et client.ips.set_reverse. Plus aucun client.call("GET", "/domain/...") en bas niveau.

Toutes les actions sont idempotentes côté shard : avant de créer un record, on vérifie qu'il n'existe pas déjà. Un relancement après interruption ne crée pas de doublons.

Constants

REVERSE_MAX_ATTEMPTS = 12

Nombre max de tentatives et délai entre chaque quand OVH refuse le reverse parce que la résolution forward n'a pas encore propagé. 12 × 10s = 2 min, suffisant pour que les NS OVH prennent en compte un record A/AAAA fraîchement refresh.

REVERSE_RETRY_DELAY = 10.seconds

Class methods

apply!(client : OvhApi::Client, plan : Plan, logger : Proc(String, Nil)) : Nil

Exécute le plan. Chaque étape est idempotente.

Ordre : 1-2. A + AAAA dans la zone custom 3. refresh zone (OVH exige cet appel pour propager) 4. rename OVH displayName : instantané côté panel, pas besoin que quoi que ce soit d'autre soit prêt. Fait avant le reverse pour que l'opérateur voie immédiatement le nouveau nom dans le panel, même si la propagation DNS coince. 5-6. reverses v4/v6 : le shard retry en interne si la zone n'est pas encore propagée côté résolveurs OVH.

Source
build_plan(client : OvhApi::Client, service_name : String, short_name : String, zone : String) : Plan

Récupère les infos nécessaires (IPs, displayName actuel) pour construire un Plan cohérent. Utilise client.dedicated_servers.info et client.dedicated_servers.ips du shard ovh-api 0.3.0.

Source
derive_ipv6_address(base : String, cidr : String) : String

À partir d'un bloc CIDR IPv6 (ex. "2001:41d0:2:6e01::/64"), déduit l'adresse usable habituelle chez OVH : base + "::1".

Source
ensure_record(client : OvhApi::Client, zone : String, field_type : String, sub_domain : String, target : String, logger : Proc(String, Nil)) : Nil

Crée / met à jour / laisse en place un record DNS de façon idempotente. Logique portée dans le shard ovh-api 0.3.0 via client.domains.ensure_record.

Source
refresh_zone(client : OvhApi::Client, zone : String, logger : Proc(String, Nil)) : Nil
Source
set_reverse_if_needed(client : OvhApi::Client, ip : String, reverse : String, logger : Proc(String, Nil)) : Nil

Pose le reverse DNS. L'API OVH vérifie AVANT d'accepter le reverse que le forward (FQDN → IP) résout déjà côté leurs résolveurs. Ce contrôle échoue typiquement juste après domains.refresh : la zone vient d'être mise à jour mais la propagation prend quelques dizaines de secondes.

Symptôme constaté le 23 avril 2026 sur loulou : HTTP 400 : "Cannot check if loulou.aloli.net. resolves to 51.83.6.208"

Parade : retry avec backoff sur ce message précis. Les autres erreurs remontent immédiatement (pas de masquage silencieux).

Source
update_display_name(client : OvhApi::Client, service_name : String, new_name : String, logger : Proc(String, Nil)) : Nil
Source

Nested types