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
- 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.
- Dynamic DNS for Dynamic IPs:
- Perfect for environments where your public IP changes frequently, such as home internet connections with dynamic DHCP assignments.
- 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).
- Customizable Settings:
- Easily configure domain, subdomain, TTL (time-to-live), and logging options.
- Adjust the script to match your specific requirements.
- 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
- Initial Setup:
- Set up a TransIP account and generate a private key for API access at https://www.transip.nl/cp/account/api/
- Configure the script with your domain name, subdomain, and private key path.
- Fetching External IP:
- The script retrieves your current external IP using a public API like
ipinfo.io
.
- The script retrieves your current external IP using a public API like
- Token Generation:
- Generates a short-lived access token using TransIP’s authentication endpoint.
- 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.
- DNS Update:
- Uses TransIP’s secure API to update or create the DNS A-record with your current assigned external IP.
- Logging and Notifications:
- Logs each step and provides clear messages in case of issues.
Prerequisites
Before using this script, ensure you have:
- A TransIP account with API access enabled.
- A registered domain in your TransIP account.
- A static private key for secure API authentication.
- Dependencies installed on your system:
curl
,openssl
, andjq
.
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 ----"