Skip to content

Instantly share code, notes, and snippets.

@tkn777
Created January 16, 2025 18:23
Show Gist options
  • Save tkn777/f148a8df45abc98ab6463eadb2c72f4d to your computer and use it in GitHub Desktop.
Save tkn777/f148a8df45abc98ab6463eadb2c72f4d to your computer and use it in GitHub Desktop.
Create / removes acme dns records by modifying bind zone files (without using RFC2136)
#!/bin/bash
#
# Create / Removes acme dns records by modifying bind zone files.
# It does not use RFC2136, so zone files can still be edited manually. :)
#
# Notes:
# - This script is non-concurrent!
#
# Assumptions:
# - There is one zone file 'db.<domain>'. And there are no zone files for sub domains.
# - All zone files are under /etc/bind
# - The zone files are writeable for user 'bind'.
# - Every zone file contains exactly one SOA record (with the serial).
# - The serial is an incrementing one (and e.g. no date)
#
# Author: Thomas Kuhlmann ( [email protected] )
# License: LGPLv3
#
# Get command line arguments
MODE=$1
HOST_NAME=$2
TOKEN=$3
# Check command line arguments
if [ $# -lt 2 ] || ! [[ "$MODE" =~ ^[01]$ ]] || { [ "$MODE" -eq 1 ] && [ -z "$TOKEN" ]; }; then
echo "Usage: $0 MODE HOST [TOKEN]" >&2
echo "- MODE must be 0 (remove acme-entry) or 1 (add acme-entry)" >&2
echo "- TOKEN is required for MODE 1" >&2
exit 1
fi
# Get domain name by host name
DOMAIN_NAME=$(echo ${HOST_NAME} | rev | cut -d '.' -f 1-2 | rev)
if [ -z $DOMAIN_NAME ]; then
echo "Error: Could not retrieve domain name from host name ${HOST_NAME}" >&2
exit 1
fi
# Search zone file by domain name
ZONE_FILE_NAME="db.${DOMAIN_NAME}"
ZONE_FILE=$(find /etc/bind/ -type f -name "${ZONE_FILE_NAME}")
if [ ! -w "$ZONE_FILE" ]; then
echo "Error: Zone file (name: ${ZONE_FILE_NAME}) for host ${HOST_NAME} (domain: ${DOMAIN_NAME}) not found or not writable for user '$USER'." >&2
exit 1
fi
# Increment serial in zone file (inline)
ZONE_CONTENT=$(<${ZONE_FILE})
OLD_SERIAL=$(echo ${ZONE_CONTENT} | grep -oP 'IN\s+SOA.*\(\s*\d+' | tr "(" " " | rev | cut -d ' ' -f 1 | rev)
if [ -z $OLD_SERIAL ]; then
echo "Error: Found no serial in zone file ${ZONE_FILE}" >&2
exit 1
fi
NEW_SERIAL=$(printf "%06d" $((10#${OLD_SERIAL} + 1)))
sed -i "s/${OLD_SERIAL}/${NEW_SERIAL}/" ${ZONE_FILE}
# Add / remove acme-challenge entry in zone file (inline)
ZONE_RECORD_NAME="_acme-challenge.${HOST_NAME}."
sed -i "/${ZONE_RECORD_NAME}/d" ${ZONE_FILE} # always remove existin one(s), if any
if [ "${MODE}" -eq 1 ]; then
echo "${ZONE_RECORD_NAME} 60 IN TXT \"${TOKEN}\"" >> ${ZONE_FILE}
fi
# Reload bind9 zones
/usr/sbin/rndc reload > /dev/null
if [ $? -ne 0 ]; then
echo "Error: While reloading zones." >&2
exit 1
fi
# In case of an add, wait for publish of dns record
if [ "${MODE}" -eq 1 ]; then
MAX_WAIT=60; ELAPSED=0
while [ ${ELAPSED} -lt ${MAX_WAIT} ]; do
sleep 1; ELAPSED=$((ELAPSED + 1))
RESULT=$(dig @9.9.9.9 _acme-challenge.${HOST_NAME} TXT +short)
if [ -n "${RESULT}" ]; then break; fi
done
if [ $ELAPSED -ge $MAX_WAIT ]; then
echo "Error: DNS record for _acme-challenge.${HOST_NAME} not found after 60 seconds." >&2
exit 1
fi
fi
# Regular exit
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment