Dynadot: Skillnad mellan sidversioner
Utseende
| Rad 65: | Rad 65: | ||
==== Add this: ==== | ==== Add this: ==== | ||
<pre> | <pre> | ||
# SUBDOMAIN should be | # ===== Dynadot settings ===== | ||
DOMAIN= | # SUBDOMAIN should be empty for root domain | ||
DOMAIN=viberq.org | |||
SUBDOMAIN= | SUBDOMAIN= | ||
TYPE=A | TYPE=A | ||
PASSWORD= | PASSWORD=<hash pw> | ||
TTL=300 | TTL=300 | ||
</pre> | </pre> | ||
Nuvarande version från 21 januari 2026 kl. 08.35
Dynadot Dynamic DNS (DDNS) Setup
[redigera | redigera wikitext]This page documents how to configure Dynamic DNS for Dynadot and automatically update the A record when the public IP address changes.
Requirements
[redigera | redigera wikitext]- Dynadot account
- Domain using Dynadot DNS
- DDNS password generated in Dynadot
- Ubuntu server with outbound HTTPS access
Dynadot configuration
[redigera | redigera wikitext]1. Enable Dynadot DNS
[redigera | redigera wikitext]- Log in to Dynadot
- Go to My Domains
- Click the domain
- Set DNS → Dynadot DNS
2. Enable Dynamic DNS
[redigera | redigera wikitext]- Domain settings → Dynamic DNS
- Enable DDNS
- Generate a DDNS password
- Save it securely
3. Required values
[redigera | redigera wikitext]| Field | Example |
|---|---|
| Domain | example.com |
| Subdomain | @ or www |
| Record type | A (IPv4) or AAAA (IPv6) |
| DDNS password | (generated in Dynadot) |
Dynadot update endpoint
[redigera | redigera wikitext]Dynadot accepts HTTP GET updates:
https://www.dynadot.com/set_ddns
Parameters:
- domain
- subDomain
- type
- ip
- pwd
- ttl (optional)
Ubuntu auto-update script
[redigera | redigera wikitext]Script: dynadot-ddns.sh
[redigera | redigera wikitext]nano .env
Add this:
[redigera | redigera wikitext]# ===== Dynadot settings ===== # SUBDOMAIN should be empty for root domain DOMAIN=viberq.org SUBDOMAIN= TYPE=A PASSWORD=<hash pw> TTL=300
chmod 600 .env
#!/usr/bin/env bash
set -euo pipefail
# ===== PATHS / CONFIG (override via env) =====
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_LOCAL="${ENV_LOCAL:-$SCRIPT_DIR/.env}"
ENV_DISCORD="${ENV_DISCORD:-$SCRIPT_DIR/../bots/discord/.env}"
LOG_FILE="${LOG_FILE:-$SCRIPT_DIR/log.txt}"
LOG_KEEP="${LOG_KEEP:-5}" # keep log.txt.1 .. log.txt.$LOG_KEEP
STATE_DIR="${STATE_DIR:-$SCRIPT_DIR/state}"
IP_FILE="${IP_FILE:-$STATE_DIR/dynadot_last_ip.txt}"
SSL_NOTIFY_STATE="${SSL_NOTIFY_STATE:-$STATE_DIR/ssl_expiry_last_notified.txt}"
DISCORD_API_VERSION="${DISCORD_API_VERSION:-9}"
ENABLE_DDNS="${ENABLE_DDNS:-1}"
ENABLE_SSL="${ENABLE_SSL:-1}"
# ===== ARGS =====
FORCE=0
DDNS_ONLY=0
SSL_ONLY=0
while [[ $# -gt 0 ]]; do
case "$1" in
--force) FORCE=1; shift ;;
--ddns-only) DDNS_ONLY=1; shift ;;
--ssl-only) SSL_ONLY=1; shift ;;
-h|--help)
cat <<EOF
Usage: $0 [--force] [--ddns-only|--ssl-only]
--force Force update/reload even if unchanged
--ddns-only Run only DDNS task
--ssl-only Run only SSL task
EOF
exit 0
;;
*)
echo "Unknown arg: $1" >&2
exit 2
;;
esac
done
# ===== HELPERS =====
mkdir -p "$STATE_DIR"
rotate_logs() {
local i
for ((i=LOG_KEEP; i>=1; i--)); do
if [[ -f "${LOG_FILE}.${i}" ]]; then
if (( i == LOG_KEEP )); then
rm -f "${LOG_FILE}.${i}"
else
mv -f "${LOG_FILE}.${i}" "${LOG_FILE}.$((i+1))"
fi
fi
done
[[ -f "$LOG_FILE" ]] && mv -f "$LOG_FILE" "${LOG_FILE}.1"
}
log() {
local msg="${1:-}"
printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$msg" >> "$LOG_FILE"
}
die() {
local msg="${1:-}"
log "ERROR: $msg"
echo "ERROR: $msg" >&2
exit 1
}
require_vars() {
local v
for v in "$@"; do
[[ -n "${!v:-}" ]] || die "Missing required env var: $v"
done
}
has_vars() {
local v
for v in "$@"; do
[[ -n "${!v:-}" ]] || return 1
done
return 0
}
load_env_files_shell_safe() {
[[ -f "$ENV_LOCAL" ]] || die "Missing local .env: $ENV_LOCAL"
[[ -f "$ENV_DISCORD" ]] || die "Missing discord .env: $ENV_DISCORD"
# Only use with trusted, shell-safe KEY=value files.
set -a
# shellcheck source=/dev/null
source "$ENV_LOCAL"
# shellcheck source=/dev/null
source "$ENV_DISCORD"
set +a
}
curl_get() {
curl --fail --show-error --silent --location "$1"
}
curl_get_to_file() {
local url="$1"
local out="$2"
curl --fail --show-error --silent --location -o "$out" "$url"
}
run_root() {
if [[ "${EUID:-$(id -u)}" -eq 0 ]]; then
"$@"
else
command -v sudo >/dev/null 2>&1 || die "sudo not found (need root for: $*)"
sudo -n "$@" || die "sudo requires a password (configure non-interactive sudo or run as root): $*"
fi
}
discord_send_embed() {
local author="${1:-Monitor}"
local title="${2:-}"
local desc="${3:-}"
local color="${4:-16753920}"
require_vars DISCORD_BOT_TOKEN LOGS_CHANNEL_ID
local channel_id="$LOGS_CHANNEL_ID"
local payload
payload="$(python3 - "$author" "$title" "$desc" "$color" <<'PY'
import json, sys
author, title, desc, color = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4])
print(json.dumps({
"embeds": [{
"title": title,
"description": desc,
"color": color,
"author": {"name": author},
}]
}))
PY
)"
curl --fail --show-error --silent \
-H "Authorization: Bot ${DISCORD_BOT_TOKEN}" \
-H "Content-Type: application/json" \
-X POST \
-d "$payload" \
"https://discord.com/api/v${DISCORD_API_VERSION}/channels/${channel_id}/messages" \
> /dev/null || true
}
sha256_file() {
local f="$1"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$f" | awk '{print $1}'
else
shasum -a 256 "$f" | awk '{print $1}'
fi
}
# ===== START =====
rotate_logs
log "---- Script started ----"
load_env_files_shell_safe
require_vars DISCORD_BOT_TOKEN LOGS_CHANNEL_ID
# ===== TASK: DDNS =====
ddns_task() {
require_vars DOMAIN PASSWORD TTL
local current_ip last_ip subdomain type update_url response
current_ip="$(curl_get "https://checkip.amazonaws.com" | tr -d '[:space:]')" || {
local desc
printf -v desc "Could not fetch public IP."
discord_send_embed "DDNS" "DDNS ERROR" "$desc" 15158332
die "Could not fetch public IP"
}
[[ -f "$IP_FILE" ]] || : > "$IP_FILE"
last_ip="$(cat "$IP_FILE" 2>/dev/null || true)"
log "DDNS current IP: $current_ip"
log "DDNS last IP: ${last_ip:-<empty>}"
if [[ "$current_ip" == "$last_ip" && "$FORCE" -ne 1 ]]; then
log "DDNS: IP unchanged; no update."
return 0
fi
log "DDNS: proceeding (force=$FORCE)."
subdomain="${SUBDOMAIN:-}"
type="${TYPE:-A}"
update_url="https://www.dynadot.com/set_ddns?containRoot=true&domain=${DOMAIN}&subDomain=${subdomain}&type=${type}&ip=${current_ip}&pwd=${PASSWORD}&ttl=${TTL}"
response="$(curl_get "$update_url")" || {
local desc
printf -v desc "Dynadot request failed.\nDomain: %s\nOld IP: %s\nNew IP: %s\nForced: %s" \
"$DOMAIN" "${last_ip:-N/A}" "$current_ip" "$([[ "$FORCE" -eq 1 ]] && echo yes || echo no)"
discord_send_embed "DDNS" "DDNS FAIL" "$desc" 15158332
die "Dynadot request failed"
}
log "DDNS Dynadot response: $response"
if echo "$response" | grep -qi "success"; then
echo "$current_ip" > "$IP_FILE"
log "DDNS: IP updated and saved."
local desc
printf -v desc "Domain: %s\nOld IP: %s\nNew IP: %s\nForced: %s" \
"$DOMAIN" "${last_ip:-N/A}" "$current_ip" "$([[ "$FORCE" -eq 1 ]] && echo yes || echo no)"
discord_send_embed "DDNS" "DDNS UPDATED" "$desc" 3066993
else
log "DDNS: update failed."
local desc
printf -v desc "Domain: %s\nOld IP: %s\nNew IP: %s\nForced: %s\nResponse: %s" \
"$DOMAIN" "${last_ip:-N/A}" "$current_ip" "$([[ "$FORCE" -eq 1 ]] && echo yes || echo no)" "$response"
discord_send_embed "DDNS" "DDNS FAIL" "$desc" 15158332
fi
}
# ===== TASK: SSL =====
ssl_task() {
require_vars DYNADOT_API_KEY DYNADOT_DOMAIN SSL_DIR WEB_SERVER EXPIRY_WARN_DAYS
local cert_file key_file tmp_file url new_hash old_hash
cert_file="$SSL_DIR/fullchain.pem"
key_file="$SSL_DIR/privkey.pem"
[[ -f "$key_file" ]] || die "Missing private key at $key_file"
run_root mkdir -p "$SSL_DIR"
tmp_file="$(mktemp)"
trap 'rm -f "$tmp_file"' RETURN
url="https://www.dynadot.com/letsencrypt/download_cert?key=${DYNADOT_API_KEY}&domain=${DYNADOT_DOMAIN}"
if ! curl_get_to_file "$url" "$tmp_file"; then
local desc
printf -v desc "Failed to download certificate.\nDomain: %s" "$DYNADOT_DOMAIN"
discord_send_embed "SSL Monitor" "SSL ERROR" "$desc" 15158332
die "Failed to download certificate"
fi
if ! openssl x509 -noout -in "$tmp_file" >/dev/null 2>&1; then
local desc
printf -v desc "Downloaded file is not a valid x509 cert.\nDomain: %s" "$DYNADOT_DOMAIN"
discord_send_embed "SSL Monitor" "SSL ERROR" "$desc" 15158332
die "Downloaded file invalid"
fi
new_hash="$(sha256_file "$tmp_file")"
old_hash=""
[[ -f "$cert_file" ]] && old_hash="$(sha256_file "$cert_file")"
if [[ -n "$old_hash" && "$new_hash" == "$old_hash" && "$FORCE" -ne 1 ]]; then
log "SSL: cert unchanged; no install/reload."
else
log "SSL: proceeding (force=$FORCE)."
run_root install -o root -g root -m 644 "$tmp_file" "$cert_file"
log "SSL: cert installed."
if systemctl is-active --quiet "$WEB_SERVER"; then
run_root systemctl reload "$WEB_SERVER"
log "SSL: reloaded service: $WEB_SERVER"
local desc
printf -v desc "Installed certificate.\nDomain: %s\nService: %s reloaded.\nForced: %s" \
"$DYNADOT_DOMAIN" "$WEB_SERVER" "$([[ "$FORCE" -eq 1 ]] && echo yes || echo no)"
discord_send_embed "SSL Monitor" "SSL UPDATED" "$desc" 3066993
else
log "SSL: WARNING - service not active: $WEB_SERVER"
local desc
printf -v desc "%s not running after SSL update.\nDomain: %s" "$WEB_SERVER" "$DYNADOT_DOMAIN"
discord_send_embed "SSL Monitor" "SSL WARNING" "$desc" 16753920
fi
fi
local expiry_date expiry_epoch now_epoch days_left last_notified
expiry_date="$(openssl x509 -enddate -noout -in "$cert_file" | cut -d= -f2 || true)"
[[ -n "$expiry_date" ]] || die "Could not read cert expiry from: $cert_file"
expiry_epoch="$(date -d "$expiry_date" +%s)"
now_epoch="$(date +%s)"
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
log "SSL: expiry: $expiry_date (${days_left} days left)"
last_notified="$(cat "$SSL_NOTIFY_STATE" 2>/dev/null || true)"
if (( days_left <= EXPIRY_WARN_DAYS )); then
if [[ "$last_notified" != "$expiry_date" ]]; then
local desc
printf -v desc "Domain: %s\nExpires in: %s days\nExpiry: %s" "$DYNADOT_DOMAIN" "$days_left" "$expiry_date"
discord_send_embed "SSL Monitor" "SSL EXPIRY WARNING" "$desc" 16753920
echo "$expiry_date" > "$SSL_NOTIFY_STATE"
log "SSL: expiry warning sent (state updated)."
else
log "SSL: expiry warning already sent for this expiry date."
fi
fi
}
# ===== RUN =====
if [[ "$DDNS_ONLY" -eq 1 ]]; then
ENABLE_SSL=0
fi
if [[ "$SSL_ONLY" -eq 1 ]]; then
ENABLE_DDNS=0
fi
if [[ "$ENABLE_DDNS" == "1" ]]; then
ddns_task
else
log "DDNS: disabled"
fi
if [[ "$ENABLE_SSL" == "1" ]]; then
# Skip SSL gracefully if env vars aren't present
if has_vars DYNADOT_API_KEY DYNADOT_DOMAIN SSL_DIR WEB_SERVER EXPIRY_WARN_DAYS; then
ssl_task
else
log "SSL: skipped (missing SSL env vars). Tip: add SSL vars or run with --ddns-only."
fi
else
log "SSL: disabled"
fi
log "---- Script finished ----"
echo
Installation
[redigera | redigera wikitext]1. Save script
[redigera | redigera wikitext]sudo nano /usr/local/bin/dynadot-ddns.sh
Paste script and edit variables.
2. Make executable
[redigera | redigera wikitext]sudo chmod +x /usr/local/bin/dynadot-ddns.sh
3. Test manually
[redigera | redigera wikitext]/usr/local/bin/dynadot-ddns.sh
4. Add cron job (every 5 minutes)
[redigera | redigera wikitext]crontab -e
Add:
*/5 * * * * /usr/local/bin/dynadot-ddns.sh
Security notes
[redigera | redigera wikitext]- Store DDNS password securely
- Limit file permissions
- Do not commit the script to public repositories
References
[redigera | redigera wikitext]- Dynadot Help – Dynamic DNS
- Dynadot API documentation