Aggiunge registry nodi stream e provisioning Hetzner/lab per lo scale-out.
Prepara l'architettura multi-nodo (MediaMTX+ffmpeg) con assignment URL per sessione, admin di provision/drain e provider Cloud/DNS astratti verso mltv-stream.net. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "faraday"
|
||||
require "cgi"
|
||||
|
||||
module Streams
|
||||
module DnsProviders
|
||||
# Hetzner Cloud DNS (Console) — zone mltv-stream.net.
|
||||
#
|
||||
# ENV:
|
||||
# HCLOUD_TOKEN (stesso del Cloud)
|
||||
# STREAM_DNS_ZONE (default mltv-stream.net)
|
||||
# STREAM_DNS_TTL (default 60)
|
||||
class Hetzner < Base
|
||||
API = "https://api.hetzner.cloud/v1"
|
||||
|
||||
def initialize(token: ENV.fetch("HCLOUD_TOKEN"), zone: nil, conn: nil)
|
||||
@token = token
|
||||
@zone = zone || ENV.fetch("STREAM_DNS_ZONE", "mltv-stream.net")
|
||||
@ttl = ENV.fetch("STREAM_DNS_TTL", "60").to_i
|
||||
@conn = conn
|
||||
end
|
||||
|
||||
def upsert_a(name, ip)
|
||||
rr_name = relative_name(name)
|
||||
delete_a(name)
|
||||
post("/zones/#{CGI.escape(@zone)}/rrsets", {
|
||||
name: rr_name,
|
||||
type: "A",
|
||||
ttl: @ttl,
|
||||
records: [{ value: ip.to_s, comment: "matchlivetv stream-node" }],
|
||||
labels: { "matchlivetv" => "true", "role" => "stream-node" }
|
||||
})
|
||||
true
|
||||
end
|
||||
|
||||
def delete_a(name)
|
||||
rr_name = relative_name(name)
|
||||
encoded = CGI.escape(rr_name)
|
||||
response = conn.delete("/zones/#{CGI.escape(@zone)}/rrsets/#{encoded}/A") do |req|
|
||||
req.headers.update(auth_headers)
|
||||
end
|
||||
return true if response.status == 404 || response.status == 204 || response.success?
|
||||
|
||||
raise Error, "Hetzner DNS API #{response.status}: #{response.body.inspect}"
|
||||
end
|
||||
|
||||
def resolve(name)
|
||||
rr_name = relative_name(name)
|
||||
data = get("/zones/#{CGI.escape(@zone)}/rrsets", name: rr_name, type: "A")
|
||||
rrset = Array(data["rrsets"]).first
|
||||
Array(rrset&.dig("records")).first&.dig("value")
|
||||
rescue Error
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def relative_name(name)
|
||||
host = name.to_s.strip.downcase.delete_suffix(".")
|
||||
suffix = ".#{@zone}"
|
||||
return "@" if host == @zone
|
||||
return host.delete_suffix(suffix) if host.end_with?(suffix)
|
||||
|
||||
host
|
||||
end
|
||||
|
||||
def conn
|
||||
@conn ||= Faraday.new(url: API) do |f|
|
||||
f.request :json
|
||||
f.response :json, content_type: /\bjson$/
|
||||
f.adapter Faraday.default_adapter
|
||||
end
|
||||
end
|
||||
|
||||
def auth_headers
|
||||
{ "Authorization" => "Bearer #{@token}" }
|
||||
end
|
||||
|
||||
def get(path, params = {})
|
||||
response = conn.get(path) do |req|
|
||||
req.headers.update(auth_headers)
|
||||
req.params.update(params)
|
||||
end
|
||||
unwrap!(response)
|
||||
end
|
||||
|
||||
def post(path, body)
|
||||
response = conn.post(path) do |req|
|
||||
req.headers.update(auth_headers)
|
||||
req.body = body
|
||||
end
|
||||
unwrap!(response)
|
||||
end
|
||||
|
||||
def unwrap!(response)
|
||||
unless response.success?
|
||||
raise Error, "Hetzner DNS API #{response.status}: #{response.body.inspect}"
|
||||
end
|
||||
|
||||
response.body.is_a?(Hash) ? response.body : {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user