Compare commits
42 Commits
2.2
...
1732de343d
| Author | SHA1 | Date | |
|---|---|---|---|
| 1732de343d | |||
| 51b2b455a2 | |||
| f8f3bf1539 | |||
| f8908d7830 | |||
| 8a5a72e15a | |||
| 1da49b06db | |||
| 1336c8e63b | |||
| 450733a8d5 | |||
| 92e69d1b85 | |||
| 00ea88780d | |||
| d9aa8973a3 | |||
| ec75cffbf1 | |||
| b8fb744623 | |||
| d0b2c781bf | |||
| c4d28d1f8c | |||
| f085838781 | |||
| 4a6c339a07 | |||
| c94f12e976 | |||
| 5443612381 | |||
| df144a5211 | |||
| 04811805c3 | |||
| aa446c00e6 | |||
| 56a5ad59fe | |||
| ce1ab86d22 | |||
| cae57ccf6b | |||
| 0fcd0d9c13 | |||
| e411a35b93 | |||
| 0e787be456 | |||
| 17c33d237f | |||
| 5b55ac95a3 | |||
| f4dc8f44b1 | |||
| 3792881529 | |||
| c1f54a619c | |||
| 4c997a6b7d | |||
| 1824d9f139 | |||
| 50b4cf105f | |||
| 1e3b545a23 | |||
| 380509c966 | |||
| 71da487aa8 | |||
| 3be6eaee0c | |||
| 5c6c8ec4ea | |||
| c7297a512c |
@@ -55,6 +55,23 @@
|
|||||||
*.bat text eol=crlf
|
*.bat text eol=crlf
|
||||||
*.cmd text eol=crlf
|
*.cmd text eol=crlf
|
||||||
|
|
||||||
|
# web frontend stack -- force LF so SRI hashes are always correct
|
||||||
|
*.html text eol=lf
|
||||||
|
*.htm text eol=lf
|
||||||
|
*.css text eol=lf
|
||||||
|
*.min.css text eol=lf
|
||||||
|
*.js text eol=lf
|
||||||
|
*.min.js text eol=lf
|
||||||
|
*.php text eol=lf
|
||||||
|
|
||||||
|
# Visual Studio projects (Rider also)
|
||||||
|
*.cs diff=csharp
|
||||||
|
*.sln merge=union
|
||||||
|
*.csproj merge=union
|
||||||
|
*.vbproj merge=union
|
||||||
|
*.fsproj merge=union
|
||||||
|
*.dbproj merge=union
|
||||||
|
|
||||||
# Serialisation
|
# Serialisation
|
||||||
*.json text
|
*.json text
|
||||||
*.toml text
|
*.toml text
|
||||||
@@ -79,3 +96,5 @@
|
|||||||
.gitattributes export-ignore
|
.gitattributes export-ignore
|
||||||
.gitignore export-ignore
|
.gitignore export-ignore
|
||||||
.gitkeep export-ignore
|
.gitkeep export-ignore
|
||||||
|
.idea export-ignore
|
||||||
|
.vscode export-ignore
|
||||||
|
|||||||
+34
@@ -1 +1,35 @@
|
|||||||
|
#
|
||||||
|
# JetBrains exclusions
|
||||||
|
#
|
||||||
|
|
||||||
|
riderModule.iml
|
||||||
|
/_ReSharper.Caches/
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
.idea/**/GitCommitMessageStorage.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# modules
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# project-specific exclusions
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DeveloperToolsToolWindowSettingsV1" lastSelectedContentNodeId="base64-encoder-decoder" pluginVersion="9.0.0">
|
||||||
|
<developerToolsConfigurations />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+15
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GitToolBoxProjectSettings">
|
||||||
|
<option name="commitMessageIssueKeyValidationOverride">
|
||||||
|
<BoolValueOverride>
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</BoolValueOverride>
|
||||||
|
</option>
|
||||||
|
<option name="commitMessageValidationEnabledOverride">
|
||||||
|
<BoolValueOverride>
|
||||||
|
<option name="enabled" value="true" />
|
||||||
|
</BoolValueOverride>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="com.itcodebox.notebooks.projectservice.ProjectUIState">
|
||||||
|
<option name="selectedNotebookId" value="1" />
|
||||||
|
<option name="selectedChapterId" value="1" />
|
||||||
|
<option name="selectedNoteId" value="1" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RiderProjectSettingsUpdater">
|
||||||
|
<option name="singleClickDiffPreview" value="1" />
|
||||||
|
<option name="unhandledExceptionsIgnoreList" value="1" />
|
||||||
|
<option name="vcsConfiguration" value="3" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
#
|
#
|
||||||
# update Cloudflare DNS records with current (dynamic) IP address
|
# update Cloudflare DNS records with current (dynamic) IP address
|
||||||
# Script by Asif Bacchus <asif@bacchus.cloud>
|
# Script by Asif Bacchus <asif@bacchus.cloud>
|
||||||
# Last modified: May 10, 2021
|
# Last modified: July 19, 2026
|
||||||
# Version 2.2
|
# Version 3.0
|
||||||
#
|
#
|
||||||
|
|
||||||
### text formatting presets using tput
|
### text formatting presets using tput
|
||||||
@@ -31,7 +31,6 @@ else
|
|||||||
width=80
|
width=80
|
||||||
fi
|
fi
|
||||||
|
|
||||||
### functions
|
|
||||||
badParam() {
|
badParam() {
|
||||||
if [ "$1" = "null" ]; then
|
if [ "$1" = "null" ]; then
|
||||||
printf "\n%sERROR: '%s' cannot have a NULL (empty) value.\n" "$err" "$2"
|
printf "\n%sERROR: '%s' cannot have a NULL (empty) value.\n" "$err" "$2"
|
||||||
@@ -47,65 +46,80 @@ badParam() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
exitError() {
|
getHostname() {
|
||||||
case "$1" in
|
(hostname -s)
|
||||||
3)
|
|
||||||
errMsg="Unable to connect to Cloudflare servers. This is probably a temporary networking issue. Please try again later."
|
|
||||||
;;
|
|
||||||
10)
|
|
||||||
errMsg="Unable to auto-detect IP address. Try again later or supply the IP address to be used."
|
|
||||||
;;
|
|
||||||
20)
|
|
||||||
errMsg="Cloudflare authorized email address (cfEmail) is either null or undefined. Please check your Cloudflare credentials file."
|
|
||||||
;;
|
|
||||||
21)
|
|
||||||
errMsg="Cloudflare authorized API key (cfKey) is either null or undefined. Please check your Cloudflare credentials file."
|
|
||||||
;;
|
|
||||||
22)
|
|
||||||
errMsg="Cloudflare zone id (cfZoneId) is either null or undefined. Please check your Cloudflare credentials file."
|
|
||||||
;;
|
|
||||||
25)
|
|
||||||
errMsg="Cloudflare API error. Please review any 'CF-ERR:' lines in this log for details."
|
|
||||||
;;
|
|
||||||
26)
|
|
||||||
errMsg="${failedHostCount} host update(s) failed. Any 'CF-ERR:' lines noted in this log may help determine what went wrong."
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
writeLog error "An unspecified error occurred. (code: 99)"
|
|
||||||
printf "%s[%s] -- Cloudflare DDNS update-script: completed with error(s) --%s\n" "$err" "$(stamp)" "$norm" >>"$logFile"
|
|
||||||
exit 99
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
writeLog error "$errMsg" "$1"
|
|
||||||
printf "%s[%s] -- Cloudflare DDNS update-script: completed with error(s) --%s\n" "$err" "$(stamp)" "$norm" >>"$logFile"
|
|
||||||
exit "$1"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exitOK() {
|
getTimeStamp() {
|
||||||
printf "%s[%s] -- Cloudflare DDNS update-script: completed successfully --%s\n" "$ok" "$(stamp)" "$norm" >>"$logFile"
|
(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
exit 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lowercase() {
|
||||||
|
echo "$1" | tr '[:upper:]' '[:lower:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
uppercase() {
|
||||||
|
echo "$1" | tr '[:lower:]' '[:upper:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1: cloudflare error object, 2: operation identifier
|
||||||
listCFErrors() {
|
listCFErrors() {
|
||||||
# extract error codes and messages in separate variables, replace newlines with underscores
|
# extract error codes and messages in separate variables, replace newlines with underscores
|
||||||
codes="$(printf "%s" "$1" | jq -r '.errors | .[] | .code' | tr '\n' '_')"
|
codes="$(printf "%s" "$1" | jq -r '.errors | .[] | .code' | tr '\n' '_')"
|
||||||
messages="$(printf "%s" "$1" | jq -r '.errors | .[] | .message' | tr '\n' '_')"
|
messages="$(printf "%s" "$1" | jq -r '.errors | .[] | .message' | tr '\n' '_')"
|
||||||
|
|
||||||
# iterate codes and messages and assemble into coherent messages in log
|
# iterate codes and messages, assemble into a coherent log message
|
||||||
while [ -n "$codes" ] && [ -n "$messages" ]; do
|
while [ -n "$codes" ] && [ -n "$messages" ]; do
|
||||||
# get first code and message in respective sets
|
# get the first code and message in respective sets
|
||||||
code="${codes%%_*}"
|
code="${codes%%_*}"
|
||||||
message="${messages%%_*}"
|
message="${messages%%_*}"
|
||||||
|
|
||||||
# update codes and messages sets by removing first item in each set
|
# update sets of codes and messages by removing the first item (above) in each set
|
||||||
codes="${codes#*_}"
|
codes="${codes#*_}"
|
||||||
messages="${messages#*_}"
|
messages="${messages#*_}"
|
||||||
|
|
||||||
# output to log
|
# output to log
|
||||||
writeLog cf "$message" "$code"
|
writePlainTextLog "${message}" "err" "fail" "$2" "$code"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
standardizeLogLevels() {
|
||||||
|
LEVEL_TO_STANDARDIZE="$(lowercase "$1")"
|
||||||
|
case "$LEVEL_TO_STANDARDIZE" in
|
||||||
|
"critical" | "crit" | "fatal")
|
||||||
|
echo "CRIT"
|
||||||
|
;;
|
||||||
|
"error" | "err")
|
||||||
|
echo "ERR"
|
||||||
|
;;
|
||||||
|
"warning" | "warn")
|
||||||
|
echo "WARN"
|
||||||
|
;;
|
||||||
|
"information" | "info")
|
||||||
|
echo "INFO"
|
||||||
|
;;
|
||||||
|
"debug" | "verbose")
|
||||||
|
echo "DEBUG"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "INFO"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# modified syslog fmt: <ISO-8601 timestamp> <hostname> <tag>[pid]: [LEVEL:$2] [STATUS:$3] <MESSAGE:$1> (<OPERATION:$4>:<CODE:$5>)
|
||||||
|
writePlainTextLog() {
|
||||||
|
# not enough information to generate a meaningful log message
|
||||||
|
if [ -z "$1" ] || [ $# -lt 3 ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s %s %s[%s]: [%s] [%s] %s (%s:%s)\n" \
|
||||||
|
"$(getTimeStamp)" "$(getHostname)" "$LOG_PROGRAM_NAME" "$$" \
|
||||||
|
"$(standardizeLogLevels "$2")" "$(uppercase "$3")" "$1" "${4:-UNSPECIFIED}" "${5:-0}" \
|
||||||
|
>>"$logFile"
|
||||||
|
}
|
||||||
|
|
||||||
scriptExamples() {
|
scriptExamples() {
|
||||||
newline
|
newline
|
||||||
printf "Update Cloudflare DNS host A/AAAA records with current IP address.\n"
|
printf "Update Cloudflare DNS host A/AAAA records with current IP address.\n"
|
||||||
@@ -157,6 +171,10 @@ scriptHelp() {
|
|||||||
textBlock "Switch value. Output log to console (stdout) instead of a log file. Can be combined with --nc if desired."
|
textBlock "Switch value. Output log to console (stdout) instead of a log file. Can be combined with --nc if desired."
|
||||||
textBlockDefaults "(disabled: output to log file)"
|
textBlockDefaults "(disabled: output to log file)"
|
||||||
newline
|
newline
|
||||||
|
textBlockSwitches "--log-journal"
|
||||||
|
textBlock "Switch value. Output a structured log entry to your journaling system instead of a log file. Implies --nc."
|
||||||
|
textBlockDefaults "(disabled: output to log file)"
|
||||||
|
newline
|
||||||
textBlockSwitches "--no-log"
|
textBlockSwitches "--no-log"
|
||||||
textBlock "Switch value. Do not create a log (i.e. no console, no file). You will not have *any* output from the script if you choose this option, so you will not know if updates succeeded or failed."
|
textBlock "Switch value. Do not create a log (i.e. no console, no file). You will not have *any* output from the script if you choose this option, so you will not know if updates succeeded or failed."
|
||||||
textBlockDefaults "(disabled: output to log file)"
|
textBlockDefaults "(disabled: output to log file)"
|
||||||
@@ -188,10 +206,6 @@ scriptHelp() {
|
|||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
stamp() {
|
|
||||||
(date +%F" "%T)
|
|
||||||
}
|
|
||||||
|
|
||||||
newline() {
|
newline() {
|
||||||
printf "\n"
|
printf "\n"
|
||||||
}
|
}
|
||||||
@@ -208,51 +222,11 @@ textBlockSwitches() {
|
|||||||
printf "%s%s%s\n" "$cyan" "$1" "$norm"
|
printf "%s%s%s\n" "$cyan" "$1" "$norm"
|
||||||
}
|
}
|
||||||
|
|
||||||
writeLog() {
|
|
||||||
case "$1" in
|
|
||||||
cf)
|
|
||||||
printf "[%s] CF-ERR: %s (code: %s)\n" "$(stamp)" "$2" "$3" >>"$logFile"
|
|
||||||
;;
|
|
||||||
err)
|
|
||||||
printf "%s[%s] ERR: %s%s\n" "$err" "$(stamp)" "$2" "$norm" >>"$logFile"
|
|
||||||
;;
|
|
||||||
error)
|
|
||||||
printf "%s[%s] ERROR: %s (code: %s)%s\n" "$err" "$(stamp)" "$2" "$3" "$norm" >>"$logFile"
|
|
||||||
;;
|
|
||||||
process)
|
|
||||||
printf "%s[%s] %s... %s" "$cyan" "$(stamp)" "$2" "$norm" >>"$logFile"
|
|
||||||
;;
|
|
||||||
process-done)
|
|
||||||
printf "%s%s%s\n" "$cyan" "$2" "$norm" >>"$logFile"
|
|
||||||
;;
|
|
||||||
process-error)
|
|
||||||
printf "%sERROR%s\n" "$err" "$norm" >>"$logFile"
|
|
||||||
;;
|
|
||||||
process-warning)
|
|
||||||
printf "%s%s%s\n" "$warn" "$2" "$norm" >>"$logFile"
|
|
||||||
;;
|
|
||||||
stamped)
|
|
||||||
printf "[%s] %s\n" "$(stamp)" "$2" >>"$logFile"
|
|
||||||
;;
|
|
||||||
success)
|
|
||||||
printf "%s[%s] SUCCESS: %s%s\n" "$ok" "$(stamp)" "$2" "$norm" >>"$logFile"
|
|
||||||
;;
|
|
||||||
warn)
|
|
||||||
printf "%s[%s] WARN: %s%s\n" "$warn" "$(stamp)" "$2" "$norm" >>"$logFile"
|
|
||||||
;;
|
|
||||||
warning)
|
|
||||||
printf "%s[%s] WARNING: %s%s\n" "$warn" "$(stamp)" "$2" "$norm" >>"$logFile"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
printf "%s\n" "$2" >>"$logFile"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
### default variable values
|
### default variable values
|
||||||
scriptPath="$(CDPATH='' \cd -- "$(dirname -- "$0")" && pwd -P)"
|
scriptPath="$(CDPATH='' \cd -- "$(dirname -- "$0")" && pwd -P)"
|
||||||
scriptName="$(basename "$0")"
|
scriptName="$(basename "$0")"
|
||||||
logFile="$scriptPath/${scriptName%.*}.log"
|
logFile="$scriptPath/${scriptName%.*}.log"
|
||||||
|
logToJournal=0
|
||||||
accountFile="$scriptPath/cloudflare.credentials"
|
accountFile="$scriptPath/cloudflare.credentials"
|
||||||
colourizeLogFile=1
|
colourizeLogFile=1
|
||||||
dnsRecords=""
|
dnsRecords=""
|
||||||
@@ -292,6 +266,10 @@ while [ $# -gt 0 ]; do
|
|||||||
# log to the console instead of a file
|
# log to the console instead of a file
|
||||||
logFile="/dev/stdout"
|
logFile="/dev/stdout"
|
||||||
;;
|
;;
|
||||||
|
--log-journal)
|
||||||
|
# log to the journal system instead of a file
|
||||||
|
logToJournal=1
|
||||||
|
;;
|
||||||
--no-log)
|
--no-log)
|
||||||
# do not log anything
|
# do not log anything
|
||||||
logFile="/dev/null"
|
logFile="/dev/null"
|
||||||
@@ -353,16 +331,26 @@ done
|
|||||||
### pre-flight checks
|
### pre-flight checks
|
||||||
if ! command -v curl >/dev/null; then
|
if ! command -v curl >/dev/null; then
|
||||||
printf "\n%sThis script requires 'curl' be installed and accessible. Exiting.%s\n\n" "$err" "$norm"
|
printf "\n%sThis script requires 'curl' be installed and accessible. Exiting.%s\n\n" "$err" "$norm"
|
||||||
|
writePlainTextLog "'curl' must be installed and accessible" "fatal" "fail" "preexec" 2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
if ! command -v jq >/dev/null; then
|
if ! command -v jq >/dev/null; then
|
||||||
printf "\n%sThis script requires 'jq' be installed and accessible. Exiting.%s\n\n" "$err" "$norm"
|
printf "\n%sThis script requires 'jq' be installed and accessible. Exiting.%s\n\n" "$err" "$norm"
|
||||||
|
writePlainTextLog "'jq' must be installed and accessible" "fatal" "fail" "preexec" 2
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
[ -z "$dnsRecords" ] && badParam errMsg "You must specify at least one DNS record to update. Exiting."
|
if [ -z "$dnsRecords" ]; then
|
||||||
# verify credentials file exists and is not empty (default check)
|
badParam errMsg "You must specify at least one DNS record to update. Exiting."
|
||||||
|
writePlainTextLog "At least one DNS record to update must be specified" "fatal" "fail" "params" 1
|
||||||
|
fi
|
||||||
|
# verify the credentials file exists and is not empty (default check)
|
||||||
if [ ! -f "$accountFile" ] || [ ! -s "$accountFile" ]; then
|
if [ ! -f "$accountFile" ] || [ ! -s "$accountFile" ]; then
|
||||||
badParam errMsg "Cannot find Cloudflare credentials file (${accountFile}). Exiting."
|
badParam errMsg "Cannot find the Cloudflare credentials file (${accountFile}). Exiting."
|
||||||
|
writePlainTextLog "Cannot find the specified Cloudflare credentials file (${accountFile})" "fatal" "fail" "params" 1
|
||||||
|
fi
|
||||||
|
if [ "$logToJournal" -eq 1 ] && ! command -v logger >/dev/null 2>&1; then
|
||||||
|
printf "\n%sThis script requires 'logger' be installed to write entries to your journaling system. Exiting.%s\n\n" "$err" "$norm"
|
||||||
|
exit 2
|
||||||
fi
|
fi
|
||||||
# turn off log file colourization if parameter is set
|
# turn off log file colourization if parameter is set
|
||||||
if [ "$colourizeLogFile" -eq 0 ]; then
|
if [ "$colourizeLogFile" -eq 0 ]; then
|
||||||
@@ -377,74 +365,72 @@ if [ "$colourizeLogFile" -eq 0 ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
### initial log entries
|
### initial log entries
|
||||||
{
|
writePlainTextLog "starting '${scriptName}'" "info" "ok" "script"
|
||||||
printf "%s[%s] -- Cloudflare DDNS update-script: starting --%s\n" "$ok" "$(stamp)" "$norm"
|
writePlainTextLog "script path: ${scriptPath}/${scriptName}" "debug" "ok" "startup"
|
||||||
printf "Parameters:\n"
|
writePlainTextLog "credentials file: ${accountFile}" "debug" "ok" "startup"
|
||||||
printf "script path: %s\n" "$scriptPath/$scriptName"
|
|
||||||
printf "credentials file: %s\n" "$accountFile"
|
|
||||||
|
|
||||||
if [ "$ip4" -eq 1 ]; then
|
if [ "$ip4" -eq 1 ]; then
|
||||||
printf "mode: IP4\n"
|
writePlainTextLog "mode: IPv4" "debug" "ok" "startup"
|
||||||
elif [ "$ip6" -eq 1 ]; then
|
elif [ "$ip6" -eq 1 ]; then
|
||||||
printf "mode: IP6\n"
|
writePlainTextLog "mode: IPv6" "debug" "ok" "startup"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# detect and report IP address
|
# detect and report IP address
|
||||||
if [ -z "$ipAddress" ]; then
|
if [ -z "$ipAddress" ]; then
|
||||||
# detect public ip address
|
# detect public ip address
|
||||||
if [ "$ip4" -eq 1 ]; then
|
if [ "$ip4" -eq 1 ]; then
|
||||||
if ! ipAddress="$(curl -s $ip4DetectionSvc)"; then
|
if ! ipAddress="$(curl -s $ip4DetectionSvc)"; then
|
||||||
printf "ddns ip address:%s ERROR%s\n" "$err" "$norm"
|
writePlainTextLog \
|
||||||
exitError 10
|
"Unable to auto-detect this machine's public IP address; try again later or supply the IP address to be used" "err" "fail" "detectip" 10
|
||||||
|
exit 10
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ "$ip6" -eq 1 ]; then
|
if [ "$ip6" -eq 1 ]; then
|
||||||
if ! ipAddress="$(curl -s $ip6DetectionSvc)"; then
|
if ! ipAddress="$(curl -s $ip6DetectionSvc)"; then
|
||||||
printf "ddns ip address:%s ERROR%s\n" "$err" "$norm"
|
writePlainTextLog \
|
||||||
exitError 10
|
"Unable to auto-detect this machine's public IP address; try again later or supply the IP address to be used" "err" "fail" "detectip" 10
|
||||||
|
exit 10
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
printf "ddns ip address (detected): %s\n" "$ipAddress"
|
writePlainTextLog "DDNS IP address (detected): $ipAddress" "info" "ok" "startup"
|
||||||
else
|
else
|
||||||
printf "ddns ip address (supplied): %s\n" "$ipAddress"
|
writePlainTextLog "DDNS IP address (supplied): $ipAddress" "info" "ok" "startup"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# iterate DNS records to update
|
# iterate DNS records to update
|
||||||
dnsRecordsToUpdate="$(printf '%s' "${dnsRecords}" | sed "s/${dnsSeparator}*$//")$dnsSeparator"
|
dnsRecordsToUpdate="$(printf '%s' "${dnsRecords}" | sed "s/${dnsSeparator}*$//")$dnsSeparator"
|
||||||
while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator" ]; do
|
while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator" ]; do
|
||||||
record="${dnsRecordsToUpdate%%${dnsSeparator}*}"
|
record="${dnsRecordsToUpdate%%${dnsSeparator}*}"
|
||||||
dnsRecordsToUpdate="${dnsRecordsToUpdate#*${dnsSeparator}}"
|
dnsRecordsToUpdate="${dnsRecordsToUpdate#*${dnsSeparator}}"
|
||||||
|
|
||||||
if [ -z "$record" ]; then continue; fi
|
if [ -z "$record" ]; then
|
||||||
printf "updating record: %s\n" "$record"
|
continue
|
||||||
done
|
fi
|
||||||
|
writePlainTextLog "DNS host record '${record}' queued for update" "info" "ok" "startup"
|
||||||
printf "(end of parameter list)\n"
|
done
|
||||||
} >>"$logFile"
|
|
||||||
|
|
||||||
### read Cloudflare credentials
|
### read Cloudflare credentials
|
||||||
writeLog process "Reading Cloudflare credentials"
|
|
||||||
case "$accountFile" in
|
case "$accountFile" in
|
||||||
/*)
|
/*)
|
||||||
# absolute path, use as-is
|
# absolute path, use as-is
|
||||||
# shellcheck source=./cloudflare.credentials
|
# shellcheck source=./cloudflare.credentials
|
||||||
. "$accountFile"
|
. "$accountFile"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# relative path, rewrite
|
# relative path, rewrite
|
||||||
# shellcheck source=./cloudflare.credentials
|
# shellcheck source=./cloudflare.credentials
|
||||||
. "./$accountFile"
|
. "./$accountFile"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
if [ -z "$cfKey" ]; then
|
if [ -z "$cfKey" ]; then
|
||||||
writeLog process-error
|
writePlainTextLog "Cloudflare authorized API key (cfKey) is either null or undefined; please check your Cloudflare credentials file" "err" "fail" "creds" 21
|
||||||
exitError 21
|
exit 21
|
||||||
fi
|
fi
|
||||||
if [ -z "$cfZoneId" ]; then
|
if [ -z "$cfZoneId" ]; then
|
||||||
writeLog process-error
|
writePlainTextLog "Cloudflare zone id (cfZoneId) is either null or undefined; please check your Cloudflare credentials file" "err" "fail" "creds" 22
|
||||||
exitError 22
|
exit 22
|
||||||
fi
|
fi
|
||||||
writeLog process-done "DONE"
|
writePlainTextLog "Cloudflare credentials file read successfully" "debug" "ok" "creds"
|
||||||
|
|
||||||
### connect to Cloudflare and do what needs to be done!
|
### connect to Cloudflare and do what needs to be done!
|
||||||
dnsRecordsToUpdate="$dnsRecords$dnsSeparator"
|
dnsRecordsToUpdate="$dnsRecords$dnsSeparator"
|
||||||
@@ -459,70 +445,69 @@ while [ -n "$dnsRecordsToUpdate" ] && [ "$dnsRecordsToUpdate" != "$dnsSeparator"
|
|||||||
record="${dnsRecordsToUpdate%%${dnsSeparator}*}"
|
record="${dnsRecordsToUpdate%%${dnsSeparator}*}"
|
||||||
dnsRecordsToUpdate="${dnsRecordsToUpdate#*${dnsSeparator}}"
|
dnsRecordsToUpdate="${dnsRecordsToUpdate#*${dnsSeparator}}"
|
||||||
|
|
||||||
if [ -z "$record" ]; then continue; fi
|
if [ -z "$record" ]; then
|
||||||
writeLog process "Processing ${record}"
|
continue
|
||||||
|
|
||||||
# exit if curl/network error
|
|
||||||
if ! cfLookup="$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${cfZoneId}/dns_records?name=${record}&type=${recordType}" \
|
|
||||||
-H "Authorization: Bearer ${cfKey}" \
|
|
||||||
-H "Content-Type: application/json")"; then
|
|
||||||
writeLog process-error
|
|
||||||
exitError 3
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# exit if API error
|
# exit if curl/network error
|
||||||
# exit here since API errors on GET request probably indicates authentication error which would affect all remaining operations
|
if ! cfLookup="$(
|
||||||
# no reason to continue processing other hosts and pile-up errors which might look like a DoS attempt
|
curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${cfZoneId}/dns_records?name=${record}&type=${recordType}" \
|
||||||
|
-H "Authorization: Bearer ${cfKey}" \
|
||||||
|
-H "Content-Type: application/json"
|
||||||
|
)"; then
|
||||||
|
writePlainTextLog "Unable to connect to Cloudflare servers; please try again later." "err" "fail" "cflogin" 3
|
||||||
|
exit 3
|
||||||
|
fi
|
||||||
|
|
||||||
|
# an API error on this GET request likely indicates an authentication error that would affect all remaining operations
|
||||||
|
# no reason to continue processing other dns records and pile-up errors which might look like a DoS attempt
|
||||||
cfSuccess="$(printf "%s" "$cfLookup" | jq -r '.success')"
|
cfSuccess="$(printf "%s" "$cfLookup" | jq -r '.success')"
|
||||||
if [ "$cfSuccess" = "false" ]; then
|
if [ "$cfSuccess" = "false" ]; then
|
||||||
writeLog process-error
|
listCFErrors "$cfLookup" "cflogin"
|
||||||
listCFErrors "$cfLookup"
|
writePlainTextLog "Cloudflare API error; review any previously logged 'CF-ERR:' lines for details." "err" "fail" "cflogin" 25
|
||||||
exitError 25
|
exit 25
|
||||||
fi
|
fi
|
||||||
|
|
||||||
resultCount="$(printf "%s" "$cfLookup" | jq '.result_info.count')"
|
resultCount="$(printf "%s" "$cfLookup" | jq '.result_info.count')"
|
||||||
|
|
||||||
# skip to next host if cannot find existing host record (this script *updates* only, does not create!)
|
# skip to the next host if an existing host record cannot be found (this script *updates* only, does not create!)
|
||||||
if [ "$resultCount" = "0" ]; then
|
if [ "$resultCount" = "0" ]; then
|
||||||
# warn if record of host not found
|
writePlainTextLog "Cannot find an existing record matching '${record}' to update" "warn" "warn" "ddns"
|
||||||
writeLog process-warning "NOT FOUND"
|
|
||||||
writeLog warn "Cannot find existing record to update for DNS entry: ${record}"
|
|
||||||
invalidDomainCount=$((invalidDomainCount + 1))
|
invalidDomainCount=$((invalidDomainCount + 1))
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
objectId=$(printf "%s" "$cfLookup" | jq -r '.result | .[] | .id')
|
objectId=$(printf "%s" "$cfLookup" | jq -r '.result | .[] | .id')
|
||||||
currentIpAddr=$(printf "%s" "$cfLookup" | jq -r '.result | .[] | .content')
|
currentIpAddr=$(printf "%s" "$cfLookup" | jq -r '.result | .[] | .content')
|
||||||
writeLog process-done "FOUND: IP = ${currentIpAddr}"
|
writePlainTextLog "The current IP address for '${record}' is ${currentIpAddr}" "debug" "ok" "ddns"
|
||||||
|
|
||||||
# skip to next hostname if record already up-to-date
|
# skip to next hostname if record already up to date
|
||||||
if [ "$currentIpAddr" = "$ipAddress" ]; then
|
if [ "$currentIpAddr" = "$ipAddress" ]; then
|
||||||
writeLog stamped "IP address for ${record} is already up-to-date"
|
writePlainTextLog "The IP address for '${record}' is already up to date" "info" "ok" "ddns"
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# update record
|
# update record
|
||||||
writeLog process "Updating IP address for ${record}"
|
|
||||||
updateJSON="$(jq -n --arg key0 content --arg value0 "${ipAddress}" '{($key0):$value0}')"
|
updateJSON="$(jq -n --arg key0 content --arg value0 "${ipAddress}" '{($key0):$value0}')"
|
||||||
|
|
||||||
# exit if curl/network error
|
# exit if curl/network error
|
||||||
if ! cfResult="$(curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/${cfZoneId}/dns_records/${objectId}" \
|
if ! cfResult="$(
|
||||||
|
curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/${cfZoneId}/dns_records/${objectId}" \
|
||||||
-H "Authorization: Bearer ${cfKey}" \
|
-H "Authorization: Bearer ${cfKey}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
--data "${updateJSON}")"; then
|
--data "${updateJSON}"
|
||||||
writeLog process-error
|
)"; then
|
||||||
exitError 3
|
writePlainTextLog "Unable to connect to Cloudflare servers; please try again later." "err" "fail" "ddns" 3
|
||||||
|
exit 3
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# note update success or failure
|
# note update success or failure
|
||||||
cfSuccess="$(printf "%s" "$cfResult" | jq '.success')"
|
cfSuccess="$(printf "%s" "$cfResult" | jq '.success')"
|
||||||
if [ "$cfSuccess" = "true" ]; then
|
if [ "$cfSuccess" = "true" ]; then
|
||||||
writeLog process-done "DONE"
|
writePlainTextLog "The IP address for '${record}' successfully updated" "info" "ok" "ddns"
|
||||||
writeLog success "IP address for ${record} updated."
|
|
||||||
else
|
else
|
||||||
writeLog process-error
|
listCFErrors "$cfResult" "ddns"
|
||||||
listCFErrors "$cfResult"
|
writePlainTextLog "Unable to update the IP address for '${record}'" "err" "fail" "ddns"
|
||||||
writeLog err "Unable to update IP address for ${record}"
|
|
||||||
# do not exit with error, API error here is probably an update issue specific to this host
|
# do not exit with error, API error here is probably an update issue specific to this host
|
||||||
# increment counter and note it after all processing finished
|
# increment counter and note it after all processing finished
|
||||||
failedHostCount=$((failedHostCount + 1))
|
failedHostCount=$((failedHostCount + 1))
|
||||||
@@ -531,12 +516,14 @@ done
|
|||||||
|
|
||||||
# exit
|
# exit
|
||||||
if [ "$invalidDomainCount" -ne 0 ]; then
|
if [ "$invalidDomainCount" -ne 0 ]; then
|
||||||
writeLog warning "${invalidDomainCount} invalid host(s) supplied for updating."
|
writePlainTextLog "${invalidDomainCount} invalid host(s) were supplied for updating" "warn" "warn" "ddns"
|
||||||
fi
|
fi
|
||||||
if [ "$failedHostCount" -ne 0 ]; then
|
if [ "$failedHostCount" -ne 0 ]; then
|
||||||
exitError 26
|
writePlainTextLog \
|
||||||
|
"${failedHostCount} host update(s) failed; review 'CF-ERR:' lines in this log to help determine what may have gone wrong" "err" "fail" "ddns" 26
|
||||||
|
exit 26
|
||||||
else
|
else
|
||||||
exitOK
|
writePlainTextLog "${scriptName} completed successfully" "info" "ok" "script"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
### exit return codes
|
### exit return codes
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
getTimeStamp() {
|
||||||
|
(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
}
|
||||||
|
|
||||||
|
getHostname() {
|
||||||
|
(hostname -s)
|
||||||
|
}
|
||||||
|
|
||||||
|
lowercase() {
|
||||||
|
echo "$1" | tr '[:upper:]' '[:lower:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
uppercase() {
|
||||||
|
echo "$1" | tr '[:lower:]' '[:upper:]'
|
||||||
|
}
|
||||||
|
|
||||||
|
standardizeLogLevels() {
|
||||||
|
LEVEL_TO_STANDARDIZE="$(lowercase "$1")"
|
||||||
|
case "$LEVEL_TO_STANDARDIZE" in
|
||||||
|
"critical" | "crit" | "fatal")
|
||||||
|
echo "CRIT"
|
||||||
|
;;
|
||||||
|
"error" | "err")
|
||||||
|
echo "ERR"
|
||||||
|
;;
|
||||||
|
"warning" | "warn")
|
||||||
|
echo "WARN"
|
||||||
|
;;
|
||||||
|
"information" | "info")
|
||||||
|
echo "INFO"
|
||||||
|
;;
|
||||||
|
"debug" | "verbose")
|
||||||
|
echo "DEBUG"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "INFO"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
translateLogLevelsToJournalD() {
|
||||||
|
LEVEL_TO_TRANSLATE="$(standardizeLogLevels "$1")"
|
||||||
|
case "$LEVEL_TO_TRANSLATE" in
|
||||||
|
"CRIT")
|
||||||
|
echo 2
|
||||||
|
;;
|
||||||
|
"ERR")
|
||||||
|
echo 3
|
||||||
|
;;
|
||||||
|
"WARN")
|
||||||
|
echo 4
|
||||||
|
;;
|
||||||
|
"INFO")
|
||||||
|
echo 6
|
||||||
|
;;
|
||||||
|
"DEBUG")
|
||||||
|
echo 7
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo 6
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# modified syslog fmt: <ISO-8601 timestamp> <hostname> <tag>[pid]: [LEVEL:$2] [STATUS:$3] <MESSAGE:$1> (<OPERATION:$4>:<CODE:$5>)
|
||||||
|
writePlainTextLog() {
|
||||||
|
# not enough information to generate a meaningful log message
|
||||||
|
if [ -z "$1" ] || [ $# -lt 3 ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s %s %s[%s]: [%s] [%s] %s (%s:%s)\n" \
|
||||||
|
"$(getTimeStamp)" "$(getHostname)" "$LOG_PROGRAM_NAME" "$$" \
|
||||||
|
"$(standardizeLogLevels "$2")" "$(uppercase "$3")" "$1" "${4:-UNSPECIFIED}" "${5:-999}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# $1: message, $2: level, $3: status, [$4: operation], [$5: code]
|
||||||
|
writeJsonLog() {
|
||||||
|
# not enough information to generate a meaningful log message
|
||||||
|
if [ -z "$1" ] || [ $# -lt 3 ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
PROPS="$(
|
||||||
|
jq --null-input \
|
||||||
|
--arg timestamp "$(getTimeStamp)" \
|
||||||
|
--arg hostname "$(getHostname)" \
|
||||||
|
--arg pName "$LOG_PROGRAM_NAME" \
|
||||||
|
--arg pid "$$" \
|
||||||
|
--arg level "$(standardizeLogLevels "$2")" \
|
||||||
|
--arg status "$(uppercase "$3")" \
|
||||||
|
--arg message "$1" \
|
||||||
|
--arg operation "${4:-UNSPECIFIED}" \
|
||||||
|
--arg code "${5:-999}" \
|
||||||
|
'{"timestamp":$timestamp, "hostname":$hostname, "programName":$pName, "pid":$pid, "level":$level, "status":$status, "message":$message, "operation":$operation, "code":$code}' \
|
||||||
|
)"
|
||||||
|
|
||||||
|
echo "$PROPS" | jq "."
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# $1: message, $2: level, $3: status, $4: operation, $5: code
|
||||||
|
writeJournalDLog() {
|
||||||
|
# not enough information to generate a meaningful log message
|
||||||
|
if [ -z "$1" ] || [ $# -lt 3 ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
J_TIMESTAMP="$(getTimeStamp)"
|
||||||
|
J_PID="$$"
|
||||||
|
J_PRIO="$(translateLogLevelsToJournalD "$2")"
|
||||||
|
J_STAT="$(uppercase "$3")"
|
||||||
|
|
||||||
|
logger --journald <<end
|
||||||
|
SYSLOG_TIMESTAMP=${J_TIMESTAMP}
|
||||||
|
SYSLOG_FACILITY=3
|
||||||
|
SYSLOG_IDENTIFIER=${LOG_PROGRAM_NAME}
|
||||||
|
SYSLOG_PID=${J_PID}
|
||||||
|
PRIORITY=${J_PRIO}
|
||||||
|
STATUS=${J_STAT}
|
||||||
|
MESSAGE=$1
|
||||||
|
OPERATION=${4:-UNSPECIFIED}
|
||||||
|
ERRNO=${5:-999}
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_PROGRAM_NAME="cfddns"
|
||||||
|
|
||||||
|
if [ "$1" = "text" ]; then
|
||||||
|
writePlainTextLog # this will do nothing
|
||||||
|
writePlainTextLog "Testing plain-text logging output" # this will also do nothing
|
||||||
|
writePlainTextLog "Informational plain-text log message" "information" "ok" # info message
|
||||||
|
writePlainTextLog "Plain-text message forced to info level" "whoops" "ok" # level translated to default
|
||||||
|
writePlainTextLog "Warning! This is plain-text." "warning" "flagged" # warning level with 'flagged' status
|
||||||
|
writePlainTextLog "Error sample! This is plain-text." "err" "error" # error level message
|
||||||
|
writePlainTextLog "Unable to continue, critical failure. (This is plain-text)" "fatal" "exit" "startup" "2" # critical level error with operation and error code
|
||||||
|
elif [ "$1" = "json" ]; then
|
||||||
|
writeJsonLog # this will do nothing
|
||||||
|
writeJsonLog "This is a test message" # this will also do nothing
|
||||||
|
writeJsonLog "Informational JSON log message" "information" "ok" # info message
|
||||||
|
writeJsonLog "JSON message forced to info level" "whoops" "ok" # level translated to default
|
||||||
|
writeJsonLog "Warning! This is JSON." "warning" "flagged" # warning level with 'flagged' status
|
||||||
|
writeJsonLog "Error sample! This is JSON." "err" "error" # error level message
|
||||||
|
writeJsonLog "Unable to continue, critical failure. (This is JSON)" "fatal" "exit" "startup" "2" # critical level error with operation and error code
|
||||||
|
elif [ "$1" = "journal" ]; then
|
||||||
|
writeJournalDLog # this will do nothing
|
||||||
|
writeJournalDLog "This is a test message" # this will also do nothing
|
||||||
|
writeJournalDLog "Informational JOURNALD log message" "information" "ok" # info message
|
||||||
|
writeJournalDLog "JOURNALD message forced to info level" "whoops" "ok" # level translated to default
|
||||||
|
writeJournalDLog "Warning! This is a JOURNALD message." "warning" "flagged" # warning level with 'flagged' status
|
||||||
|
writeJournalDLog "Error sample! This is a JOURNALD." "err" "error" # error level message
|
||||||
|
writeJournalDLog "Unable to continue, critical failure. (This is JOURNALD message)" "fatal" "exit" "startup" "2" # critical level error with operation and error code
|
||||||
|
else
|
||||||
|
printf "\nPlease specify either 'text', 'json', or 'journal' after the script name. Exiting.\n\n"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user