Skip to content

Instantly share code, notes, and snippets.

@Lenart12
Last active February 2, 2026 11:50
Show Gist options
  • Select an option

  • Save Lenart12/4fb72b4d072fab873922c4c3f3545b42 to your computer and use it in GitHub Desktop.

Select an option

Save Lenart12/4fb72b4d072fab873922c4c3f3545b42 to your computer and use it in GitHub Desktop.
OpenWRT DDNS client (ddns-scripts) update script for neoserv.si
#!/bin/sh
# Neoserv DDNS updater (OpenWrt ddns-scripts style)
# Requires install of full packages of: curl, grep, jq
# BusyBox grep for example in not enough
neoserv_update() {
ENDPOINT="https://moj.neoserv.si"
UA="Mozilla/5.0 OpenWrtDDNSScript/1.0"
COOKIES="/tmp/neoserv.$$"
trap 'rm -f "$COOKIES"' RETURN
die() {
write_log 14 "ERROR: $*"
return 1
}
[ -z "$username" ] && { die "Service section not configured correctly! Missing email as 'username'"; return 1; }
[ -z "$password" ] && { die "Service section not configured correctly! Missing password as 'password'"; return 1; }
[ -z "$domain" ] && { die "Service section not configured correctly! Missing 'domain'"; return 1; }
[ -z "$__IP" ] && { die "Missing IP address"; return 1; }
[ "$use_ipv6" -eq 1 ] && { die "IPv6 is not supported by this DDNS script"; return 1; }
USER="$username"
PASS="$password"
IP="$__IP"
DOMAIN="$(echo "$domain" | awk -F. '{print $(NF-1)"."$NF}')"
if [ "$domain" = "$DOMAIN" ]; then
HOST=""
else
HOST="$(echo "$domain" | sed "s/\.$DOMAIN$//")"
fi
NAME="${HOST:+$HOST.}$DOMAIN"
write_log 7 "Updating Neoserv DDNS: $NAME → $IP"
csrf_html() {
grep -oP 'name="_token"\s+value="\K[^"]+' | head -1
}
csrf_cookie() {
grep -oP 'XSRF-TOKEN\s+\K[^[:space:]]+' "$COOKIES" 2>/dev/null |
sed 's/%\([0-9A-Fa-f][0-9A-Fa-f]\)/\\x\1/g' |
xargs printf | base64 -d 2>/dev/null | jq -r '.value'
}
LOGIN=$(curl -s -c "$COOKIES" -A "$UA" "$ENDPOINT/login") \
|| { die "Failed to load login page"; return 1; }
CSRF=$(printf "%s" "$LOGIN" | csrf_html)
[ -z "$CSRF" ] && { die "Failed to extract CSRF token"; return 1; }
curl -s -L -c "$COOKIES" -b "$COOKIES" -A "$UA" \
-d "_token=$CSRF" \
-d "email=$USER" \
--data-urlencode "password=$PASS" \
"$ENDPOINT/login" > /dev/null \
|| { die "Login failed"; return 1; }
write_log 7 "Logged in as $USER"
CART_ID=$(curl -s -b "$COOKIES" "$ENDPOINT/services?type=domains" |
grep -oP '/domains/\d+' | head -1 | grep -oP '\d+')
[ -z "$CART_ID" ] && { die "Domain $DOMAIN not found (or login failed)"; return 1; }
HTML=$(curl -s -b "$COOKIES" "$ENDPOINT/domains/$CART_ID/records") \
|| { die "Failed to load DNS records"; return 1; }
SNAPSHOT=$(printf "%s" "$HTML" |
grep -oP 'wire:snapshot="\K[^"]+' |
sed 's/"/"/g; s/&/\&/g' |
jq -sc --arg name "$NAME" --arg domain "$DOMAIN" '
map(select(.memo.name=="cart.domain.domain-record-row")) |
map(select(
(if .data.record[0].host!="" then .data.record[0].host+"."+ $domain else $domain end)
== $name
))[0]
')
[ -z "$SNAPSHOT" ] && { die "DNS record $NAME not found"; return 1; }
OLD_IP=$(printf "%s" "$SNAPSHOT" | jq -r '.data.record[0].record')
write_log 7 "Current IP: $OLD_IP"
CSRF=$(printf "%s" "$HTML" | csrf_html)
[ -n "$CSRF" ] || CSRF=$(csrf_cookie)
[ -n "$CSRF" ] || { die "Failed to obtain CSRF token"; return 1; }
PAYLOAD=$(jq -n \
--arg t "$CSRF" \
--arg s "$(printf "%s" "$SNAPSHOT" | jq -c .)" \
--arg ip "$IP" \
'{
_token: $t,
components: [{
snapshot: $s,
updates: { "form.record": $ip },
calls: [{ path:"", method:"save", params:[] }]
}]
}')
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-b "$COOKIES" -A "$UA" \
-H "Content-Type: application/json" \
-H "X-Livewire;" \
-d "$PAYLOAD" \
"$ENDPOINT/livewire/update")
[ "$STATUS" = "200" ] || { die "Update failed (HTTP $STATUS)"; return 1; }
write_log 7 "SUCCESS: Updated $NAME to $IP"
return 0
}
neoserv_update
return $?
@Lenart12
Copy link
Author

Lenart12 commented Feb 2, 2026

Test harness:

#!/bin/sh
# DDNS test harness

set -u

### ---- mock ddns-scripts environment ---------------------------------------

# Logging function used by ddns-scripts
write_log() {
    level="$1"
    shift
    printf '[ddns][level=%s] %s\n' "$level" "$*"
}

# DDNS-provided variables
username="$(cat USER.txt)"
password="$(cat PASSWORD.txt)"
domain="example.com"     # test domain / subdomain
__IP="0.0.0.1"               # test IP
use_ipv6=0

# Optional vars sometimes present in ddns
ip_source="script"
force_interval=0

### ---- source the provider -------------------------------------------------

SCRIPT="./neoserv_ddns.sh"

if [ ! -f "$SCRIPT" ]; then
    echo "ERROR: Cannot find $SCRIPT"
    exit 1
fi

echo ">>> Sourcing DDNS provider: $SCRIPT"
. "$SCRIPT"

RET=$?

### ---- result --------------------------------------------------------------

echo ">>> Provider returned: $RET"

if [ "$RET" -eq 0 ]; then
    echo ">>> TEST SUCCESS"
else
    echo ">>> TEST FAILURE"
fi

exit "$RET"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment