Files
ubicloud/prog/dns_zone/setup_dns_server_vm.rb
Furkan Sahin 12dbeb57a1 Update location references with foreign key in the controlplane
We are basically updating the location references everywhere with a
location id and adding the location relationship to the models to be
able to fetch location names when needed.
This also makes the LocationNameConverter model obsolete, so we are
removing it.

Use model id as value for Sequel::Model in resource creation form

Use id of the location as preselected value in Postgres update form
2025-03-23 15:48:19 +01:00

169 lines
4.7 KiB
Ruby

# frozen_string_literal: true
class Prog::DnsZone::SetupDnsServerVm < Prog::Base
subject_is :vm, :sshable
def self.assemble(dns_server_id, name: nil, vm_size: "standard-2", storage_size_gib: 30, location_id: Location::HETZNER_FSN1_ID)
unless (dns_server = DnsServer[dns_server_id])
fail "No existing Dns Server"
end
unless Project[Config.dns_service_project_id]
fail "No existing Project"
end
unless Location[location_id]
fail "No existing Location"
end
# The .assemble function is meant to be run by an operator manually. If/when we want to make this more programmatic
# we should move this check to a pre-validation label of the prog.
fail "Existing DNS Server VMs are not in sync, try again later" unless vms_in_sync?(dns_server.vms)
name ||= "#{dns_server.ubid}-#{SecureRandom.alphanumeric(8).downcase}"
DB.transaction do
vm_st = Prog::Vm::Nexus.assemble_with_sshable(
Config.dns_service_project_id,
sshable_unix_user: "ubi",
location_id:,
name: name,
size: vm_size,
storage_volumes: [
{encrypted: true, size_gib: storage_size_gib}
],
boot_image: "ubuntu-jammy",
enable_ip4: true
)
Strand.create_with_id(prog: "DnsZone::SetupDnsServerVm", label: "start", stack: [{subject_id: vm_st.id, dns_server_id: dns_server_id}])
end
end
def self.vms_in_sync?(vms)
return true if vms.nil? || vms.empty?
outputs = vms.map do |vm|
lines = vm.sshable.cmd("sudo -u knot knotc", stdin: "zone-read --").split("\n")
lines.map do |line|
parts = line.split
# Serial number for the SOA record can vary, and it's normal so exclude that
if parts[3] == "SOA"
parts.delete_at(6)
parts.join(" ")
else
line
end
end
end
outputs.map(&:to_set).uniq.count == 1
end
def ds
@ds ||= DnsServer[frame["dns_server_id"]]
end
label def start
nap 5 unless vm.strand.label == "wait"
register_deadline(nil, 15 * 60)
hop_prepare
end
label def prepare
sshable.cmd <<~SH
sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo sed -i ':a;N;$!ba;s/127.0.0.1 localhost\\n\\n#/127.0.0.1 localhost\\n127.0.0.1 #{vm.inhost_name}\\n\\n#/' /etc/hosts
sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf
sudo apt-get update
sudo apt-get -y install apt-transport-https ca-certificates wget
sudo wget -O /usr/share/keyrings/cznic-labs-pkg.gpg https://pkg.labs.nic.cz/gpg
echo "deb [signed-by=/usr/share/keyrings/cznic-labs-pkg.gpg] https://pkg.labs.nic.cz/knot-dns jammy main" | sudo tee /etc/apt/sources.list.d/cznic-labs-knot-dns.list
sudo apt-get update
sudo systemctl reboot
SH
hop_setup_knot
end
label def setup_knot
nap 5 unless sshable.available?
sshable.cmd <<~SH
sudo apt-get -y install knot
echo "KNOTD_ARGS="-C /var/lib/knot/confdb"" | sudo tee -a /etc/default/knot
SH
knot_config = <<-CONF
server:
rundir: "/run/knot"
user: "knot:knot"
listen: [ "0.0.0.0@53", "::@53" ]
log:
- target: "syslog"
any: "info"
database:
storage: "/var/lib/knot"
acl:
- id: "allow_dynamic_updates"
address: "127.0.0.1/32"
action: "update"
template:
- id: "default"
storage: "/var/lib/knot"
file: "%s.zone"
acl: "allow_dynamic_updates"
zonefile-sync: "60"
zonefile-load: "difference"
journal-content: "all"
zone:
#{ds.dns_zones.map { |dz| "- domain: \"#{dz.name}.\"" }.join("\n ")}
CONF
sshable.cmd("sudo tee /etc/knot/knot.conf > /dev/null", stdin: knot_config)
hop_sync_zones
end
label def sync_zones
nap 5 if ds.dns_zones.any?(&:refresh_dns_servers_set?)
ds.dns_zones.each do |dz|
zone_config = <<-CONF
#{dz.name}. 3600 SOA ns.#{dz.name}. #{dz.name}. 37 86400 7200 1209600 #{dz.neg_ttl}
#{dz.name}. 3600 NS #{ds.name}.
CONF
sshable.cmd("sudo -u knot tee /var/lib/knot/#{dz.name}.zone > /dev/null", stdin: zone_config)
end
sshable.cmd "sudo systemctl restart knot"
ds.dns_zones.each(&:purge_obsolete_records)
commands = ds.dns_zones.flat_map do |dz|
["zone-abort #{dz.name}", "zone-begin #{dz.name}"] +
dz.records.map do |r|
"zone-set #{dz.name} #{r.name} #{r.ttl} #{r.type} #{r.data}"
end + ["zone-commit #{dz.name}", "zone-flush #{dz.name}"]
end
# Put records
sshable.cmd("sudo -u knot knotc", stdin: commands.join("\n"))
hop_validate
end
label def validate
hop_sync_zones unless Prog::DnsZone::SetupDnsServerVm.vms_in_sync?(ds.vms + [vm])
ds.add_vm vm unless ds.vms.map(&:id).include? vm.id
pop "created VM for DnsServer"
end
end