# 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!

- URL: https://www.allsubjectsmatter.nl/stay-connected-with-a-dynamic-dns-updater-for-transip/
- Language: en
- Published: 2024-11-29
- Updated: 2024-11-29
- Author: Remco Loup (https://www.allsubjectsmatter.nl/author/remco/)
- Tags: Under the hood
- Translation (nl, translated with AI): https://www.allsubjectsmatter.nl/nl/stay-connected-with-a-dynamic-dns-updater-for-transip/

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:**
    - Set up a TransIP account and generate a private key for API access at [https://www.transip.nl/cp/account/api/](https://www.transip.nl/cp/account/api/)
    - Configure the script with your domain name, subdomain, and private key path.
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`.

```bash
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.

```bash
#!/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 ----"
```
