Corregge il provisioning Hetzner: Faraday, cloud-init e default cpx12.

Sistemati path API /v1, AppArmor/auth MediaMTX sul nodo e defaults nbg1 così lo smoke Cloud è ripetibile.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-10 12:20:36 +02:00
co-authored by Cursor
parent e5fc925bae
commit 559284f0b2
10 changed files with 113 additions and 66 deletions
@@ -15,7 +15,8 @@ module Streams
# HCLOUD_NETWORK_ID (opzionale, private network / WireGuard prep)
# HCLOUD_USER_DATA_FILE (opzionale, cloud-init path)
class Hetzner < Base
API = "https://api.hetzner.cloud/v1"
# Trailing slash obbligatorio: path assoluti tipo "/servers" altrimenti droppano /v1.
API = "https://api.hetzner.cloud/v1/"
def initialize(token: ENV.fetch("HCLOUD_TOKEN"), conn: nil)
@token = token
@@ -25,12 +26,12 @@ module Streams
def create_node(name:, labels: {})
body = {
name: name,
server_type: ENV.fetch("HCLOUD_SERVER_TYPE", "cpx21"),
server_type: ENV.fetch("HCLOUD_SERVER_TYPE", "cpx12"),
image: ENV.fetch("HCLOUD_IMAGE", "debian-12"),
location: ENV.fetch("HCLOUD_LOCATION", "fsn1"),
location: ENV.fetch("HCLOUD_LOCATION", "nbg1"),
start_after_create: true,
labels: default_labels.merge(stringify_labels(labels)),
ssh_keys: [ENV.fetch("HCLOUD_SSH_KEY", "matchlivetv-stream")],
ssh_keys: [ENV.fetch("HCLOUD_SSH_KEY", "matchlivetv-stream-hetzner")],
public_net: {
enable_ipv4: true,
enable_ipv6: false
@@ -41,7 +42,7 @@ module Streams
user_data = cloud_init_user_data
body[:user_data] = user_data if user_data.present?
data = post("/servers", body)
data = post("servers", body)
server = data["server"] || {}
action = data["action"]
wait_action!(action) if action
@@ -51,7 +52,7 @@ module Streams
end
def destroy_node(instance_id)
delete("/servers/#{instance_id}")
delete("servers/#{instance_id}")
true
end
@@ -61,14 +62,14 @@ module Streams
params[:label_selector] = label_selector if label_selector.present?
params[:label_selector] ||= "matchlivetv=true,role=stream-node"
data = get("/servers", params)
data = get("servers", params)
Array(data["servers"]).map { |s| instance_from_server(s) }
end
def wait_until_running(instance_id, timeout: 180)
deadline = Time.now + timeout
loop do
data = get("/servers/#{instance_id}")
data = get("servers/#{instance_id}")
server = data["server"]
status = server["status"]
if status == "running"
@@ -169,7 +170,7 @@ module Streams
deadline = Time.now + timeout
id = action["id"]
loop do
data = get("/actions/#{id}")
data = get("actions/#{id}")
status = data.dig("action", "status")
return if status == "success"
raise Error, "Hetzner action #{id} failed: #{data.inspect}" if status == "error"
@@ -12,7 +12,8 @@ module Streams
# STREAM_DNS_ZONE (default mltv-stream.net)
# STREAM_DNS_TTL (default 60)
class Hetzner < Base
API = "https://api.hetzner.cloud/v1"
# Trailing slash obbligatorio: path assoluti altrimenti droppano /v1.
API = "https://api.hetzner.cloud/v1/"
def initialize(token: ENV.fetch("HCLOUD_TOKEN"), zone: nil, conn: nil)
@token = token
@@ -24,7 +25,7 @@ module Streams
def upsert_a(name, ip)
rr_name = relative_name(name)
delete_a(name)
post("/zones/#{CGI.escape(@zone)}/rrsets", {
post("zones/#{CGI.escape(@zone)}/rrsets", {
name: rr_name,
type: "A",
ttl: @ttl,
@@ -37,7 +38,7 @@ module Streams
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|
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?
@@ -47,7 +48,7 @@ module Streams
def resolve(name)
rr_name = relative_name(name)
data = get("/zones/#{CGI.escape(@zone)}/rrsets", name: rr_name, type: "A")
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