#!/bin/sh # # SPDX-License-Identifier: GPL-3.0-or-later # # Script for updating NameSilo DNS records # 2026 Lin Fan # # using following options from /etc/config/ddns # domain - domain (e.g. example.com) # username - sub domain (e.g. www) # password - api key # # optional parameters (param_opt) from /etc/config/ddns # ttl - ttl in seconds (e.g. ttl=7200, default '3600') # pp - proxy protocol (e.g. pp=socks5h, default 'http') # fmt - response format (e.g. fmt=xml, default 'json') # # reference: https://www.namesilo.com/api-reference # # SECURITY NOTE: # The NameSilo API requires the API key to be passed as a URL query parameter. # This means the key may be exposed in shell history, process listings (ps), # and system logs that record full command lines. Use a dedicated API key and # protect access to this system and its logs accordingly. # check curl [ -z "$CURL" ] || [ -z "$CURL_SSL" ] && { write_log 14 "NameSilo script requires cURL with SSL support" return 1 } [ -n "$proxy" ] && [ -z "$CURL_PROXY" ] && { write_log 14 "cURL: libcurl compiled without proxy support" return 1 } # check options [ -z "$username" ] && write_log 14 "NameSilo: 'username' (sub domain) not set" && return 1 [ -z "$password" ] && write_log 14 "NameSilo: 'password' (api key) not set" && return 1 [ -z "$domain" ] && write_log 14 "NameSilo: 'domain' not set" && return 1 [ "$use_ipv6" -eq 1 ] && type="AAAA" || type="A" # parse optional parameters [ -n "$param_opt" ] && { for pair in $param_opt ; do case $pair in ttl=*) param_opt_ttl=${pair#*=} ;; pp=*) param_opt_pp=${pair#*=} ;; fmt=*) param_opt_fmt=${pair#*=} ;; *) # ignore others ;; esac done } # check optional parameters ttl="${param_opt_ttl:-3600}" fmt=$(echo ${param_opt_fmt:-json} | tr 'A-Z' 'a-z') [ "$fmt" != "json" ] && [ "$fmt" != "xml" ] && { write_log 14 "NameSilo: optional parameter 'fmt' must be json (default) or xml" return 1 } # load jshn.sh [ "$fmt" == "json" ] && { . /usr/share/libubox/jshn.sh } # check xmllint (optional) [ "$fmt" == "xml" ] && { XMLLINT=$(command -v xmllint) [ -z "$XMLLINT" ] && { write_log 7 "Suggestion: install 'libxml2-utils' to parse XML response accurately" } } # curl command CURL_CMD="$CURL -sSf -o $DATFILE --stderr $ERRFILE" [ -n "$proxy" ] && { [ -z "$param_opt_pp" ] && { proxy_arg="--proxy ${proxy}" } || { proxy_arg="--proxy ${param_opt_pp}://${proxy}" } CURL_CMD="$CURL_CMD $proxy_arg" } # extract response code get_code() { # json [ "$fmt" == "json" ] && { local json=$(cat) local code json_load "$json" json_select "reply" json_get_var code "code" printf "%s\n" "$code" return } # xml [ -n "$XMLLINT" ] && { # try xmllint first $XMLLINT --xpath "string(/namesilo/reply/code)" - 2>/dev/null } || { # fallback to grep/sed grep -o '.*' | sed 's///;s/<\/code>//' 2>/dev/null } } # extract detail message get_detail() { # json [ "$fmt" == "json" ] && { local json=$(cat) local detail json_load "$json" json_select "reply" json_get_var detail "detail" printf "%s\n" "$detail" return } # xml [ -n "$XMLLINT" ] && { # try xmllint first $XMLLINT --xpath "string(/namesilo/reply/detail)" - 2>/dev/null } || { # fallback to grep/sed grep -o '.*' | sed 's///;s/<\/detail>//' 2>/dev/null } } # extract rrid get_rrid() { # json [ "$fmt" == "json" ] && { local json=$(cat) json_load "$json" json_select "reply" json_select "resource_record" local idx=1 while json_is_a $idx object ; do local host local rtyp local rrid json_select $idx json_get_var host "host" json_get_var rtyp "type" json_get_var rrid "record_id" [ "$host" == "$username" ] && [ "$rtyp" == "$type" ] && { printf "%s\n" "$rrid" return } idx=$(( idx + 1 )) json_select .. done return } # xml [ -n "$XMLLINT" ] && { # try xmllint first $XMLLINT --xpath "string(/namesilo/reply/resource_record[host='$username'][type='$type']/record_id)" - 2>/dev/null } || { # fallback to grep/sed local record for record in $(sed "s//\n/g" | grep -o ".*$username.*") ; do local rtyp=$(printf "%s\n" "$record" | sed "s/.*//;s/<\/type>.*//") [ "$rtyp" == "$type" ] && { printf "%s\n" "$record" | sed "s/.*//;s/<\/record_id>.*//" return } done } } # call domain api call_api() { local endpoint="$1" local params="$2" local url="https://www.namesilo.com/api/${endpoint}?version=1&type=${fmt}&key=${password}&${params}" # call api $CURL_CMD "$url" local rc=$? [ "$rc" -ne 0 ] && { write_log 3 "NameSilo: API request to '$endpoint' failed with exit code $rc" return 1 } # check response local response=$(cat "$DATFILE") [ -z "$response" ] && { write_log 3 "NameSilo: Empty response from API '$endpoint'" return 1 } # check reply code local code=$(printf "%s\n" "$response" | get_code) [ "$code" != "300" ] && { local detail=$(printf "%s\n" "$response" | get_detail) write_log 3 "NameSilo: API request to '$endpoint' returned with error '$code - $detail'" return 1 } printf '%s\n' "$response" return 0 } # get rrid response=$(call_api dnsListRecords "domain=$domain") [ -z "$response" ] && return 1 rrid=$(printf "%s\n" "$response" | get_rrid) [ -z "$rrid" ] && { write_log 14 "NameSilo: No matching '$type' DNS record found for host '$username' in domain '$domain'" return 1 } # update subdomain record call_api dnsUpdateRecord "domain=$domain&rrid=$rrid&rrhost=$username&rrvalue=$__IP&rrttl=$ttl" && { write_log 7 "NameSilo: '$type' DNS record for '$username.$domain' updated successfully to IP '$__IP'" return 0 } || { write_log 3 "NameSilo: Failed to update '$type' DNS record for '$username.$domain' to IP '$__IP'" return 1 }