mirror of
https://codeberg.org/polyteknisk-radiogruppe/the_prg_server_configuration.git
synced 2026-06-13 18:28:55 +02:00
Revert to initial idea of copy-pasting but attirbuting the following script by https://github.com/K0p1-Git/cloudflare-ddns-updater/blob/main/cloudflare-template.sh
This commit is contained in:
parent
1f3594efa5
commit
4be80d2c42
1 changed files with 165 additions and 127 deletions
|
|
@ -5,39 +5,12 @@
|
|||
...
|
||||
}: let
|
||||
choose = paths: builtins.head (builtins.filter (p: builtins.pathExists p) paths);
|
||||
# Package the upstream cloudflare-ddns-updater from GitHub into the Nix store
|
||||
upstreamOwner = "K0p1-Git";
|
||||
upstreamRepo = "cloudflare-ddns-updater";
|
||||
upstreamRev = "e9906c3aa0b73c26e3473618ad7a69db853e669d";
|
||||
upstreamSrc = pkgs.fetchFromGitHub {
|
||||
owner = upstreamOwner;
|
||||
repo = upstreamRepo;
|
||||
rev = upstreamRev;
|
||||
sha256 = "0j1pv57hk9rhr1kxqqjxg71y0d2d1hi3b4yiq908x5kcv3abbina";
|
||||
};
|
||||
|
||||
cloudflare-ddns-pkg = pkgs.stdenv.mkDerivation {
|
||||
pname = "cloudflare-ddns-updater";
|
||||
version = upstreamRev;
|
||||
src = upstreamSrc;
|
||||
# no build required for a script-only repo
|
||||
buildPhase = '': '';
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin $out/lib/cloudflare-ddns
|
||||
cp -r $src/* $out/lib/cloudflare-ddns/ || true
|
||||
if [ -x "$out/lib/cloudflare-ddns/update.sh" ]; then
|
||||
cp "$out/lib/cloudflare-ddns/update.sh" $out/bin/cloudflare-ddns
|
||||
chmod +x $out/bin/cloudflare-ddns
|
||||
else
|
||||
# If upstream layout changes, keep the repo contents available under $out/lib
|
||||
true
|
||||
fi
|
||||
'';
|
||||
meta = with pkgs.lib; {
|
||||
description = "Cloudflare DDNS updater";
|
||||
license = licenses.mit;
|
||||
};
|
||||
};
|
||||
# Domain and Cloudflare DDNS records configured here. Update this list to add/remove records.
|
||||
domain = "prg-radio.org";
|
||||
records = ["git" "grafana" "anubis" "wavelog" "partdb" "mail" "mailadmin" "@"];
|
||||
recordsStr = lib.concatStringsSep " " records;
|
||||
zoneId = "9fde8d0fa53502f2d1b7e0b1d3765d49";
|
||||
envFile = "/home/traefikprg/cloudflare/cloudflare.env";
|
||||
in {
|
||||
options.local = {
|
||||
hostname = lib.mkOption {
|
||||
|
|
@ -595,113 +568,178 @@ in {
|
|||
};
|
||||
};
|
||||
|
||||
# PRG Cloudflare DDNS updater
|
||||
environment.etc."cloudflare-ddns/update.sh" = {
|
||||
text = let
|
||||
domain = "prg-radio.org";
|
||||
records = ["git" "grafana" "anubis" "wavelog" "partdb" "mail" "mailadmin" "@"];
|
||||
recordsStr = lib.concatStringsSep " " records;
|
||||
zoneId = "9fde8d0fa53502f2d1b7e0b1d3765d49";
|
||||
envFile = "/home/traefikprg/cloudflare/cloudflare.env";
|
||||
in ''
|
||||
#!/usr/bin/env bash
|
||||
set -euuo pipefail
|
||||
# PRG Cloudflare DDNS updater - split into a single-run upstream script and a wrapper that loops records
|
||||
environment.etc."cloudflare-ddns/update-single.sh" = {
|
||||
text = ''
|
||||
#!/bin/bash
|
||||
## K0p1-Git cloudflare-ddns-updater (packaged copy)
|
||||
## Upstream: https://github.com/K0p1-Git/cloudflare-ddns-updater
|
||||
## Author: K0p1-Git
|
||||
## License: MIT
|
||||
|
||||
# Load environment variables (must contain CLOUDFLARE_API_TOKEN)
|
||||
if [ -f "${envFile}" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "${envFile}"
|
||||
fi
|
||||
## change to "bin/sh" when necessary
|
||||
|
||||
if [ -z "$${CLOUDFLARE_API_TOKEN:-}" ]; then
|
||||
echo "CLOUDFLARE_API_TOKEN is not set in ${envFile}, aborting" >&2
|
||||
exit 2
|
||||
fi
|
||||
auth_email="" # The email used to login 'https://dash.cloudflare.com'
|
||||
auth_method="token" # Set to "global" for Global API Key or "token" for Scoped API Token
|
||||
auth_key="" # Your API Token or Global API Key
|
||||
zone_identifier="" # Can be found in the "Overview" tab of your domain
|
||||
record_name="" # Which record you want to be synced
|
||||
ttl=3600 # Set the DNS TTL (seconds)
|
||||
proxy="false" # Set the proxy to true or false
|
||||
sitename="" # Title of site "Example Site"
|
||||
slackchannel="" # Slack Channel #example
|
||||
slackuri="" # URI for Slack WebHook "https://hooks.slack.com/services/xxxxx"
|
||||
discorduri="" # URI for Discord WebHook "https://discordapp.com/api/webhooks/xxxxx"
|
||||
|
||||
API_BASE="https://api.cloudflare.com/client/v4"
|
||||
|
||||
# get current public IPv4
|
||||
CURRENT_IP=$(curl -4 -sS https://ipv4.icanhazip.com | tr -d '\n') || true
|
||||
if [ -z "$CURRENT_IP" ]; then
|
||||
echo "Failed to detect public IPv4 address" >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
jq=${pkgs.jq}/bin/jq
|
||||
|
||||
update_or_create() {
|
||||
local fqdn="$1"
|
||||
# Query existing A records for fqdn
|
||||
res=$(curl -sS -X GET "$API_BASE/zones/${zoneId}/dns_records?type=A&name=$fqdn" \
|
||||
-H "Authorization: Bearer $${CLOUDFLARE_API_TOKEN:-}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
ok=$(echo "$res" | $jq -r '.success')
|
||||
if [ "$ok" != "true" ]; then
|
||||
echo "Cloudflare API query failed for $fqdn: $res" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
existing_ip=$(echo "$res" | $jq -r '.result[0].content // empty')
|
||||
record_id=$(echo "$res" | $jq -r '.result[0].id // empty')
|
||||
|
||||
if [ -n "$existing_ip" ] && [ "$existing_ip" = "$CURRENT_IP" ]; then
|
||||
echo "$fqdn is already set to $CURRENT_IP, skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
payload=$(cat <<JSON
|
||||
{"type":"A","name":"$fqdn","content":"$CURRENT_IP","ttl":1,"proxied":false}
|
||||
JSON
|
||||
###########################################
|
||||
## Check if we have a public IP
|
||||
###########################################
|
||||
REGEX_IPV4="^(0*(1?[0-9]{1,2}|2([0-4][0-9]|5[0-5]))\\.){3}0*(1?[0-9]{1,2}|2([0-4][0-9]|5[0-5]))$"
|
||||
IP_SERVICES=(
|
||||
"https://api.ipify.org"
|
||||
"https://ipv4.icanhazip.com"
|
||||
"https://ipinfo.io/ip"
|
||||
)
|
||||
|
||||
if [ -n "$record_id" ]; then
|
||||
echo "Updating $fqdn ($record_id) -> $CURRENT_IP"
|
||||
curl -sS -X PUT "$API_BASE/zones/${zoneId}/dns_records/$record_id" \
|
||||
-H "Authorization: Bearer $${CLOUDFLARE_API_TOKEN:-}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "$payload" | $jq -r '.'
|
||||
else
|
||||
echo "Creating record $fqdn -> $CURRENT_IP"
|
||||
curl -sS -X POST "$API_BASE/zones/${zoneId}/dns_records" \
|
||||
-H "Authorization: Bearer $${CLOUDFLARE_API_TOKEN:-}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "$payload" | $jq -r '.'
|
||||
fi
|
||||
}
|
||||
# Try all the ip services for a valid IPv4 address
|
||||
for service in $${IP_SERVICES[@]}; do
|
||||
RAW_IP=$(curl -s $service)
|
||||
if [[ $RAW_IP =~ $REGEX_IPV4 ]]; then
|
||||
CURRENT_IP=$BASH_REMATCH
|
||||
logger -s "DDNS Updater: Fetched IP $CURRENT_IP"
|
||||
break
|
||||
else
|
||||
logger -s "DDNS Updater: IP service $service failed."
|
||||
fi
|
||||
done
|
||||
|
||||
# Exit if IP fetching failed
|
||||
if [[ -z "$${CURRENT_IP}" ]]; then
|
||||
logger -s "DDNS Updater: Failed to find a valid IP."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
###########################################
|
||||
## Check and set the proper auth header
|
||||
###########################################
|
||||
if [[ "$${auth_method}" == "global" ]]; then
|
||||
auth_header="X-Auth-Key:"
|
||||
else
|
||||
auth_header="Authorization: Bearer"
|
||||
fi
|
||||
|
||||
###########################################
|
||||
## Seek for the A record
|
||||
###########################################
|
||||
|
||||
logger "DDNS Updater: Check Initiated"
|
||||
record=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$${zone_identifier}/dns_records?type=A&name=$${record_name}" \
|
||||
-H "X-Auth-Email: $${auth_email}" \
|
||||
-H "$${auth_header} $${auth_key}" \
|
||||
-H "Content-Type: application/json")
|
||||
|
||||
###########################################
|
||||
## Check if the domain has an A record
|
||||
###########################################
|
||||
if [[ $record == *"\\"count\\":0"* ]]; then
|
||||
logger -s "DDNS Updater: Record does not exist, perhaps create one first? ($${CURRENT_IP} for $${record_name})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
###########################################
|
||||
## Get existing IP
|
||||
###########################################
|
||||
old_ip=$(echo "$record" | sed -E 's/.*"content":"(([0-9]{1,3}\\.){3}[0-9]{1,3})".*/\\1/')
|
||||
# Compare if they're the same
|
||||
if [[ $CURRENT_IP == $old_ip ]]; then
|
||||
logger "DDNS Updater: IP ($CURRENT_IP) for $${record_name} has not changed."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
###########################################
|
||||
## Set the record identifier from result
|
||||
###########################################
|
||||
record_identifier=$(echo "$record" | sed -E 's/.*"id":"([A-Za-z0-9_]+)".*/\\1/')
|
||||
|
||||
###########################################
|
||||
## Change the IP@Cloudflare using the API
|
||||
###########################################
|
||||
update=$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/$${zone_identifier}/dns_records/$${record_identifier}" \
|
||||
-H "X-Auth-Email: $${auth_email}" \
|
||||
-H "$${auth_header} $${auth_key}" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{\"type\":\"A\",\"name\":\"$${record_name}\",\"content\":\"$${CURRENT_IP}\",\"ttl\":$${ttl},\"proxied\":$${proxy}}")
|
||||
|
||||
###########################################
|
||||
## Report the status (simplified payload construction to avoid nested-quote issues)
|
||||
###########################################
|
||||
case "$update" in
|
||||
*"\\\"success\\\":false"*)
|
||||
echo -e "DDNS Updater: $${CURRENT_IP} $${record_name} DDNS failed for $${record_identifier} ($${CURRENT_IP}). DUMPING RESULTS:\n$update" | logger -s
|
||||
if [[ -n "$${slackuri}" ]]; then
|
||||
msg="$${sitename} DDNS Update Failed: $${record_name}: $${record_identifier} ($${CURRENT_IP})."
|
||||
curl -L -X POST "$${slackuri}" --data-raw "{\"channel\":\"$${slackchannel}\",\"text\":\"$${msg}\"}"
|
||||
fi
|
||||
if [[ -n "$${discorduri}" ]]; then
|
||||
msg="$${sitename} DDNS Update Failed: $${record_name}: $${record_identifier} ($${CURRENT_IP})."
|
||||
curl -i -H "Accept: application/json" -H "Content-Type:application/json" -X POST --data-raw "{\"content\":\"$${msg}\"}" "$${discorduri}"
|
||||
fi
|
||||
exit 1;;
|
||||
*)
|
||||
msg="$${sitename} Updated: $${record_name}'s new IP Address is $${CURRENT_IP}"
|
||||
logger "DDNS Updater: $${CURRENT_IP} $${record_name} DDNS updated."
|
||||
if [[ -n "$${slackuri}" ]]; then
|
||||
curl -L -X POST "$${slackuri}" --data-raw "{\"channel\":\"$${slackchannel}\",\"text\":\"$${msg}\"}"
|
||||
fi
|
||||
if [[ -n "$${discorduri}" ]]; then
|
||||
curl -i -H "Accept: application/json" -H "Content-Type:application/json" -X POST --data-raw "{\"content\":\"$${msg}\"}" "$${discorduri}"
|
||||
fi
|
||||
exit 0;;
|
||||
esac
|
||||
|
||||
echo "Starting PRG Cloudflare DDNS updater: setting IP=$CURRENT_IP"
|
||||
for r in ${recordsStr}; do
|
||||
if [ "$r" = "@" ]; then
|
||||
fqdn="${domain}"
|
||||
else
|
||||
fqdn="$r.${domain}"
|
||||
fi
|
||||
update_or_create "$fqdn" || true
|
||||
done
|
||||
'';
|
||||
mode = "0755";
|
||||
group = "root";
|
||||
};
|
||||
|
||||
systemd.services."prg-cloudflare-ddns-updater" = {
|
||||
description = "PRG Cloudflare DDNS updater";
|
||||
wantedBy = ["multi-user.target"];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
EnvironmentFile = "/home/traefikprg/cloudflare/cloudflare.env";
|
||||
ExecStart = "${pkgs.bash}/bin/bash /etc/cloudflare-ddns/update.sh";
|
||||
};
|
||||
};
|
||||
environment.etc."cloudflare-ddns/update.sh" = {
|
||||
text = ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
systemd.timers."prg-cloudflare-ddns-updater" = {
|
||||
description = "Run PRG Cloudflare DDNS updater periodically";
|
||||
wantedBy = ["timers.target"];
|
||||
timerConfig = {
|
||||
OnBootSec = "1m";
|
||||
OnUnitActiveSec = "10m";
|
||||
Persistent = true;
|
||||
};
|
||||
# Wrapper: source env, map tokens, loop declared records and call the upstream single-run script
|
||||
if [ -f "${envFile}" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
source "${envFile}"
|
||||
fi
|
||||
|
||||
# Map env variables from the env file into auth_key/auth_email used by the upstream script
|
||||
if [ -n "$${CLOUDFLARE_DNS_API_TOKEN:-}" ]; then
|
||||
export auth_key="$${CLOUDFLARE_DNS_API_TOKEN:-}"
|
||||
elif [ -n "$${CLOUDFLARE_API_TOKEN:-}" ]; then
|
||||
export auth_key="$${CLOUDFLARE_API_TOKEN:-}"
|
||||
fi
|
||||
if [ -n "$${CLOUDFLARE_USERNAME:-}" ]; then
|
||||
export auth_email="$${CLOUDFLARE_USERNAME:-}"
|
||||
fi
|
||||
|
||||
# Ensure zone id is exported for the single-run script
|
||||
export zone_identifier="${zoneId}"
|
||||
|
||||
# Loop records from the Nix list. "@" maps to the base domain
|
||||
for r in ${recordsStr}; do
|
||||
if [ "$r" = "@" ]; then
|
||||
export record_name="${domain}"
|
||||
else
|
||||
export record_name="$r.${domain}"
|
||||
fi
|
||||
/etc/cloudflare-ddns/update-single.sh || true
|
||||
done
|
||||
|
||||
'';
|
||||
mode = "0755";
|
||||
group = "root";
|
||||
};
|
||||
|
||||
# Enable Tailscale for remote access to Traefik dashboard and configuration
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue