Skip to content

Instantly share code, notes, and snippets.

@aalonzolu
Last active January 13, 2025 21:05
Show Gist options
  • Save aalonzolu/08a22a59fb0c9ea0528bff9122f2028c to your computer and use it in GitHub Desktop.
Save aalonzolu/08a22a59fb0c9ea0528bff9122f2028c to your computer and use it in GitHub Desktop.
Export Vultr DNS records to BIND text file for importing in Cloudflare
import requests
import json
domain_name = 'example.com' # Change this
accces_token = 'API_KEY' #Change this
r=requests.get("https://api.vultr.com/v1/dns/records?domain="+domain_name, headers={"API-Key":accces_token,'Content-Type': 'application/json'})
JSON_DATA = r.json()
if(r.status_code != 200):
print "Error: "+r.text
else:
DNS_TYPE={}
for item in JSON_DATA:
DNS_TYPE[item['type']] = []
DNS_TYPE
for item in JSON_DATA:
DNS_TYPE[item['type']].append(item)
DNS_TYPE['MX']
for dtype in DNS_TYPE:
print ';; '+dtype
for record in DNS_TYPE[dtype]:
if record['name'] !='':
record['name']= record['name']+'.'
if record['priority']==0:
print record['name']+domain_name+'.\t'+'1\t'+'IN\t'+record['type']+'\t'+record['data']
else:
print record['name']+domain_name+'.\t'+'1\t'+'IN\t'+record['type']+'\t'+str(record['priority'])+'\t'+record['data']
@Subtletree
Copy link

Cheers mate!

Used this as a base for a new version using vultr 2 API. Worked for my usecase but may not be 100% accurate to the spec

import requests
import argparse
import re
from typing import Dict, List

def is_valid_domain(domain):
    # Regular expression to match a valid domain name
    pattern = r'^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\.)+(?:[A-Za-z]{2,})$'
    return bool(re.match(pattern, domain))

def get_dns_records(domain_name: str, access_token: str) -> dict:
    """Fetch DNS records from Vultr API v2"""
    url = f"https://api.vultr.com/v2/domains/{domain_name}/records"
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    response = requests.get(url, headers=headers)
    if response.status_code != 200:
        raise Exception(f"Error: {response.text}")
    
    return response.json()

def get_soa(domain_name: str, access_token: str) -> dict:
    """Fetch SOA from Vultr API v2"""
    url = f"https://api.vultr.com/v2/domains/{domain_name}/soa"
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json"
    }
    
    response = requests.get(url, headers=headers)
    if response.status_code != 200:
        raise Exception(f"Error: {response.text}")
    
    return response.json()

def generate_zone_file(domain_name: str, access_token: str) -> None:
    """Generate a BIND-format zone file from Vultr DNS records"""
    try:
        # Get records from API
        data = get_dns_records(domain_name, access_token)

        # Get SOA from API
        soa_data = get_soa(domain_name, access_token)
        ns_primary = soa_data['dns_soa']['nsprimary']
        email = soa_data['dns_soa']['email']
        # Format email for BIND (replace '@' with '.' and remove domain-ending dot)
        bind_email = email.replace('@', '.').rstrip('.')

        # Create SOA record
        soa_record = f"""
$TTL    86400
@       IN      SOA     {ns_primary}. {bind_email}. (
                2025010801  ; Serial
                3600        ; Refresh
                1800        ; Retry
                1209600     ; Expire
                86400       ; Minimum TTL
)
        """

        print(soa_record)
        
        # Group records by type
        dns_type: Dict[str, List] = {}
        for record in data["records"]:
            record_type = record["type"]
            if record_type not in dns_type:
                dns_type[record_type] = []
            dns_type[record_type].append(record)
        
        # Generate zone file format
        for dtype in dns_type:
            print(f"\n;; {dtype}")
            for record in dns_type[dtype]:
                # Format name field
                name = record["name"]
                if name != domain_name and name != "":
                    # Handle subdomains correctly
                    if name.endswith(domain_name):
                        f"{name}."
                else:
                    name = "@"

                value = record["data"]
                if is_valid_domain(value):
                    value = f"{value}."
                
                # Format record line
                # if record["priority"] == 0:
                if not dtype == 'MX':
                    print(f"{name}\t{record['ttl']}\tIN\t{record['type']}\t{value}")
                else:
                    print(f"{name}\t{record['ttl']}\tIN\t{record['type']}\t{record['priority']}\t{value}")

    except Exception as e:
        print(f"Error: {str(e)}")

if __name__ == "__main__":
    access_token = "API_KEY" # Change this

    parser = argparse.ArgumentParser(description="Generate a zone file for a given domain.")
    parser.add_argument('domain_name', type=str, help="The domain name example.com")
    # parser.add_argument('access_token', type=str, help="The access token")

    args = parser.parse_args()
    
    generate_zone_file(args.domain_name, access_token)

Usage:

// Add Vultr API key to py file

// export to example.com.txt file
python3 vultr-zone-file.py example.com > example.com.txt

// verify with named-checkzone linux package
named-checkzone example.com example.com.txt

@aalonzolu
Copy link
Author

Hey! I completely forgot I wrote this, Thanks for the Upgrade!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment