beyondcodebootcamp / duckdns.sh Goto Github PK
View Code? Open in Web Editor NEWA Posix Shell Script (Bash-compatible) for tracking your IP address (at home, or on devices)
License: Other
A Posix Shell Script (Bash-compatible) for tracking your IP address (at home, or on devices)
License: Other
Todos:
bump.sh
should create a github releaselastip
)These should go in the README and on Webi:
We only have this for POSIX systems. It would be great to have for Windows
https://github.com/BeyondCodeBootcamp/DuckDNS.sh/blob/main/duckdns.sh
Anyone with a Windows computer and scripting or programming experience.
Get GPT to translate and check the results function-by-function as duckdns.ps1
Here's what GPT came up with so far. It's not correct, but it gets a lot of the boilerplate out of the way. Or at least it feels like it does.
$ErrorActionPreference = "Stop"
# DuckDNS API docs: https://duckdns.org/spec.jsp
$DUCKDNS_SH_COMMAND = $Env:DUCKDNS_SH_COMMAND
if (-not $DUCKDNS_SH_COMMAND) {
$DUCKDNS_SH_COMMAND = $MyInvocation.MyCommand.Path
}
$DUCKDNS_SH_SUBCOMMAND = $args[0]
$DUCKDNS_SH_SUBDOMAIN = $args[1]
$DUCKDNS_SH_ARG_1 = $args[2]
$DUCKDNS_SH_ARG_2 = $args[3]
function cmd_auth {
if (-not $DUCKDNS_SH_SUBDOMAIN) {
Write-Output "You must provide a subdomain to authorize."
Write-Output "For reference, you already have tokens for:"
fn_list
return
}
if (Test-Path -Path "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env") {
Write-Output "'$DUCKDNS_SH_SUBDOMAIN.duckdns.org' is already ready to update"
Write-Output "(to delete: 'rm ~/.config/duckdns.sh/${DUCKDNS_SH_SUBDOMAIN}.env')"
} else {
fn_read_token
Write-Output "Created '~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env'"
}
}
function cmd_clear {
fn_require_subdomain
fn_duckdns_clear
}
function cmd_ip {
fn_require_subdomain
fn_require_dig
Write-Output "dig +short A $DUCKDNS_SH_SUBDOMAIN.duckdns.org"
$my_domain_ipv4 = (dig +short A "$DUCKDNS_SH_SUBDOMAIN.duckdns.org")
Write-Output "$DUCKDNS_SH_SUBDOMAIN.duckdns.org A $($my_domain_ipv4 -join ', ')"
Write-Output ""
Write-Output "dig +short AAAA $DUCKDNS_SH_SUBDOMAIN.duckdns.org"
$my_domain_ipv6 = (dig +short AAAA "$DUCKDNS_SH_SUBDOMAIN.duckdns.org")
Write-Output "$DUCKDNS_SH_SUBDOMAIN.duckdns.org AAAA $($my_domain_ipv6 -join ', ')"
Write-Output ""
}
function cmd_launcher_install {
fn_require_subdomain
fn_require_curl
if (-not (Test-Path -Path "$Env:UserProfile\.config\envman\PATH.env")) {
Invoke-Expression ((Invoke-WebRequest -Uri 'https://webi.sh/serviceman').Content)
. "$Env:UserProfile\.config\envman\PATH.env"
}
if (Test-Path -Path "C:\Windows\System32\launchctl.exe") {
serviceman add --user --name "sh.duckdns.$DUCKDNS_SH_SUBDOMAIN" -- "$DUCKDNS_SH_COMMAND" run $DUCKDNS_SH_SUBDOMAIN
return
}
if (Test-Path -Path "C:\Windows\System32\systemctl.exe") {
Write-Output ""
Write-Output "Running command: & 'C:\Windows\System32\serviceman.exe' add --system --path=\"$Env:Path\" --username $Env:UserName --name ""${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh"" -- ""$DUCKDNS_SH_COMMAND"" run $DUCKDNS_SH_SUBDOMAIN"
Write-Output ""
& 'C:\Windows\System32\serviceman.exe' add --system --path=$Env:Path --username $Env:UserName --name "${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh" -- "$DUCKDNS_SH_COMMAND" run $DUCKDNS_SH_SUBDOMAIN
Write-Output ""
Write-Output "Running command: & 'C:\Windows\System32\systemctl.exe' restart systemd-journald"
Write-Output ""
& 'C:\Windows\System32\systemctl.exe' restart systemd-journald
return
}
Write-Output "'launchctl' (macOS) and 'systemd' (Linux) are the only currently supported launchers"
}
function cmd_launcher_uninstall {
fn_require_subdomain
if (Test-Path -Path "$Env:UserProfile\Library\LaunchAgents\sh.duckdns.$DUCKDNS_SH_SUBDOMAIN.plist") {
Write-Output "launchctl unload -w $Env:UserProfile\Library\LaunchAgents\sh.duckdns.$DUCKDNS_SH_SUBDOMAIN.plist"
& "launchctl unload -w $Env:UserProfile\Library\LaunchAgents\sh.duckdns.$DUCKDNS_SH_SUBDOMAIN.plist"
Write-Output "Disabled login launcher."
} elseif (Test-Path -Path "C:\Windows\System32\systemctl.exe") {
Write-Output "Running command: & 'C:\Windows\System32\systemctl.exe' stop ""${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh"""
& 'C:\Windows\System32\systemctl.exe' stop "${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh"
Write-Output "Running command: & 'C:\Windows\System32\systemctl.exe' disable ""${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh"""
& 'C:\Windows\System32\systemctl.exe' disable "${DUCKDNS_SH_SUBDOMAIN}.duckdns-sh"
Write-Output "Disabled system launcher."
} else {
Write-Output "'launchctl' (macOS) and 'systemd' (Linux) are the only currently supported launchers"
}
}
function cmd_list {
Write-Output ""
fn_list
}
function cmd_myip {
fn_require_curl
Write-Output "Invoke-WebRequest -Uri 'https://api.ipify.org?format=text'"
$my_current_ipv4 = (Invoke-WebRequest -Uri 'https://api.ipify.org?format=text').Content
Write-Output "IPv4 $($my_current_ipv4)"
Write-Output ""
Write-Output "Invoke-WebRequest -Uri 'https://api64.ipify.org?format=text'"
$my_current_ipv6 = (Invoke-WebRequest -Uri 'https://api64.ipify.org?format=text').Content
if ($my_current_ipv4 -eq $my_current_ipv6) {
$my_current_ipv6 = $null
}
Write-Output "IPv6 $($my_current_ipv6)"
}
function cmd_run {
fn_require_subdomain
fn_require_env
fn_require_curl
fn_require_dig
$my_minutes = 1
$my_wait = ($my_minutes * 60)
while ($true) {
fn_update_ips
Write-Output ""
Write-Output ""
Write-Output "Waiting $my_minutes minute(s) to check '$DUCKDNS_SH_SUBDOMAIN' again..."
Start-Sleep -Seconds $my_wait
Write-Output ""
}
}
function cmd_set {
fn_require_subdomain
fn_require_env
fn_require_curl
if ($DUCKDNS_SH_ARG_2) {
fn_duckdns_update $DUCKDNS_SH_ARG_1 $DUCKDNS_SH_ARG_2
} else {
switch -wildcard ($DUCKDNS_SH_ARG_1) {
*:[a-fA-F0-9]* {
fn_duckdns_update '' $DUCKDNS_SH_ARG_1
}
*[0-9].[0-9]* {
fn_duckdns_update $DUCKDNS_SH_ARG_1 ''
}
default {
Write-Output "'$DUCKDNS_SH_ARG_1' is not a valid IPv4 or IPv6 address"
}
}
}
}
function cmd_update {
fn_require_subdomain
fn_require_env
fn_require_curl
fn_require_dig
fn_update_ips
}
function cmd_version {
$my_year = '2023'
$my_version = 'v1.0.3'
$my_date = '2023-01-15 00:49:52 +0000'
Write-Output "DuckDNS.sh $my_version ($my_date)"
Write-Output "Copyright $my_year AJ ONeal"
}
function fn_check_env {
if (-not (Test-Path -Path "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env")) {
return $false
}
. "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env"
if (-not $Env:DUCKDNS_TOKEN) {
return $false
}
return $true
}
function fn_duckdns_clear {
. "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env"
Write-Output "Invoke-WebRequest -Uri 'https://www.duckdns.org/update?domains=$DUCKDNS_SH_SUBDOMAIN&token=****&clear=true'"
Write-Output "Clearing IP address(es)..."
Invoke-WebRequest -Uri "https://www.duckdns.org/update?domains=$DUCKDNS_SH_SUBDOMAIN&token=$Env:DUCKDNS_TOKEN&clear=true" -UseBasicParsing
}
function fn_duckdns_update {
param(
[string]$my_ipv4,
[string]$my_ipv6
)
$my_ipv4_param = ""
if ($my_ipv4) {
$my_ipv4_param = "&ip=$my_ipv4"
}
$my_ipv6_param = ""
if ($my_ipv6) {
$my_ipv6_param = "&ipv6=$my_ipv6"
}
Write-Output "Invoke-WebRequest -Uri 'https://www.duckdns.org/update?domains=$DUCKDNS_SH_SUBDOMAIN&token=****$my_ipv4_param$my_ipv6_param'"
if ($my_ipv6_param) {
if ($my_ipv4_param) {
Write-Output "Updating IPv4 ($my_ipv4) and IPv6 ($my_ipv6)..."
} else {
Write-Output "Updating IPv6 ($my_ipv6)..."
}
} else {
if ($my_ipv4_param) {
Write-Output "Updating IPv4 ($my_ipv4)..."
} else {
Write-Output "at least one of ipv4 or ipv6 is required to update"
return
}
}
. "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env"
Invoke-WebRequest -Uri "https://www.duckdns.org/update?domains=$DUCKDNS_SH_SUBDOMAIN&token=$Env:DUCKDNS_TOKEN$my_ipv4_param$my_ipv6_param" -UseBasicParsing
Write-Output ""
}
function fn_list {
Write-Output "~/.config/duckdns.sh/:"
if (-not (Test-Path -Path "~/.config/duckdns.sh")) {
Write-Output "(directory does not exist)"
}
Get-ChildItem "~/.config/duckdns.sh" | ForEach-Object {
$my_domainenv = $_.Name -replace '.env$'
if ($my_domainenv -eq '*') {
Write-Output " (no subdomains have been configured)"
} else {
Write-Output " $my_domainenv"
}
}
Write-Output ""
}
function fn_read_secret {
param(
[string]$my_prompt
)
$password = Read-Host -Prompt $my_prompt -AsSecureString
$password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))
return $password
}
function fn_read_token {
$tokenPath = "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env"
if (-not (Test-Path -Path $tokenPath)) {
New-Item -Path $tokenPath -ItemType File
}
Write-Output "(the token will NOT BE SHOWN - just HIT ENTER after you paste it)"
$my_token = fn_read_secret "Duck DNS Token>"
Add-Content -Path $tokenPath -Value "DUCKDNS_TOKEN=$my_token"
}
function fn_require_curl {
if (-not (Test-Path -Path "C:\Windows\System32\curl.exe")) {
Write-Output "command 'curl' not found: please install 'curl'"
throw "Curl not found."
}
}
function fn_require_dig {
if (-not (Test-Path -Path "C:\Windows\System32\dig.exe")) {
Write-Output "command 'dig' not found: please install 'dig' (part of 'dnsutils')"
throw "Dig not found."
}
}
function fn_require_env {
if (-not (Test-Path -Path "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env")) {
Write-Output "Missing '~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env'"
throw "Environment file missing."
}
. "~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env"
if (-not $Env:DUCKDNS_TOKEN) {
Write-Output "Missing 'DUCKDNS_TOKEN=<uuid>' from '~/.config/duckdns.sh/$DUCKDNS_SH_SUBDOMAIN.env'"
throw "DUCKDNS_TOKEN not found in environment."
}
}
function fn_require_subdomain {
if (-not $DUCKDNS_SH_SUBDOMAIN) {
Write-Output "Missing <subdomain> argument. Try one of these in:"
fn_list
throw "Missing subdomain argument."
}
}
function fn_update_ips {
Write-Output "Invoke-WebRequest -Uri 'https://api.ipify.org?format=text'"
$my_domain_ipv4 = (Invoke-WebRequest -Uri 'https://api.ipify.org?format=text').Content
Write-Output "IPv4 $($my_domain_ipv4)"
Write-Output ""
Write-Output "Invoke-WebRequest -Uri 'https://api64.ipify.org?format=text'"
$my_domain_ipv6 = (Invoke-WebRequest -Uri 'https://api64.ipify.org?format=text').Content
if ($my_domain_ipv4 -eq $my_domain_ipv6) {
$my_domain_ipv6 = $null
}
Write-Output "IPv6 $($my_domain_ipv6)"
Write-Output ""
# if either ip changed to be empty
And then it stopped generating.
Function fn_update_ips {
Write-Output "dig +short A '${DUCKDNS_SH_SUBDOMAIN}.duckdns.org'"
$my_domain_ipv4 = (dig +short A "${DUCKDNS_SH_SUBDOMAIN}.duckdns.org")
Write-Output "${DUCKDNS_SH_SUBDOMAIN}.duckdns.org A ${my_domain_ipv4:-(NONE)}"
Write-Output ""
Write-Output "dig +short AAAA '${DUCKDNS_SH_SUBDOMAIN}.duckdns.org'"
$my_domain_ipv6 = (dig +short AAAA "${DUCKDNS_SH_SUBDOMAIN}.duckdns.org")
Write-Output "${DUCKDNS_SH_SUBDOMAIN}.duckdns.org AAAA ${my_domain_ipv6:-(NONE)}"
Write-Output ""
Write-Output "curl -fsSL 'https://api.ipify.org?format=text'"
$my_current_ipv4 = (curl --max-time 5.5 -Uri 'https://api.ipify.org?format=text')
Write-Output "IPv4 ${my_current_ipv4:-(NONE)}"
Write-Output ""
Write-Output "curl -fsSL 'https://api64.ipify.org?format=text'"
$my_current_ipv6 = (curl --max-time 5.5 -Uri 'https://api64.ipify.org?format=text')
if ($my_current_ipv4 -eq $my_current_ipv6) {
$my_current_ipv6 = ""
}
Write-Output "IPv6 ${my_current_ipv6:-(NONE)}"
Write-Output ""
# if either IP changed to be empty, clear both
if ($my_current_ipv4 -ne $my_domain_ipv4) {
if (-not $my_current_ipv4) {
fn_duckdns_clear
$my_domain_ipv4 = ""
$my_domain_ipv6 = ""
}
}
if ($my_current_ipv6 -ne $my_domain_ipv6) {
if (-not $my_current_ipv6) {
fn_duckdns_clear
$my_domain_ipv4 = ""
$my_domain_ipv6 = ""
}
}
# Note: at least one of the IPv4 or IPv6 *must* exist
# (otherwise we wouldn't even be able to get an empty response)
if ($my_current_ipv4 -ne $my_domain_ipv4) {
if ($my_current_ipv6 -ne $my_domain_ipv6) {
fn_duckdns_update $my_current_ipv4 $my_current_ipv6
}
else {
fn_duckdns_update $my_current_ipv4 ""
}
}
else {
if ($my_current_ipv6 -ne $my_domain_ipv6) {
fn_duckdns_update "" $my_current_ipv6
}
else {
Write-Output "No change detected."
}
}
}
Function fn_help {
Write-Output ""
Write-Output "USAGE"
Write-Output " duckdns.sh <subcommand> [arguments...]"
Write-Output ""
Write-Output "SUBCOMMANDS"
Write-Output " myip - show this device's ip(s)"
Write-Output " ip <subdomain> - show subdomain's ip(s)"
Write-Output ""
Write-Output " list - show subdomains"
Write-Output " auth <subdomain> - add Duck DNS token"
Write-Output " update <subdomain> - update subdomain to device ip"
Write-Output " set <subdomain> <ip> [ipv6] - set ipv4 and/or ipv6 explicitly"
Write-Output " clear <subdomain> - unset ip(s)"
Write-Output " run <subdomain> - check ip and update every 5m"
Write-Output " enable <subdomain> - enable on boot (Linux) or login (macOS)"
Write-Output " disable <subdomain> - disable on boot or login"
Write-Output ""
Write-Output " help - show this menu"
Write-Output " version - show version and exit"
Write-Output ""
}
Function main {
switch ($DUCKDNS_SH_SUBCOMMAND) {
"myip" { cmd_myip }
"ip" { cmd_ip }
"list" { cmd_list }
"auth" { cmd_auth }
"update" { cmd_update }
"set" { cmd_set }
"clear" { cmd_clear }
"run" { cmd_run }
"enable" { cmd_launcher_install }
"disable" { cmd_launcher_uninstall }
"help" { fn_help }
"version" { cmd_version }
default {
fn_help
exit 1
}
}
}
main
ipify
in 0cf350d)dig
in 0cf350d)enable
subcommand to setup and install as a service (via 652e188)init
auth
subcommand to setup new subdomain (via 652e188 and 05b4bb0)When we create the directories we should change the permissions to be more strict.
chmod 0700 ~/.config/duckdns.sh
chmod 0600 ~/.config/duckdns.sh/*.env
In most cases people will use this on single-user systems, so it's not that big of a deal, but it's a nice thing to do for when someone serves up their home directory, y'know.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.