Fix the PowerShell script (via Prompt Engineering)
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
Research direction
Compare the existing duckdns.sh with the proposed duckdns.ps1, starting with the DuckDNS API documentation and the generated functions shown in the issue. Validate the translation function by function on Windows, then complete and verify equivalent subcommands and behavior; done means a usable PowerShell implementation rather than the currently incomplete draft.
Written by the indexing model from the issue text.
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 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
- Dominant language
- Shell
- Stars
- 32
- Forks
- 3
- PR merge metrics
- No merged PRs in 30d
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from BeyondCodeBootcamp/DuckDNS.sh
-
Difficulty 2/5 1-3 hours Newbie friendliness 52/100
-
Difficulty 5/5 Over a week Newbie friendliness 20/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 45/100
All issues in BeyondCodeBootcamp/DuckDNS.sh
Similar issues
-
Difficulty 1/5 Under an hour Newbie friendliness 85/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
ajeetdsouza/zoxide#1323 ·
Maintainers usually reply within 1 day
-
e2e-failure ready-to-code
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
redhat-developer/rhdh-plugin-export-overlays#4391 · 1 comment ·
Maintainers usually reply within 1 day
-
documentation good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
graygarbage/HermesKey#20 ·
-
Version-drift constraint evaluator (#40): validate the candidate version and run its unit test in CIOpen
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
n8n-io/terraform-google-n8n#41 ·
Maintainers usually reply within 5 days