Stay Connected with a dynamic DNS updater for TransIP

Easily access your home PC despite a dynamic IP! This script automatically updates your TransIP DNS records with your current IP, ensuring seamless connectivity. Secure, customizable, and perfect for remote work or hosting services. Stay connected effortlessly!

21 days ago   •   5 min read

By Remco Loup.
Photo by Ross Findon / Unsplash
Table of contents

In a world with DHCP address, being able to access your home PC or server remotely is a Pita. So, if you’re are "blessed"with an ISP that assigns a dynamic public IP address, you face a significant challenge: your IP changes periodically, breaking your ability to connect to your home device using a static domain.

This is where this free Dynamic DNS (DDNS) updater script for TransIP comes to the rescue. By seamlessly updating your DNS records whenever your external IP changes, this solution ensures your domain (e.g., vpn.example.com) always points to the correct IP. Here’s why this script is a must for anyone leveraging TransIP’s DNS services.

Key Features of the Script

  1. Automated DNS Updates:
    • Automatically checks your current external IP and compares it to the DNS A-record associated with your domain.
    • Creates or updates the A-record to match your latest external IP.
  2. Dynamic DNS for Dynamic IPs:
    • Perfect for environments where your public IP changes frequently, such as home internet connections with dynamic DHCP assignments.
  3. Secure Authentication:
    • Leverages TransIP’s API with a private key for secure communication.
    • Generates short-lived tokens for added security (valid for only 10 minutes per session).
  4. Customizable Settings:
    • Easily configure domain, subdomain, TTL (time-to-live), and logging options.
    • Adjust the script to match your specific requirements.
  5. Logging and Error Handling:
    • Comprehensive logging ensures you can track every step of the update process.
    • Built-in error handling for smoother execution and debugging.

How It Works

  1. Initial Setup:
  2. Fetching External IP:
    • The script retrieves your current external IP using a public API like ipinfo.io.
  3. Token Generation:
    • Generates a short-lived access token using TransIP’s authentication endpoint.
  4. DNS Record Validation:
    • Checks if the current DNS A-record matches your external IP.
    • If it differs, the script updates the record; if it’s missing, it creates a new one.
  5. DNS Update:
    • Uses TransIP’s secure API to update or create the DNS A-record with your current assigned external IP.
  6. Logging and Notifications:
    • Logs each step and provides clear messages in case of issues.

Prerequisites

Before using this script, ensure you have:

  1. A TransIP account with API access enabled.
  2. A registered domain in your TransIP account.
  3. A static private key for secure API authentication.
  4. Dependencies installed on your system:
    • curl, openssl, and jq.
sudo apt update
sudo apt install -y curl openssl jq

Why Use This Solution?

Unlike payed third-party DDNS providers, this script gives you free control over your DNS management, ensuring privacy and avoiding vendor lock-in. It’s tailored for you.

#!/bin/bash

# Configuration settings
LOGIN_NAME="your-login-name"  # Fill in your TransIP login name
PRIVATE_KEY_PATH="/path/transip_private_key.pem"  # Path to your private key file
AUTH_URL="https://api.transip.nl/v6/auth"
TOKEN_FILE="/var/tmp/transip_token.json"  # File to store token and expiration date
DOMAIN="example.com"  # Your main or test domain
SUBDOMAIN="vpn"  # The subdomain you want to update (e.g., test.example.com)
RECORD_TYPE="A"
TTL=60  # Time-to-live for DNS records
LOG_FILE="/var/log/transip_dyndns.log"

# Function to update a log file
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" > $LOG_FILE
}

# Function to log an error and stop the script
handle_error() {
    log_message "ERROR: $1"
    echo "ERROR: $1" >&2
    exit 1
}

# Function to obtain a new access token with a short validity period (10 minutes)
generate_new_token() {
    log_message "Generating a new token with a validity period of 10 minutes."
    NONCE=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16)
    TIMESTAMP=$(date +%s)
    LABEL="Set DNS to external DHCP $TIMESTAMP"
    REQUEST_BODY=$(printf '{"login":"%s","nonce":"%s","read_only":false,"expiration_time":"10 minutes","label":"%s","global_key":true}' "$LOGIN_NAME" "$NONCE" "$LABEL")
    log_message "Request Body: $REQUEST_BODY"
    SIGNATURE=$(echo -n "$REQUEST_BODY" | openssl dgst -sha512 -sign "$PRIVATE_KEY_PATH" | base64 | tr -d '\n')
    log_message "Generated signature: $SIGNATURE"
    RESPONSE=$(curl -s -X POST "$AUTH_URL" \
        -H "Signature: $SIGNATURE" \
        -H "Content-Type: application/json" \
        -d "$REQUEST_BODY") || handle_error "Failed to connect to TransIP API"
    log_message "API-response received: $RESPONSE"
    ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r '.token // empty')
    if [ -z "$ACCESS_TOKEN" ]; then
        handle_error "The received access token is empty or invalid. Check the API-response: $RESPONSE"
    fi
    log_message "Received token: $ACCESS_TOKEN"
}

# Function to get the current DNS A-record from TransIP
get_dns_record() {
    # Get API response and store status code
    RESPONSE=$(curl -s -X GET \
        -H "Authorization: Bearer $ACCESS_TOKEN" \
        "https://api.transip.nl/v6/domains/$DOMAIN/dns")

    # Check if response contains JSON and no errors
    if echo "$RESPONSE" | jq empty 2>/dev/null; then
        RECORD_CONTENT=$(echo $RESPONSE | jq -r '.dnsEntries[] | select(.name=="'$SUBDOMAIN'" and .type=="'$RECORD_TYPE'") | .content')
    else
        handle_error "API response does not contain valid JSON or contains an error: $RESPONSE"
    fi

    # Check if we found a valid record
    if [ -z "$RECORD_CONTENT" ]; then
        log_message "No valid DNS record found for $SUBDOMAIN.$DOMAIN. Record may need to be created."
        return 1
    fi

    echo "$RECORD_CONTENT"
    return 0
}

# Function to update or create DNS record
update_or_create_dns_record() {
    # Check if record exists
    CURRENT_DNS_IP=$(get_dns_record)

    if [ -z "$CURRENT_DNS_IP" ]; then
        log_message "No DNS A-record found for $SUBDOMAIN.$DOMAIN. Creating record."
        # Create request body for new record (TTL set to 60 seconds)
        DNS_ENTRY=$(jq -n \
        --arg name "$SUBDOMAIN" \
        --arg expire "60" \
        --arg type "$RECORD_TYPE" \
        --arg content "$CURRENT_IP" \
        '{"dnsEntry": {"name": $name, "expire": ($expire | tonumber), "type": $type, "content": $content}}')

        log_message "DNS entry (create): $DNS_ENTRY"

        # Execute POST request to create new DNS entry with better status code handling
        RESPONSE=$(curl -s -w "%{http_code}" -X POST \
            -H "Authorization: Bearer $ACCESS_TOKEN" \
            -H "Content-Type: application/json" \
            -d "$DNS_ENTRY" \
            "https://api.transip.nl/v6/domains/$DOMAIN/dns")

        HTTP_STATUS_UPDATE=$(echo "$RESPONSE" | tail -n1)
        RESPONSE_BODY=$(echo "$RESPONSE" | head -n -1)

        log_message "API response for creating DNS entry: $RESPONSE_BODY"
        log_message "HTTP status code received for creation: $HTTP_STATUS_UPDATE"

        if [ "$HTTP_STATUS_UPDATE" -ne 201 ]; then
            handle_error "API call for creating DNS entry failed with status code $HTTP_STATUS_UPDATE."
        fi

        log_message "New DNS A-record successfully created."
    else
        log_message "Updating DNS A-record for $SUBDOMAIN.$DOMAIN to IP $CURRENT_IP"
        # Create request body for updating the record
        DNS_ENTRY=$(jq -n \
        --arg name "$SUBDOMAIN" \
        --arg expire "$TTL" \
        --arg type "$RECORD_TYPE" \
        --arg content "$CURRENT_IP" \
        '{"dnsEntry": {"name": $name, "expire": ($expire | tonumber), "type": $type, "content": $content}}')

        log_message "DNS entry (update): $DNS_ENTRY"

        # Execute PATCH request to update the existing DNS record
        RESPONSE=$(curl -s -w "%{http_code}" -X PATCH \
            -H "Authorization: Bearer $ACCESS_TOKEN" \
            -H "Content-Type: application/json" \
            -d "$DNS_ENTRY" \
            "https://api.transip.nl/v6/domains/$DOMAIN/dns")

        HTTP_STATUS_UPDATE=$(echo "$RESPONSE" | tail -n1)
        RESPONSE_UPDATE_BODY=$(echo "$RESPONSE" | head -n -1)

        log_message "API update response received: $RESPONSE_UPDATE_BODY"
        log_message "HTTP status code received for update: $HTTP_STATUS_UPDATE"

        # Accept both 200 and 204 as successful updates
        if [ "$HTTP_STATUS_UPDATE" -ne 200 ] && [ "$HTTP_STATUS_UPDATE" -ne 204 ]; then
            handle_error "API call for DNS update failed with status code $HTTP_STATUS_UPDATE."
        else
            log_message "DNS A-record successfully updated to $CURRENT_IP"
        fi
    fi
}

# Get the current external IP
CURRENT_IP=$(curl -s http://ipinfo.io/ip) || handle_error "Failed to get external IP"

# Log the start
log_message "---- Start new run ----"
log_message "Current external IP: $CURRENT_IP"

# Always generate a short lived new token.
generate_new_token


# Try to update or create the DNS record
update_or_create_dns_record

log_message "---- End of run ----"

Spread the word

Keep reading