Create Bind9 “hook” script for use with acme.sh

Add “hook” processing file for acme.sh

Create the file ‘~/.acme.sh/dns_bind9.sh’

Add the following to this file:

#!/bin/bash
# acme.sh DNS API hook for BIND9

BIND_UPDATE_SCRIPT="/usr/local/bin/update_bind9_txt.sh"

dns_bind9_add() {
    fulldomain="$1"
    txtvalue="$2"

    _debug "dns_bind9_add: $fulldomain = $txtvalue"

    "$BIND_UPDATE_SCRIPT" "ADD" "$fulldomain" "$txtvalue" || return 1

    return 0
}

dns_bind9_rm() {
    fulldomain="$1"
    txtvalue="$2"

    _debug "dns_bind9_rm: $fulldomain (value $txtvalue)"

    "$BIND_UPDATE_SCRIPT" "DEL" "$fulldomain" "$txtvalue" || return 0

    return 0
}

Set file as executable

chmod +x ~/.acme.sh/dns_bind9.sh

Create Bind9 update script ‘/usr/local/bin/update_bind9_txt.sh’

Add the following to this file:

#!/bin/bash

# Must be run as root
if [[ $EUID -ne 0 ]]; then
    echo "This script must be run as root."
    exit 1
fi

ACTION="$1"       # ADD or DEL
RECORD="$2"       # Full domain (_acme-challenge.sub.example.com)
TXT_VALUE="$3"

ZONE_DIR="/etc/bind/zones"

if [[ -z "$ACTION" || -z "$RECORD" || ( "$ACTION" == "ADD" && -z "$TXT_VALUE" ) ]]; then
    echo "Usage: $0 ADD|DEL full.domain.name txt-value"
    exit 1
fi

# Function to extract zone name from domain
find_zone_name() {
    domain="$1"
    while [[ "$domain" == *.* ]]; do
        zone_candidate="$domain"
        zone_file="$ZONE_DIR/$zone_candidate.zone"
        if [[ -f "$zone_file" ]]; then
            echo "$zone_candidate"
            return
        fi
        domain="${domain#*.}"
    done
    echo ""
}

ZONE_NAME="$(find_zone_name "$RECORD")"

if [[ -z "$ZONE_NAME" ]]; then
    echo "Zone file for $RECORD not found in $ZONE_DIR"
    exit 1
fi

ZONE_FILE="$ZONE_DIR/$ZONE_NAME.zone"

# Backup
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
cp "$ZONE_FILE" "$ZONE_FILE.bak_$TIMESTAMP" || exit 1

# Function to increment SOA serial
increment_serial() {
    sed -i -E "/; serial/ {
        s/([0-9]{10})/\1 + 1/e
    }" "$ZONE_FILE"
}

# Build relative name for zone file (strip zone from record)
REL_NAME="${RECORD%.$ZONE_NAME}"
REL_NAME="${REL_NAME%.}"  # remove trailing dot if present

if [[ -z "$REL_NAME" || "$REL_NAME" == "$RECORD" ]]; then
    REL_NAME="@"
fi

case "$ACTION" in
    ADD)
        echo "$REL_NAME. 300 IN TXT \"$TXT_VALUE\"" >> "$ZONE_FILE"
        ;;
    DEL)
        sed -i "\|^$REL_NAME[[:space:]].*IN[[:space:]]TXT[[:space:]]\"$TXT_VALUE\"|d" "$ZONE_FILE"
        ;;
    *)
        echo "Unknown action: $ACTION"
        exit 1
        ;;
esac

increment_serial

# Reload the zone
rndc reload "$ZONE_NAME" || {
    echo "Failed to reload zone $ZONE_NAME"
    exit 1
}

echo "Successfully updated $ZONE_NAME for record $REL_NAME"

Set file as executable

chmod +x /usr/local/bin/update_bind9_txt.sh

About the Author

Jim Lucas

Owner and proprietor of this establishment

1 thought on “Create Bind9 “hook” script for use with acme.sh

Leave a Reply

Your email address will not be published. Required fields are marked *