-
Notifications
You must be signed in to change notification settings - Fork 2
Open
Labels
enhancementNew feature or requestNew feature or requestgood first issueGood for newcomersGood for newcomershacktoberfestup-for-grabs
Description
Problem
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
Who can solve it?
Anyone with a Windows computer and scripting or programming experience.
Solution
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 emptyAnd 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
}
}
}
mainReactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or requestgood first issueGood for newcomersGood for newcomershacktoberfestup-for-grabs