Git Product home page Git Product logo

duckdns.sh's Issues

security(minor): chmod 0700 ~/.config/duckdns.sh

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.

v1.0 RoadMap

  • Update with token via DuckDNS API (via dfea775)
  • Check current IP address (via ipify in 0cf350d)
  • Store last known IP (via dig in 0cf350d)
  • Do some math to calc 5 min, wait, loop (via dfea775)
  • enable subcommand to setup and install as a service (via 652e188)
  • init auth subcommand to setup new subdomain (via 652e188 and 05b4bb0)

Nits to take care of someday

Todos:

  • docs & help: the update actually happens every 1 minute
  • bump.sh should create a github release
    (the click-button output message is good enough)
  • Possibly store last known local IP address (lastip)
  • Force update every 24 hours?
  • something better than ipify for IPv6? (i.e. only IPv6, less logic)
  • webi: PR request.js to fix method HEAD changing to GET

Fix the PowerShell script (via Prompt Engineering)

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

v1.1 Roadmap

  • consistently quiet curl with -fsSL, and use --max-time 5.5 (via a2f4bde)
  • add version command and info (via eef5ded)
  • set execute bit
  • some sort of shell header info (name, desc, author and such) duckdns.sh version
  • Publish to Webi

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.