Git Product home page Git Product logo

duckdns.sh's Introduction

duckdns.sh's People

Contributors

coolaj86 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

duckdns.sh's Issues

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.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)

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

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.

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.